Merge "Drop prebuilt files to android source tree." into rvc-dev
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
index 35a2436..9376198 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -25,6 +25,7 @@
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
+import static android.os.UserHandle.USER_CURRENT;
import static android.os.UserHandle.USER_NULL;
import static com.android.server.blob.BlobStoreConfig.LOGV;
@@ -46,6 +47,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.blob.BlobHandle;
import android.app.blob.BlobInfo;
import android.app.blob.IBlobStoreManager;
@@ -1378,7 +1381,14 @@
+ "queryBlobsForUser()");
}
- return queryBlobsForUserInternal(userId);
+ final int resolvedUserId = userId == USER_CURRENT
+ ? ActivityManager.getCurrentUser() : userId;
+ // Don't allow any other special user ids apart from USER_CURRENT
+ final ActivityManagerInternal amInternal = LocalServices.getService(
+ ActivityManagerInternal.class);
+ amInternal.ensureNotSpecialUser(resolvedUserId);
+
+ return queryBlobsForUserInternal(resolvedUserId);
}
@Override
@@ -1479,12 +1489,13 @@
private static final int FLAG_DUMP_CONFIG = 1 << 2;
private int mSelectedSectionFlags;
- private boolean mDumpFull;
+ private boolean mDumpUnredacted;
private final ArrayList<String> mDumpPackages = new ArrayList<>();
private final ArrayList<Integer> mDumpUids = new ArrayList<>();
private final ArrayList<Integer> mDumpUserIds = new ArrayList<>();
private final ArrayList<Long> mDumpBlobIds = new ArrayList<>();
private boolean mDumpHelp;
+ private boolean mDumpAll;
public boolean shouldDumpSession(String packageName, int uid, long blobId) {
if (!CollectionUtils.isEmpty(mDumpPackages)
@@ -1503,7 +1514,7 @@
}
public boolean shouldDumpAllSections() {
- return mSelectedSectionFlags == 0;
+ return mDumpAll || (mSelectedSectionFlags == 0);
}
public void allowDumpSessions() {
@@ -1545,7 +1556,7 @@
}
public boolean shouldDumpFull() {
- return mDumpFull;
+ return mDumpUnredacted;
}
public boolean shouldDumpUser(int userId) {
@@ -1567,10 +1578,12 @@
for (int i = 0; i < args.length; ++i) {
final String opt = args[i];
- if ("--full".equals(opt) || "-f".equals(opt)) {
+ if ("--all".equals(opt) || "-a".equals(opt)) {
+ dumpArgs.mDumpAll = true;
+ } else if ("--unredacted".equals(opt) || "-u".equals(opt)) {
final int callingUid = Binder.getCallingUid();
if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
- dumpArgs.mDumpFull = true;
+ dumpArgs.mDumpUnredacted = true;
}
} else if ("--sessions".equals(opt)) {
dumpArgs.allowDumpSessions();
@@ -1580,7 +1593,7 @@
dumpArgs.allowDumpConfig();
} else if ("--package".equals(opt) || "-p".equals(opt)) {
dumpArgs.mDumpPackages.add(getStringArgRequired(args, ++i, "packageName"));
- } else if ("--uid".equals(opt) || "-u".equals(opt)) {
+ } else if ("--uid".equals(opt)) {
dumpArgs.mDumpUids.add(getIntArgRequired(args, ++i, "uid"));
} else if ("--user".equals(opt)) {
dumpArgs.mDumpUserIds.add(getIntArgRequired(args, ++i, "userId"));
diff --git a/apex/permission/framework/Android.bp b/apex/permission/framework/Android.bp
index 68c27a8..c43fabd 100644
--- a/apex/permission/framework/Android.bp
+++ b/apex/permission/framework/Android.bp
@@ -21,12 +21,18 @@
path: "java",
}
-java_library {
+java_sdk_library {
name: "framework-permission",
+ defaults: ["framework-module-defaults"],
srcs: [
":framework-permission-sources",
],
- sdk_version: "module_current",
+
+ // TODO(b/155480189) - Remove naming_scheme once references have been resolved.
+ // Temporary java_sdk_library component naming scheme to use to ease the transition from separate
+ // modules to java_sdk_library.
+ naming_scheme: "framework-modules",
+
apex_available: [
"com.android.permission",
"test_com.android.permission",
@@ -40,91 +46,5 @@
visibility: [
"//frameworks/base/apex/permission:__subpackages__",
],
-}
-
-stubs_defaults {
- name: "framework-permission-stubs-defaults",
- srcs: [ ":framework-permission-sources" ],
- libs: [ "framework-annotations-lib" ],
- dist: { dest: "framework-permission.txt" },
-}
-
-droidstubs {
- name: "framework-permission-stubs-srcs-publicapi",
- defaults: [
- "framework-module-stubs-defaults-publicapi",
- "framework-permission-stubs-defaults",
- ],
- check_api: {
- last_released: {
- api_file: ":framework-permission.api.public.latest",
- removed_api_file: ":framework-permission-removed.api.public.latest",
- },
- api_lint: {
- new_since: ":framework-permission.api.public.latest",
- },
- },
-}
-
-droidstubs {
- name: "framework-permission-stubs-srcs-systemapi",
- defaults: [
- "framework-module-stubs-defaults-systemapi",
- "framework-permission-stubs-defaults",
- ],
- check_api: {
- last_released: {
- api_file: ":framework-permission.api.system.latest",
- removed_api_file: ":framework-permission-removed.api.system.latest",
- },
- api_lint: {
- new_since: ":framework-permission.api.system.latest",
- },
- },
-}
-
-droidstubs {
- name: "framework-permission-api-module_libs_api",
- defaults: [
- "framework-module-api-defaults-module_libs_api",
- "framework-permission-stubs-defaults",
- ],
- check_api: {
- last_released: {
- api_file: ":framework-permission.api.module-lib.latest",
- removed_api_file: ":framework-permission-removed.api.module-lib.latest",
- },
- api_lint: {
- new_since: ":framework-permission.api.module-lib.latest",
- },
- },
-}
-
-droidstubs {
- name: "framework-permission-stubs-srcs-module_libs_api",
- defaults: [
- "framework-module-stubs-defaults-module_libs_api",
- "framework-permission-stubs-defaults",
- ],
-}
-
-java_library {
- name: "framework-permission-stubs-publicapi",
- srcs: [ ":framework-permission-stubs-srcs-publicapi" ],
- defaults: ["framework-module-stubs-lib-defaults-publicapi"],
- dist: { dest: "framework-permission.jar" },
-}
-
-java_library {
- name: "framework-permission-stubs-systemapi",
- srcs: [ ":framework-permission-stubs-srcs-systemapi" ],
- defaults: ["framework-module-stubs-lib-defaults-systemapi"],
- dist: { dest: "framework-permission.jar" },
-}
-
-java_library {
- name: "framework-permission-stubs-module_libs_api",
- srcs: [ ":framework-permission-stubs-srcs-module_libs_api" ],
- defaults: ["framework-module-stubs-lib-defaults-module_libs_api"],
- dist: { dest: "framework-permission.jar" },
+ stubs_library_visibility: ["//visibility:public"],
}
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
index 5cf5e0b..cbc8ed6 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
@@ -662,14 +662,19 @@
return;
}
+ // Cleann up from previous statsd - cancel any alarms that had been set. Do this here
+ // instead of in binder death because statsd can come back and set different alarms, or not
+ // want to set an alarm when it had been set. This guarantees that when we get a new statsd,
+ // we cancel any alarms before it is able to set them.
+ cancelAnomalyAlarm();
+ cancelPullingAlarm();
+ cancelAlarmForSubscriberTriggering();
+
if (DEBUG) Log.d(TAG, "Saying hi to statsd");
mStatsManagerService.statsdReady(statsd);
try {
statsd.statsCompanionReady();
- cancelAnomalyAlarm();
- cancelPullingAlarm();
-
BroadcastReceiver appUpdateReceiver = new AppUpdateReceiver();
BroadcastReceiver userUpdateReceiver = new UserUpdateReceiver();
BroadcastReceiver shutdownEventReceiver = new ShutdownEventReceiver();
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index ffd83ba..7090bd4 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -307,9 +307,6 @@
FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation);
FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsNoCondition);
FRIEND_TEST(GaugeMetricE2eTest, TestConditionChangeToTrueSamplePulledEvents);
- FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents);
- FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm);
- FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation);
FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket);
FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets);
@@ -328,6 +325,7 @@
FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation);
FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations);
+ FRIEND_TEST(CountMetricE2eTest, TestInitialConditionChanges);
FRIEND_TEST(CountMetricE2eTest, TestSlicedState);
FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap);
FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates);
@@ -345,6 +343,10 @@
FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat);
FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset);
+ FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges);
+ FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents);
+ FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm);
+ FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 8ba52ef..12158b3 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -4945,9 +4945,12 @@
/**
* Used for pull network statistics via mobile|wifi networks, and sliced by interesting dimensions.
- * Note that data is expected to be sliced into more dimensions in future. In other words,
- * the caller must not assume the data is unique when filtering with a set of matching conditions.
- * Thus, as the dimension grows, the caller will not be affected.
+ * Note that the data is expected to be sliced into more dimensions in future. In other words,
+ * the caller must not assume any row of data is one full report when filtering with a set of
+ * matching conditions, because future data may represent with multiple rows what is currently
+ * represented by one.
+ * To avoid being broken by future slicing, callers must take care to aggregate rows even if they
+ * query all the existing columns.
*
* Pulled from:
* StatsPullAtomService (using NetworkStatsService to get NetworkStats)
@@ -4972,21 +4975,26 @@
optional int32 rat_type = 6;
// Mcc/Mnc read from sim if the record is for a specific subscription, null indicates the
- // record is combined regardless of subscription.
+ // record is combined across subscriptions.
optional string sim_mcc = 7;
optional string sim_mnc = 8;
+ // Allows mobile virtual network operators (MVNOs) to be identified with individual IDs.
+ // See TelephonyManager#getSimCarrierId.
+ optional int32 carrier_id = 9;
+
// Enumeration of opportunistic states with an additional ALL state indicates the record is
// combined regardless of the boolean value in its field.
enum DataSubscriptionState {
+ UNKNOWN = 0; // For server side backward compatibility.
ALL = 1;
OPPORTUNISTIC = 2;
NOT_OPPORTUNISTIC = 3;
}
// Mark whether the subscription is an opportunistic data subscription, and ALL indicates the
- // record is combined regardless of opportunistic data subscription.
+ // record is combined across opportunistic data subscriptions.
// See {@link SubscriptionManager#setOpportunistic}.
- optional DataSubscriptionState opportunistic_data_sub = 9;
+ optional DataSubscriptionState opportunistic_data_sub = 10;
}
/**
@@ -9665,6 +9673,9 @@
SWITCH_USER = 1; // Indicates that this is a user switch event
START_USER = 2; // Indicates that this is a user start event
CREATE_USER = 3; // Indicates that this is a user create event
+ USER_RUNNING_LOCKED = 4; // Indicates that user is running in locked state
+ UNLOCKING_USER = 5; // Indicates that this is a user unlocking event
+ UNLOCKED_USER = 6; // Indicates that this is a user unlocked event
}
optional Event event = 3;
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 5fabb5f..e86fdf0 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -459,6 +459,7 @@
FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap);
FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates);
FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithPrimaryFields);
+ FRIEND_TEST(CountMetricE2eTest, TestInitialConditionChanges);
FRIEND_TEST(DurationMetricE2eTest, TestOneBucket);
FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets);
@@ -488,6 +489,7 @@
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions);
+ FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index c30532a..ad30a88 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -292,9 +292,6 @@
FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation);
FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsNoCondition);
FRIEND_TEST(GaugeMetricE2eTest, TestConditionChangeToTrueSamplePulledEvents);
- FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents);
- FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm);
- FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation);
FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket);
FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets);
@@ -322,6 +319,7 @@
TestActivationOnBootMultipleActivationsDifferentActivationTypes);
FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart);
+ FRIEND_TEST(CountMetricE2eTest, TestInitialConditionChanges);
FRIEND_TEST(CountMetricE2eTest, TestSlicedState);
FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap);
FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates);
@@ -339,6 +337,10 @@
FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSuperset);
FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset);
+ FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges);
+ FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents);
+ FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm);
+ FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions);
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 7c0057d..2e6043d 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -131,7 +131,7 @@
UNKNOWN = 0;
FALSE = 1;
}
- optional InitialValue initial_value = 5 [default = FALSE];
+ optional InitialValue initial_value = 5 [default = UNKNOWN];
optional FieldMatcher dimensions = 6;
}
diff --git a/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp
index 1a7cd55..04eb400 100644
--- a/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp
@@ -28,6 +28,92 @@
#ifdef __ANDROID__
/**
+ * Tests the initial condition and condition after the first log events for
+ * count metrics with either a combination condition or simple condition.
+ *
+ * Metrics should be initialized with condition kUnknown (given that the
+ * predicate is using the default InitialValue of UNKNOWN). The condition should
+ * be updated to either kFalse or kTrue if a condition event is logged for all
+ * children conditions.
+ */
+TEST(CountMetricE2eTest, TestInitialConditionChanges) {
+ // Initialize config.
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+ config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root.
+
+ auto syncStartMatcher = CreateSyncStartAtomMatcher();
+ *config.add_atom_matcher() = syncStartMatcher;
+ *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
+ *config.add_atom_matcher() = CreateBatteryStateNoneMatcher();
+ *config.add_atom_matcher() = CreateBatteryStateUsbMatcher();
+
+ auto screenOnPredicate = CreateScreenIsOnPredicate();
+ *config.add_predicate() = screenOnPredicate;
+
+ auto deviceUnpluggedPredicate = CreateDeviceUnpluggedPredicate();
+ *config.add_predicate() = deviceUnpluggedPredicate;
+
+ auto screenOnOnBatteryPredicate = config.add_predicate();
+ screenOnOnBatteryPredicate->set_id(StringToId("screenOnOnBatteryPredicate"));
+ screenOnOnBatteryPredicate->mutable_combination()->set_operation(LogicalOperation::AND);
+ addPredicateToPredicateCombination(screenOnPredicate, screenOnOnBatteryPredicate);
+ addPredicateToPredicateCombination(deviceUnpluggedPredicate, screenOnOnBatteryPredicate);
+
+ // CountSyncStartWhileScreenOnOnBattery (CombinationCondition)
+ CountMetric* countMetric1 = config.add_count_metric();
+ countMetric1->set_id(StringToId("CountSyncStartWhileScreenOnOnBattery"));
+ countMetric1->set_what(syncStartMatcher.id());
+ countMetric1->set_condition(screenOnOnBatteryPredicate->id());
+ countMetric1->set_bucket(FIVE_MINUTES);
+
+ // CountSyncStartWhileOnBattery (SimpleCondition)
+ CountMetric* countMetric2 = config.add_count_metric();
+ countMetric2->set_id(StringToId("CountSyncStartWhileOnBatterySliceScreen"));
+ countMetric2->set_what(syncStartMatcher.id());
+ countMetric2->set_condition(deviceUnpluggedPredicate.id());
+ countMetric2->set_bucket(FIVE_MINUTES);
+
+ const uint64_t bucketStartTimeNs = 10000000000; // 0:10
+ const uint64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
+ int uid = 12345;
+ int64_t cfgId = 98765;
+ ConfigKey cfgKey(uid, cfgId);
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+ EXPECT_TRUE(metricsManager->isConfigValid());
+ EXPECT_EQ(2, metricsManager->mAllMetricProducers.size());
+
+ sp<MetricProducer> metricProducer1 = metricsManager->mAllMetricProducers[0];
+ sp<MetricProducer> metricProducer2 = metricsManager->mAllMetricProducers[1];
+
+ EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition);
+ EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition);
+
+ auto screenOnEvent =
+ CreateScreenStateChangedEvent(bucketStartTimeNs + 30, android::view::DISPLAY_STATE_ON);
+ processor->OnLogEvent(screenOnEvent.get());
+ EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition);
+ EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition);
+
+ auto pluggedUsbEvent = CreateBatteryStateChangedEvent(
+ bucketStartTimeNs + 50, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB);
+ processor->OnLogEvent(pluggedUsbEvent.get());
+ EXPECT_EQ(ConditionState::kFalse, metricProducer1->mCondition);
+ EXPECT_EQ(ConditionState::kFalse, metricProducer2->mCondition);
+
+ auto pluggedNoneEvent = CreateBatteryStateChangedEvent(
+ bucketStartTimeNs + 70, BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE);
+ processor->OnLogEvent(pluggedNoneEvent.get());
+ EXPECT_EQ(ConditionState::kTrue, metricProducer1->mCondition);
+ EXPECT_EQ(ConditionState::kTrue, metricProducer2->mCondition);
+}
+
+/**
* Test a count metric that has one slice_by_state with no primary fields.
*
* Once the CountMetricProducer is initialized, it has one atom id in
@@ -85,7 +171,7 @@
x x x x x x (syncStartEvents)
| | (ScreenIsOnEvent)
| | (ScreenIsOffEvent)
- | (ScreenUnknownEvent)
+ | (ScreenDozeEvent)
*/
// Initialize log events - first bucket.
std::vector<int> attributionUids1 = {123};
@@ -243,9 +329,8 @@
|-----------------------------|-----------------------------|--
x x x x x x x x x (syncStartEvents)
-----------------------------------------------------------SCREEN_OFF events
- | (ScreenStateUnknownEvent = 0)
| | (ScreenStateOffEvent = 1)
- | (ScreenStateDozeEvent = 3)
+ | | (ScreenStateDozeEvent = 3)
| (ScreenStateDozeSuspendEvent =
4)
-----------------------------------------------------------SCREEN_ON events
@@ -262,7 +347,7 @@
attributionTags1, "sync_name")); // 0:30
events.push_back(CreateScreenStateChangedEvent(
bucketStartTimeNs + 30 * NS_PER_SEC,
- android::view::DisplayStateEnum::DISPLAY_STATE_UNKNOWN)); // 0:40
+ android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 0:40
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 60 * NS_PER_SEC, attributionUids1,
attributionTags1, "sync_name")); // 1:10
events.push_back(CreateScreenStateChangedEvent(
@@ -625,9 +710,8 @@
|------------------------|------------------------|--
1 1 1 1 1 2 1 1 2 (AppCrashEvents)
---------------------------------------------------SCREEN_OFF events
- | (ScreenUnknownEvent = 0)
| | (ScreenOffEvent = 1)
- | (ScreenDozeEvent = 3)
+ | | (ScreenDozeEvent = 3)
---------------------------------------------------SCREEN_ON events
| | (ScreenOnEvent = 2)
| (ScreenOnSuspendEvent = 6)
@@ -660,7 +744,7 @@
CreateAppCrashOccurredEvent(bucketStartTimeNs + 20 * NS_PER_SEC, 1 /*uid*/)); // 0:30
events.push_back(CreateScreenStateChangedEvent(
bucketStartTimeNs + 30 * NS_PER_SEC,
- android::view::DisplayStateEnum::DISPLAY_STATE_UNKNOWN)); // 0:40
+ android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 0:40
events.push_back(
CreateAppCrashOccurredEvent(bucketStartTimeNs + 60 * NS_PER_SEC, 1 /*uid*/)); // 1:10
events.push_back(CreateUidProcessStateChangedEvent(
diff --git a/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp b/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
index e595f29..4d39282 100644
--- a/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
@@ -63,8 +63,143 @@
return config;
}
+StatsdConfig CreateStatsdConfigWithStates() {
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+ config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root.
+
+ auto pulledAtomMatcher = CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE);
+ *config.add_atom_matcher() = pulledAtomMatcher;
+ *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
+ *config.add_atom_matcher() = CreateBatteryStateNoneMatcher();
+ *config.add_atom_matcher() = CreateBatteryStateUsbMatcher();
+
+ auto screenOnPredicate = CreateScreenIsOnPredicate();
+ *config.add_predicate() = screenOnPredicate;
+
+ auto screenOffPredicate = CreateScreenIsOffPredicate();
+ *config.add_predicate() = screenOffPredicate;
+
+ auto deviceUnpluggedPredicate = CreateDeviceUnpluggedPredicate();
+ *config.add_predicate() = deviceUnpluggedPredicate;
+
+ auto screenOnOnBatteryPredicate = config.add_predicate();
+ screenOnOnBatteryPredicate->set_id(StringToId("screenOnOnBatteryPredicate"));
+ screenOnOnBatteryPredicate->mutable_combination()->set_operation(LogicalOperation::AND);
+ addPredicateToPredicateCombination(screenOnPredicate, screenOnOnBatteryPredicate);
+ addPredicateToPredicateCombination(deviceUnpluggedPredicate, screenOnOnBatteryPredicate);
+
+ auto screenOffOnBatteryPredicate = config.add_predicate();
+ screenOffOnBatteryPredicate->set_id(StringToId("ScreenOffOnBattery"));
+ screenOffOnBatteryPredicate->mutable_combination()->set_operation(LogicalOperation::AND);
+ addPredicateToPredicateCombination(screenOffPredicate, screenOffOnBatteryPredicate);
+ addPredicateToPredicateCombination(deviceUnpluggedPredicate, screenOffOnBatteryPredicate);
+
+ const State screenState =
+ CreateScreenStateWithSimpleOnOffMap(/*screen on id=*/321, /*screen off id=*/123);
+ *config.add_state() = screenState;
+
+ // ValueMetricSubsystemSleepWhileScreenOnOnBattery
+ auto valueMetric1 = config.add_value_metric();
+ valueMetric1->set_id(metricId);
+ valueMetric1->set_what(pulledAtomMatcher.id());
+ valueMetric1->set_condition(screenOnOnBatteryPredicate->id());
+ *valueMetric1->mutable_value_field() =
+ CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
+ valueMetric1->set_bucket(FIVE_MINUTES);
+ valueMetric1->set_use_absolute_value_on_reset(true);
+ valueMetric1->set_skip_zero_diff_output(false);
+ valueMetric1->set_max_pull_delay_sec(INT_MAX);
+
+ // ValueMetricSubsystemSleepWhileScreenOffOnBattery
+ ValueMetric* valueMetric2 = config.add_value_metric();
+ valueMetric2->set_id(StringToId("ValueMetricSubsystemSleepWhileScreenOffOnBattery"));
+ valueMetric2->set_what(pulledAtomMatcher.id());
+ valueMetric2->set_condition(screenOffOnBatteryPredicate->id());
+ *valueMetric2->mutable_value_field() =
+ CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
+ valueMetric2->set_bucket(FIVE_MINUTES);
+ valueMetric2->set_use_absolute_value_on_reset(true);
+ valueMetric2->set_skip_zero_diff_output(false);
+ valueMetric2->set_max_pull_delay_sec(INT_MAX);
+
+ // ValueMetricSubsystemSleepWhileOnBatterySliceScreen
+ ValueMetric* valueMetric3 = config.add_value_metric();
+ valueMetric3->set_id(StringToId("ValueMetricSubsystemSleepWhileOnBatterySliceScreen"));
+ valueMetric3->set_what(pulledAtomMatcher.id());
+ valueMetric3->set_condition(deviceUnpluggedPredicate.id());
+ *valueMetric3->mutable_value_field() =
+ CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
+ valueMetric3->add_slice_by_state(screenState.id());
+ valueMetric3->set_bucket(FIVE_MINUTES);
+ valueMetric3->set_use_absolute_value_on_reset(true);
+ valueMetric3->set_skip_zero_diff_output(false);
+ valueMetric3->set_max_pull_delay_sec(INT_MAX);
+ return config;
+}
+
} // namespace
+/**
+ * Tests the initial condition and condition after the first log events for
+ * value metrics with either a combination condition or simple condition.
+ *
+ * Metrics should be initialized with condition kUnknown (given that the
+ * predicate is using the default InitialValue of UNKNOWN). The condition should
+ * be updated to either kFalse or kTrue if a condition event is logged for all
+ * children conditions.
+ */
+TEST(ValueMetricE2eTest, TestInitialConditionChanges) {
+ StatsdConfig config = CreateStatsdConfigWithStates();
+ int64_t baseTimeNs = getElapsedRealtimeNs();
+ int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
+ int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000;
+
+ ConfigKey cfgKey;
+ int32_t tagId = util::SUBSYSTEM_SLEEP_STATE;
+ auto processor =
+ CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
+ SharedRefBase::make<FakeSubsystemSleepCallback>(), tagId);
+
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+ EXPECT_TRUE(metricsManager->isConfigValid());
+ EXPECT_EQ(3, metricsManager->mAllMetricProducers.size());
+
+ // Combination condition metric - screen on and device unplugged
+ sp<MetricProducer> metricProducer1 = metricsManager->mAllMetricProducers[0];
+ // Simple condition metric - device unplugged
+ sp<MetricProducer> metricProducer2 = metricsManager->mAllMetricProducers[2];
+
+ EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition);
+ EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition);
+
+ auto screenOnEvent =
+ CreateScreenStateChangedEvent(configAddedTimeNs + 30, android::view::DISPLAY_STATE_ON);
+ processor->OnLogEvent(screenOnEvent.get());
+ EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition);
+ EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition);
+
+ auto screenOffEvent =
+ CreateScreenStateChangedEvent(configAddedTimeNs + 40, android::view::DISPLAY_STATE_OFF);
+ processor->OnLogEvent(screenOffEvent.get());
+ EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition);
+ EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition);
+
+ auto pluggedUsbEvent = CreateBatteryStateChangedEvent(
+ configAddedTimeNs + 50, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB);
+ processor->OnLogEvent(pluggedUsbEvent.get());
+ EXPECT_EQ(ConditionState::kFalse, metricProducer1->mCondition);
+ EXPECT_EQ(ConditionState::kFalse, metricProducer2->mCondition);
+
+ auto pluggedNoneEvent = CreateBatteryStateChangedEvent(
+ configAddedTimeNs + 70, BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE);
+ processor->OnLogEvent(pluggedNoneEvent.get());
+ EXPECT_EQ(ConditionState::kFalse, metricProducer1->mCondition);
+ EXPECT_EQ(ConditionState::kTrue, metricProducer2->mCondition);
+}
+
TEST(ValueMetricE2eTest, TestPulledEvents) {
auto config = CreateStatsdConfig();
int64_t baseTimeNs = getElapsedRealtimeNs();
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 6a7ad1f..582df0c 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -169,7 +169,6 @@
return atom_matcher;
}
-
AtomMatcher CreateScreenTurnedOnAtomMatcher() {
return CreateScreenStateChangedAtomMatcher("ScreenTurnedOn",
android::view::DisplayStateEnum::DISPLAY_STATE_ON);
@@ -335,22 +334,46 @@
return state;
}
+State CreateScreenStateWithSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId) {
+ State state;
+ state.set_id(StringToId("ScreenStateSimpleOnOff"));
+ state.set_atom_id(util::SCREEN_STATE_CHANGED);
+
+ auto map = CreateScreenStateSimpleOnOffMap(screenOnId, screenOffId);
+ *state.mutable_map() = map;
+
+ return state;
+}
+
StateMap_StateGroup CreateScreenStateOnGroup(int64_t screenOnId) {
StateMap_StateGroup group;
group.set_group_id(screenOnId);
- group.add_value(2);
- group.add_value(5);
- group.add_value(6);
+ group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+ group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_VR);
+ group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_ON_SUSPEND);
return group;
}
StateMap_StateGroup CreateScreenStateOffGroup(int64_t screenOffId) {
StateMap_StateGroup group;
group.set_group_id(screenOffId);
- group.add_value(0);
- group.add_value(1);
- group.add_value(3);
- group.add_value(4);
+ group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
+ group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE);
+ group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE_SUSPEND);
+ return group;
+}
+
+StateMap_StateGroup CreateScreenStateSimpleOnGroup(int64_t screenOnId) {
+ StateMap_StateGroup group;
+ group.set_group_id(screenOnId);
+ group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+ return group;
+}
+
+StateMap_StateGroup CreateScreenStateSimpleOffGroup(int64_t screenOffId) {
+ StateMap_StateGroup group;
+ group.set_group_id(screenOffId);
+ group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
return group;
}
@@ -361,6 +384,13 @@
return map;
}
+StateMap CreateScreenStateSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId) {
+ StateMap map;
+ *map.add_group() = CreateScreenStateSimpleOnGroup(screenOnId);
+ *map.add_group() = CreateScreenStateSimpleOffGroup(screenOffId);
+ return map;
+}
+
void addPredicateToPredicateCombination(const Predicate& predicate,
Predicate* combinationPredicate) {
combinationPredicate->mutable_combination()->add_predicate(predicate.id());
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index dc012c5..6a5d5da 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -149,17 +149,30 @@
// Create State proto for overlay state atom.
State CreateOverlayState();
+// Create State proto for screen state atom with on/off map.
State CreateScreenStateWithOnOffMap(int64_t screenOnId, int64_t screenOffId);
+// Create State proto for screen state atom with simple on/off map.
+State CreateScreenStateWithSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId);
+
// Create StateGroup proto for ScreenState ON group
StateMap_StateGroup CreateScreenStateOnGroup(int64_t screenOnId);
// Create StateGroup proto for ScreenState OFF group
StateMap_StateGroup CreateScreenStateOffGroup(int64_t screenOffId);
+// Create StateGroup proto for simple ScreenState ON group
+StateMap_StateGroup CreateScreenStateSimpleOnGroup(int64_t screenOnId);
+
+// Create StateGroup proto for simple ScreenState OFF group
+StateMap_StateGroup CreateScreenStateSimpleOffGroup(int64_t screenOffId);
+
// Create StateMap proto for ScreenState ON/OFF map
StateMap CreateScreenStateOnOffMap(int64_t screenOnId, int64_t screenOffId);
+// Create StateMap proto for simple ScreenState ON/OFF map
+StateMap CreateScreenStateSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId);
+
// Add a predicate to the predicate combination.
void addPredicateToPredicateCombination(const Predicate& predicate, Predicate* combination);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 8073982..c650643 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -6031,10 +6031,12 @@
* this API to enforce auto time will result in
* {@link UserManager#DISALLOW_CONFIG_DATE_TIME} being set, while calling this API to lift
* the requirement will result in {@link UserManager#DISALLOW_CONFIG_DATE_TIME} being cleared.
+ * From Android 11, this API can also no longer be called on a managed profile.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param required Whether auto time is set required or not.
- * @throws SecurityException if {@code admin} is not a device owner.
+ * @throws SecurityException if {@code admin} is not a device owner, not a profile owner or
+ * if this API is called on a managed profile.
* @deprecated From {@link android.os.Build.VERSION_CODES#R}. Use {@link #setAutoTimeEnabled}
* to turn auto time on or off and use {@link UserManager#DISALLOW_CONFIG_DATE_TIME}
* to prevent the user from changing this setting.
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index 75ce0dc..3fef92b 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -625,7 +625,10 @@
}
}
defaultView = inflater.inflate(layoutId, this, false);
- defaultView.setOnClickListener(this::onDefaultViewClicked);
+ if (!(defaultView instanceof AdapterView)) {
+ // AdapterView does not support onClickListener
+ defaultView.setOnClickListener(this::onDefaultViewClicked);
+ }
} else {
Log.w(TAG, "can't inflate defaultView because mInfo is missing");
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 312e98e..d086459 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -127,7 +127,6 @@
import java.util.List;
import java.util.Set;
import java.util.UUID;
-import java.util.concurrent.atomic.AtomicInteger;
/**
* Parser for package files (APKs) on disk. This supports apps packaged either
@@ -239,11 +238,6 @@
public static final boolean LOG_UNSAFE_BROADCASTS = false;
- /**
- * Total number of packages that were read from the cache. We use it only for logging.
- */
- public static final AtomicInteger sCachedPackageReadCount = new AtomicInteger();
-
// Set of broadcast actions that are safe for manifest receivers
public static final Set<String> SAFE_BROADCASTS = new ArraySet<>();
static {
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index 9b809b8..b978ae5 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -191,6 +191,58 @@
}
}
+ /** {@hide} */
+ public static long executeInsert(@NonNull SQLiteDatabase db, @NonNull String sql,
+ @Nullable Object[] bindArgs) throws SQLException {
+ try (SQLiteStatement st = db.compileStatement(sql)) {
+ bindArgs(st, bindArgs);
+ return st.executeInsert();
+ }
+ }
+
+ /** {@hide} */
+ public static int executeUpdateDelete(@NonNull SQLiteDatabase db, @NonNull String sql,
+ @Nullable Object[] bindArgs) throws SQLException {
+ try (SQLiteStatement st = db.compileStatement(sql)) {
+ bindArgs(st, bindArgs);
+ return st.executeUpdateDelete();
+ }
+ }
+
+ /** {@hide} */
+ private static void bindArgs(@NonNull SQLiteStatement st, @Nullable Object[] bindArgs) {
+ if (bindArgs == null) return;
+
+ for (int i = 0; i < bindArgs.length; i++) {
+ final Object bindArg = bindArgs[i];
+ switch (getTypeOfObject(bindArg)) {
+ case Cursor.FIELD_TYPE_NULL:
+ st.bindNull(i + 1);
+ break;
+ case Cursor.FIELD_TYPE_INTEGER:
+ st.bindLong(i + 1, ((Number) bindArg).longValue());
+ break;
+ case Cursor.FIELD_TYPE_FLOAT:
+ st.bindDouble(i + 1, ((Number) bindArg).doubleValue());
+ break;
+ case Cursor.FIELD_TYPE_BLOB:
+ st.bindBlob(i + 1, (byte[]) bindArg);
+ break;
+ case Cursor.FIELD_TYPE_STRING:
+ default:
+ if (bindArg instanceof Boolean) {
+ // Provide compatibility with legacy
+ // applications which may pass Boolean values in
+ // bind args.
+ st.bindLong(i + 1, ((Boolean) bindArg).booleanValue() ? 1 : 0);
+ } else {
+ st.bindString(i + 1, bindArg.toString());
+ }
+ break;
+ }
+ }
+ }
+
/**
* Binds the given Object to the given SQLiteProgram using the proper
* typing. For example, bind numbers as longs/doubles, and everything else
diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
index 36ec67e..669d046 100644
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -626,7 +626,7 @@
Log.d(TAG, sql);
}
}
- return db.executeSql(sql, sqlArgs);
+ return DatabaseUtils.executeInsert(db, sql, sqlArgs);
}
/**
@@ -702,7 +702,7 @@
Log.d(TAG, sql);
}
}
- return db.executeSql(sql, sqlArgs);
+ return DatabaseUtils.executeUpdateDelete(db, sql, sqlArgs);
}
/**
@@ -762,7 +762,7 @@
Log.d(TAG, sql);
}
}
- return db.executeSql(sql, sqlArgs);
+ return DatabaseUtils.executeUpdateDelete(db, sql, sqlArgs);
}
private void enforceStrictColumns(@Nullable String[] projection) {
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 230aa04..cf133d2 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -138,10 +138,25 @@
* client camera application. Using these camera devices concurrently by two different
* applications is not guaranteed to be supported, however.</p>
*
+ * <p>For concurrent operation, in chronological order :
+ * - Applications must first close any open cameras that have sessions configured, using
+ * {@link CameraDevice#close}.
+ * - All camera devices intended to be operated concurrently, must be opened using
+ * {@link #openCamera}, before configuring sessions on any of the camera devices.</p>
+ *
* <p>Each device in a combination, is guaranteed to support stream combinations which may be
* obtained by querying {@link #getCameraCharacteristics} for the key
* {@link android.hardware.camera2.CameraCharacteristics#SCALER_MANDATORY_CONCURRENT_STREAM_COMBINATIONS}.</p>
*
+ * <p>For concurrent operation, if a camera device has a non null zoom ratio range as specified
+ * by
+ * {@link android.hardware.camera2.CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE},
+ * its complete zoom ratio range may not apply. Applications can use
+ * {@link android.hardware.camera2.CaptureRequest#CONTROL_ZOOM_RATIO} >=1 and <=
+ * {@link android.hardware.camera2.CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM}
+ * during concurrent operation.
+ * <p>
+ *
* <p>The set of combinations may include camera devices that may be in use by other camera API
* clients.</p>
*
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 16e5156..66b8cab 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -162,12 +162,9 @@
/** {@hide} */
public static final String PROP_SETTINGS_FUSE = FeatureFlagUtils.PERSIST_PREFIX
+ FeatureFlagUtils.SETTINGS_FUSE_FLAG;
- /**
- * Property that determines whether {@link OP_LEGACY_STORAGE} is sticky for
- * legacy apps.
- * @hide
- */
- public static final String PROP_LEGACY_OP_STICKY = "persist.sys.legacy_storage_sticky";
+ /** {@hide} */
+ public static final String PROP_FORCED_SCOPED_STORAGE_WHITELIST =
+ "forced_scoped_storage_whitelist";
/** {@hide} */
public static final String UUID_PRIVATE_INTERNAL = null;
diff --git a/core/java/android/util/AtomicFile.java b/core/java/android/util/AtomicFile.java
index da7503d..e0d857a 100644
--- a/core/java/android/util/AtomicFile.java
+++ b/core/java/android/util/AtomicFile.java
@@ -116,10 +116,7 @@
mStartTime = startTime;
if (mLegacyBackupName.exists()) {
- if (!mLegacyBackupName.renameTo(mBaseName)) {
- Log.e(LOG_TAG, "Failed to rename legacy backup file " + mLegacyBackupName
- + " to base file " + mBaseName);
- }
+ rename(mLegacyBackupName, mBaseName);
}
try {
@@ -157,9 +154,7 @@
} catch (IOException e) {
Log.e(LOG_TAG, "Failed to close file output stream", e);
}
- if (!mNewName.renameTo(mBaseName)) {
- Log.e(LOG_TAG, "Failed to rename new file " + mNewName + " to base file " + mBaseName);
- }
+ rename(mNewName, mBaseName);
if (mCommitTag != null) {
com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(
mCommitTag, SystemClock.uptimeMillis() - mStartTime);
@@ -221,13 +216,17 @@
*/
public FileInputStream openRead() throws FileNotFoundException {
if (mLegacyBackupName.exists()) {
- if (!mLegacyBackupName.renameTo(mBaseName)) {
- Log.e(LOG_TAG, "Failed to rename legacy backup file " + mLegacyBackupName
- + " to base file " + mBaseName);
- }
+ rename(mLegacyBackupName, mBaseName);
}
- if (mNewName.exists()) {
+ // It was okay to call openRead() between startWrite() and finishWrite() for the first time
+ // (because there is no backup file), where openRead() would open the file being written,
+ // which makes no sense, but finishWrite() would still persist the write properly. For all
+ // subsequent writes, if openRead() was called in between, it would see a backup file and
+ // delete the file being written, the same behavior as our new implementation. So we only
+ // need a special case for the first write, and don't delete the new file in this case so
+ // that finishWrite() can still work.
+ if (mNewName.exists() && mBaseName.exists()) {
if (!mNewName.delete()) {
Log.e(LOG_TAG, "Failed to delete outdated new file " + mNewName);
}
@@ -304,4 +303,21 @@
IoUtils.closeQuietly(out);
}
}
+
+ private static void rename(File source, File target) {
+ // We used to delete the target file before rename, but that isn't atomic, and the rename()
+ // syscall should atomically replace the target file. However in the case where the target
+ // file is a directory, a simple rename() won't work. We need to delete the file in this
+ // case because there are callers who erroneously called mBaseName.mkdirs() (instead of
+ // mBaseName.getParentFile().mkdirs()) before creating the AtomicFile, and it worked
+ // regardless, so this deletion became some kind of API.
+ if (target.isDirectory()) {
+ if (!target.delete()) {
+ Log.e(LOG_TAG, "Failed to delete file which is a directory " + target);
+ }
+ }
+ if (!source.renameTo(target)) {
+ Log.e(LOG_TAG, "Failed to rename " + source + " to " + target);
+ }
+ }
}
diff --git a/core/java/android/view/IDisplayWindowListener.aidl b/core/java/android/view/IDisplayWindowListener.aidl
index 973a208..610e0f8 100644
--- a/core/java/android/view/IDisplayWindowListener.aidl
+++ b/core/java/android/view/IDisplayWindowListener.aidl
@@ -46,4 +46,13 @@
*/
void onDisplayRemoved(int displayId);
+ /**
+ * Called when fixed rotation is started on a display.
+ */
+ void onFixedRotationStarted(int displayId, int newRotation);
+
+ /**
+ * Called when the previous fixed rotation on a display is finished.
+ */
+ void onFixedRotationFinished(int displayId);
}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index d12a122..758062f 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -568,15 +568,11 @@
private void updateState(InsetsState newState) {
mState.setDisplayFrame(newState.getDisplayFrame());
for (int i = newState.getSourcesCount() - 1; i >= 0; i--) {
- final InsetsSource source = newState.sourceAt(i);
- final int type = source.getType();
- final InsetsSourceConsumer consumer = getSourceConsumer(type);
- consumer.updateSource(source);
- mHost.updateCompatSysUiVisibility(type, source.isVisible(),
- consumer.getControl() != null);
+ InsetsSource source = newState.sourceAt(i);
+ getSourceConsumer(source.getType()).updateSource(source);
}
for (int i = mState.getSourcesCount() - 1; i >= 0; i--) {
- final InsetsSource source = mState.sourceAt(i);
+ InsetsSource source = mState.sourceAt(i);
if (newState.peekSource(source.getType()) == null) {
mState.removeSource(source.getType());
}
@@ -1011,6 +1007,14 @@
}
/**
+ * @see ViewRootImpl#updateCompatSysUiVisibility(int, boolean, boolean)
+ */
+ public void updateCompatSysUiVisibility(@InternalInsetsType int type, boolean visible,
+ boolean hasControl) {
+ mHost.updateCompatSysUiVisibility(type, visible, hasControl);
+ }
+
+ /**
* Called when current window gains focus.
*/
public void onWindowFocusGained() {
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index a0cdcfe..df3ac87 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -18,6 +18,7 @@
import static android.view.InsetsController.ANIMATION_TYPE_NONE;
import static android.view.InsetsController.AnimationType;
+import static android.view.InsetsState.getDefaultVisibility;
import static android.view.InsetsState.toPublicType;
import android.annotation.IntDef;
@@ -82,7 +83,7 @@
mState = state;
mTransactionSupplier = transactionSupplier;
mController = controller;
- mRequestedVisible = InsetsState.getDefaultVisibility(type);
+ mRequestedVisible = getDefaultVisibility(type);
}
/**
@@ -200,12 +201,20 @@
}
boolean applyLocalVisibilityOverride() {
+ final InsetsSource source = mState.peekSource(mType);
+ final boolean isVisible = source != null ? source.isVisible() : getDefaultVisibility(mType);
+ final boolean hasControl = mSourceControl != null;
+
+ // We still need to let the legacy app know the visibility change even if we don't have the
+ // control.
+ mController.updateCompatSysUiVisibility(
+ mType, hasControl ? mRequestedVisible : isVisible, hasControl);
// If we don't have control, we are not able to change the visibility.
- if (mSourceControl == null) {
+ if (!hasControl) {
return false;
}
- if (mState.getSource(mType).isVisible() == mRequestedVisible) {
+ if (isVisible == mRequestedVisible) {
return false;
}
mState.getSource(mType).setVisible(mRequestedVisible);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index d928356..511e755 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1977,6 +1977,7 @@
(mCompatibleVisibilityInfo.globalVisibility & ~View.SYSTEM_UI_FLAG_LOW_PROFILE)
| (mAttachInfo.mSystemUiVisibility & View.SYSTEM_UI_FLAG_LOW_PROFILE);
if (mDispatchedSystemUiVisibility != mCompatibleVisibilityInfo.globalVisibility) {
+ mHandler.removeMessages(MSG_DISPATCH_SYSTEM_UI_VISIBILITY);
mHandler.sendMessage(mHandler.obtainMessage(
MSG_DISPATCH_SYSTEM_UI_VISIBILITY, mCompatibleVisibilityInfo));
}
@@ -2031,8 +2032,10 @@
}
} else {
info.globalVisibility |= systemUiFlag;
+ info.localChanges &= ~systemUiFlag;
}
if (mDispatchedSystemUiVisibility != info.globalVisibility) {
+ mHandler.removeMessages(MSG_DISPATCH_SYSTEM_UI_VISIBILITY);
mHandler.sendMessage(mHandler.obtainMessage(MSG_DISPATCH_SYSTEM_UI_VISIBILITY, info));
}
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 39c7210..301ce9f 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -166,6 +166,8 @@
public static final int FLUSH_REASON_IDLE_TIMEOUT = 5;
/** @hide */
public static final int FLUSH_REASON_TEXT_CHANGE_TIMEOUT = 6;
+ /** @hide */
+ public static final int FLUSH_REASON_SESSION_CONNECTED = 7;
/** @hide */
@IntDef(prefix = { "FLUSH_REASON_" }, value = {
@@ -174,7 +176,8 @@
FLUSH_REASON_SESSION_STARTED,
FLUSH_REASON_SESSION_FINISHED,
FLUSH_REASON_IDLE_TIMEOUT,
- FLUSH_REASON_TEXT_CHANGE_TIMEOUT
+ FLUSH_REASON_TEXT_CHANGE_TIMEOUT,
+ FLUSH_REASON_SESSION_CONNECTED
})
@Retention(RetentionPolicy.SOURCE)
public @interface FlushReason{}
@@ -609,6 +612,8 @@
return "IDLE";
case FLUSH_REASON_TEXT_CHANGE_TIMEOUT:
return "TEXT_CHANGE";
+ case FLUSH_REASON_SESSION_CONNECTED:
+ return "CONNECTED";
default:
return "UNKOWN-" + reason;
}
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 893d38d..6eb71f7 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -273,6 +273,8 @@
} else {
mState = resultCode;
mDisabled.set(false);
+ // Flush any pending data immediately as buffering forced until now.
+ flushIfNeeded(FLUSH_REASON_SESSION_CONNECTED);
}
if (sVerbose) {
Log.v(TAG, "handleSessionStarted() result: id=" + mId + " resultCode=" + resultCode
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index f78ec7c..0807f41 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -219,17 +219,34 @@
}
/**
- * Tell the client to display a confirm dialog to the user. If the client
- * returns {@code true}, WebView will assume that the client will handle the
- * confirm dialog and call the appropriate JsResult method. If the
- * client returns false, a default value of {@code false} will be returned to
- * javascript. The default behavior is to return {@code false}.
+ * Notify the host application that the web page wants to display a
+ * JavaScript {@code confirm()} dialog.
+ * <p>The default behavior if this method returns {@code false} or is not
+ * overridden is to show a dialog containing the message and suspend
+ * JavaScript execution until the dialog is dismissed. The default dialog
+ * will return {@code true} to the JavaScript {@code confirm()} code when
+ * the user presses the 'confirm' button, and will return {@code false} to
+ * the JavaScript code when the user presses the 'cancel' button or
+ * dismisses the dialog.
+ * <p>To show a custom dialog, the app should return {@code true} from this
+ * method, in which case the default dialog will not be shown and JavaScript
+ * execution will be suspended. The app should call
+ * {@code JsResult.confirm()} or {@code JsResult.cancel()} when the custom
+ * dialog is dismissed.
+ * <p>To suppress the dialog and allow JavaScript execution to continue,
+ * call {@code JsResult.confirm()} or {@code JsResult.cancel()} immediately
+ * and then return {@code true}.
+ * <p>Note that if the {@link WebChromeClient} is {@code null}, the default
+ * dialog will be suppressed and the default value of {@code false} will be
+ * returned to the JavaScript code immediately.
+ *
* @param view The WebView that initiated the callback.
* @param url The url of the page requesting the dialog.
* @param message Message to be displayed in the window.
* @param result A JsResult used to send the user's response to
* javascript.
- * @return boolean Whether the client will handle the confirm dialog.
+ * @return boolean {@code true} if the request is handled or ignored.
+ * {@code false} if WebView needs to show the default dialog.
*/
public boolean onJsConfirm(WebView view, String url, String message,
JsResult result) {
@@ -237,18 +254,33 @@
}
/**
- * Tell the client to display a prompt dialog to the user. If the client
- * returns {@code true}, WebView will assume that the client will handle the
- * prompt dialog and call the appropriate JsPromptResult method. If the
- * client returns false, a default value of {@code false} will be returned to to
- * javascript. The default behavior is to return {@code false}.
+ * Notify the host application that the web page wants to display a
+ * JavaScript {@code prompt()} dialog.
+ * <p>The default behavior if this method returns {@code false} or is not
+ * overridden is to show a dialog containing the message and suspend
+ * JavaScript execution until the dialog is dismissed. Once the dialog is
+ * dismissed, JavaScript {@code prompt()} will return the string that the
+ * user typed in, or null if the user presses the 'cancel' button.
+ * <p>To show a custom dialog, the app should return {@code true} from this
+ * method, in which case the default dialog will not be shown and JavaScript
+ * execution will be suspended. The app should call
+ * {@code JsPromptResult.confirm(result)} when the custom dialog is
+ * dismissed.
+ * <p>To suppress the dialog and allow JavaScript execution to continue,
+ * call {@code JsPromptResult.confirm(result)} immediately and then
+ * return {@code true}.
+ * <p>Note that if the {@link WebChromeClient} is {@code null}, the default
+ * dialog will be suppressed and {@code null} will be returned to the
+ * JavaScript code immediately.
+ *
* @param view The WebView that initiated the callback.
* @param url The url of the page requesting the dialog.
* @param message Message to be displayed in the window.
* @param defaultValue The default value displayed in the prompt dialog.
* @param result A JsPromptResult used to send the user's reponse to
* javascript.
- * @return boolean Whether the client will handle the prompt dialog.
+ * @return boolean {@code true} if the request is handled or ignored.
+ * {@code false} if WebView needs to show the default dialog.
*/
public boolean onJsPrompt(WebView view, String url, String message,
String defaultValue, JsPromptResult result) {
diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
index a47ad73..c8f5be4 100644
--- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
@@ -62,6 +62,7 @@
private final Context mContext;
private int mCurrentPage;
private OnProfileSelectedListener mOnProfileSelectedListener;
+ private OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
private Set<Integer> mLoadedPages;
private final UserHandle mPersonalProfileUserHandle;
private final UserHandle mWorkProfileUserHandle;
@@ -124,6 +125,10 @@
mOnProfileSelectedListener = listener;
}
+ void setOnSwitchOnWorkSelectedListener(OnSwitchOnWorkSelectedListener listener) {
+ mOnSwitchOnWorkSelectedListener = listener;
+ }
+
Context getContext() {
return mContext;
}
@@ -397,6 +402,9 @@
ProfileDescriptor descriptor = getItem(
userHandleToPageIndex(listAdapter.getUserHandle()));
showSpinner(descriptor.getEmptyStateView());
+ if (mOnSwitchOnWorkSelectedListener != null) {
+ mOnSwitchOnWorkSelectedListener.onSwitchOnWorkSelected();
+ }
mInjector.requestQuietModeEnabled(false, mWorkProfileUserHandle);
});
return true;
@@ -576,6 +584,16 @@
}
/**
+ * Listener for when the user switches on the work profile from the work tab.
+ */
+ interface OnSwitchOnWorkSelectedListener {
+ /**
+ * Callback for when the user switches on the work profile from the work tab.
+ */
+ void onSwitchOnWorkSelected();
+ }
+
+ /**
* Describes an injector to be used for cross profile functionality. Overridable for testing.
*/
@VisibleForTesting
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 4b5735c..182c7f2 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1662,6 +1662,13 @@
resetButtonBar();
resetCheckedItem();
});
+ mMultiProfilePagerAdapter.setOnSwitchOnWorkSelectedListener(
+ () -> {
+ final View workTab = tabHost.getTabWidget().getChildAt(1);
+ workTab.setFocusable(true);
+ workTab.setFocusableInTouchMode(true);
+ workTab.requestFocus();
+ });
findViewById(R.id.resolver_tab_divider).setVisibility(View.VISIBLE);
}
diff --git a/core/jni/android_os_storage_StorageManager.cpp b/core/jni/android_os_storage_StorageManager.cpp
index fd3e66b..3116cc8 100644
--- a/core/jni/android_os_storage_StorageManager.cpp
+++ b/core/jni/android_os_storage_StorageManager.cpp
@@ -17,33 +17,23 @@
#define LOG_TAG "StorageManager"
#include <android-base/file.h>
#include <android-base/logging.h>
+#include <android-base/properties.h>
#include <android-base/unique_fd.h>
#include <fcntl.h>
#include <linux/fs.h>
#include <nativehelper/JNIHelp.h>
#include "core_jni_helpers.h"
+#include "filesystem_utils.h"
namespace android {
-static const char* kProcFilesystems = "/proc/filesystems";
-
-// Checks whether the passed in filesystem is listed in /proc/filesystems
-static bool IsFilesystemSupported(const std::string& fsType) {
- std::string supported;
- if (!android::base::ReadFileToString(kProcFilesystems, &supported)) {
- PLOG(ERROR) << "Failed to read supported filesystems";
- return false;
- }
- return supported.find(fsType + "\n") != std::string::npos;
-}
-
jboolean android_os_storage_StorageManager_setQuotaProjectId(JNIEnv* env, jobject self,
jstring path, jlong projectId) {
struct fsxattr fsx;
ScopedUtfChars utf_chars_path(env, path);
- static bool sdcardFsSupported = IsFilesystemSupported("sdcardfs");
+ static bool sdcardFsSupported = IsSdcardfsUsed();
if (sdcardFsSupported) {
// sdcardfs doesn't support project ID quota tracking and takes care of quota
// in a different way.
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index c5bc083..fc2005a 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -88,12 +88,13 @@
#include <utils/String8.h>
#include <utils/Trace.h>
-#include "core_jni_helpers.h"
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedLocalRef.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#include <nativehelper/ScopedUtfChars.h>
+#include "core_jni_helpers.h"
#include "fd_utils.h"
+#include "filesystem_utils.h"
#include "nativebridge/native_bridge.h"
@@ -614,15 +615,6 @@
}
}
-static bool IsFilesystemSupported(const std::string& fsType) {
- std::string supported;
- if (!ReadFileToString("/proc/filesystems", &supported)) {
- ALOGE("Failed to read supported filesystems");
- return false;
- }
- return supported.find(fsType + "\n") != std::string::npos;
-}
-
static void PreApplicationInit() {
// The child process sets this to indicate it's not the zygote.
android_mallopt(M_SET_ZYGOTE_CHILD, nullptr, 0);
@@ -1554,15 +1546,14 @@
static void BindMountStorageToLowerFs(const userid_t user_id, const uid_t uid,
const char* dir_name, const char* package, fail_fn_t fail_fn) {
-
- bool hasSdcardFs = IsFilesystemSupported("sdcardfs");
- std::string source;
- if (hasSdcardFs) {
- source = StringPrintf("/mnt/runtime/default/emulated/%d/%s/%s", user_id, dir_name, package);
- } else {
- source = StringPrintf("/mnt/pass_through/%d/emulated/%d/%s/%s",
- user_id, user_id, dir_name, package);
- }
+ bool hasSdcardFs = IsSdcardfsUsed();
+ std::string source;
+ if (hasSdcardFs) {
+ source = StringPrintf("/mnt/runtime/default/emulated/%d/%s/%s", user_id, dir_name, package);
+ } else {
+ source = StringPrintf("/mnt/pass_through/%d/emulated/%d/%s/%s", user_id, user_id, dir_name,
+ package);
+ }
std::string target = StringPrintf("/storage/emulated/%d/%s/%s", user_id, dir_name, package);
// As the parent is mounted as tmpfs, we need to create the target dir here.
diff --git a/core/jni/filesystem_utils.h b/core/jni/filesystem_utils.h
new file mode 100644
index 0000000..c4728a0
--- /dev/null
+++ b/core/jni/filesystem_utils.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#ifndef FRAMEWORKS_BASE_CORE_JNI_MISC_UTILS_H_
+#define FRAMEWORKS_BASE_CORE_JNI_MISC_UTILS_H_
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <fcntl.h>
+#include <linux/fs.h>
+
+namespace {
+static constexpr const char* kExternalStorageSdcardfs = "external_storage.sdcardfs.enabled";
+
+static bool IsFilesystemSupported(const std::string& fsType) {
+ std::string supported;
+ if (!android::base::ReadFileToString("/proc/filesystems", &supported)) {
+ ALOGE("Failed to read supported filesystems");
+ return false;
+ }
+ return supported.find(fsType + "\n") != std::string::npos;
+}
+
+static inline bool IsSdcardfsUsed() {
+ return IsFilesystemSupported("sdcardfs") &&
+ android::base::GetBoolProperty(kExternalStorageSdcardfs, true);
+}
+} // namespace
+#endif // FRAMEWORKS_BASE_CORE_JNI_MISC_UTILS_H_
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index bfa5d70..997829e 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2678,15 +2678,4 @@
// CATEGORY: SETTINGS
// OS: R
DEVICE_CONTROLS_SETTINGS = 1844;
-
- // ACTION: Settings > Wi-Fi > Tap on Openroaming Wi-Fi
- // CATEGORY: SETTINGS
- // OS: R
- OPENROAMING_TAP = 1845;
-
- // When device already using any Wi-Fi service, to track if user still want to use Openroaming
- // ACTION: Settings > Wi-Fi > Tap on Openroaming Wi-Fi
- // CATEGORY: SETTINGS
- // OS: R
- OPENROAMING_TAP_ON_WIFI_CONNECTION = 1846;
}
diff --git a/core/proto/android/view/imefocuscontroller.proto b/core/proto/android/view/imefocuscontroller.proto
new file mode 100644
index 0000000..ff9dee6
--- /dev/null
+++ b/core/proto/android/view/imefocuscontroller.proto
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+syntax = "proto2";
+
+package android.view;
+
+option java_multiple_files = true;
+
+/**
+ * Represents a {@link android.view.ImeFocusController} object.
+ */
+message ImeFocusControllerProto {
+ optional bool has_ime_focus = 1;
+ optional string served_view = 2;
+ optional string next_served_view = 3;
+}
\ No newline at end of file
diff --git a/core/proto/android/view/imeinsetssourceconsumer.proto b/core/proto/android/view/imeinsetssourceconsumer.proto
new file mode 100644
index 0000000..6809163
--- /dev/null
+++ b/core/proto/android/view/imeinsetssourceconsumer.proto
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+syntax = "proto2";
+
+import "frameworks/base/core/proto/android/view/inputmethod/editorinfo.proto";
+
+package android.view;
+
+option java_multiple_files = true;
+
+/**
+ * Represents a {@link android.view.ImeInsetsSourceConsumer} object.
+ */
+message ImeInsetsSourceConsumerProto {
+ optional .android.view.inputmethod.EditorInfoProto focused_editor = 1;
+ optional bool is_requested_visible_awaiting_control = 2;
+}
\ No newline at end of file
diff --git a/core/proto/android/view/inputmethod/editorinfo.proto b/core/proto/android/view/inputmethod/editorinfo.proto
new file mode 100644
index 0000000..f93096f
--- /dev/null
+++ b/core/proto/android/view/inputmethod/editorinfo.proto
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+syntax = "proto2";
+
+package android.view.inputmethod;
+
+option java_multiple_files = true;
+
+/**
+ * Represents a {@link android.view.inputmethod.EditorInfo} object.
+ */
+message EditorInfoProto {
+ optional int32 input_type = 1;
+ optional int32 ime_options = 2;
+ optional string private_ime_options = 3;
+ optional string package_name = 4;
+ optional int32 field_id = 5;
+ optional int32 target_input_method_user_id = 6;
+}
\ No newline at end of file
diff --git a/core/proto/android/view/inputmethod/inputmethodeditortrace.proto b/core/proto/android/view/inputmethod/inputmethodeditortrace.proto
new file mode 100644
index 0000000..7322139
--- /dev/null
+++ b/core/proto/android/view/inputmethod/inputmethodeditortrace.proto
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+syntax = "proto2";
+option java_outer_classname = "InputMethodEditorTraceProto";
+
+package android.view.inputmethod;
+
+import "frameworks/base/core/proto/android/view/inputmethod/inputmethodmanager.proto";
+import "frameworks/base/core/proto/android/view/viewrootimpl.proto";
+import "frameworks/base/core/proto/android/view/insetscontroller.proto";
+import "frameworks/base/core/proto/android/view/insetssourceconsumer.proto";
+import "frameworks/base/core/proto/android/view/imeinsetssourceconsumer.proto";
+import "frameworks/base/core/proto/android/view/inputmethod/editorinfo.proto";
+import "frameworks/base/core/proto/android/view/imefocuscontroller.proto";
+
+/**
+ * Represents a file full of input method editor trace entries.
+ * Encoded, it should start with 0x9 0x49 0x4d 0x45 0x54 0x52 0x41 0x43 0x45 (.IMETRACE), such
+ * that they can be easily identified.
+ */
+message InputMethodEditorTraceFileProto {
+
+ /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L
+ (this is needed because enums have to be 32 bits and there's no nice way to put 64bit
+ constants into .proto files.) */
+ enum MagicNumber {
+ INVALID = 0;
+ MAGIC_NUMBER_L = 0x54454d49; /* IMET (little-endian ASCII) */
+ MAGIC_NUMBER_H = 0x45434152; /* RACE (little-endian ASCII) */
+ }
+
+ /* Must be the first field to allow winscope to auto-detect the dump type. Set to value
+ in MagicNumber */
+ optional fixed64 magic_number = 1;
+ repeated InputMethodEditorProto entry = 2;
+}
+
+/* one input method editor dump entry. */
+message InputMethodEditorProto {
+
+ /* required: elapsed realtime in nanos since boot of when this entry was logged */
+ optional fixed64 elapsed_realtime_nanos = 1;
+ optional ClientSideProto client_side_dump = 2;
+
+ /* groups together the dump from ime related client side classes */
+ message ClientSideProto {
+ optional InputMethodManagerProto input_method_manager = 1;
+ optional ViewRootImplProto view_root_impl = 2;
+ optional InsetsControllerProto insets_controller = 3;
+ optional InsetsSourceConsumerProto insets_source_consumer = 4;
+ optional ImeInsetsSourceConsumerProto ime_insets_source_consumer = 5;
+ optional EditorInfoProto editor_info = 6;
+ optional ImeFocusControllerProto ime_focus_controller = 7;
+ }
+}
\ No newline at end of file
diff --git a/core/proto/android/view/inputmethod/inputmethodmanager.proto b/core/proto/android/view/inputmethod/inputmethodmanager.proto
new file mode 100644
index 0000000..9fed0ef
--- /dev/null
+++ b/core/proto/android/view/inputmethod/inputmethodmanager.proto
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+syntax = "proto2";
+
+package android.view.inputmethod;
+
+option java_multiple_files = true;
+
+/**
+ * Represents a {@link android.view.inputmethod.InputMethodManager} object.
+ */
+message InputMethodManagerProto {
+ optional string cur_id = 1;
+ optional bool fullscreen_mode = 2;
+ optional int32 display_id = 3;
+ optional bool active = 4;
+ optional bool served_connecting = 5;
+}
\ No newline at end of file
diff --git a/core/proto/android/view/insetsanimationcontrolimpl.proto b/core/proto/android/view/insetsanimationcontrolimpl.proto
new file mode 100644
index 0000000..6eec37b
--- /dev/null
+++ b/core/proto/android/view/insetsanimationcontrolimpl.proto
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+syntax = "proto2";
+
+package android.view;
+
+option java_multiple_files = true;
+
+/**
+ * Represents a {@link android.view.InsetsAnimationControlImpl} object.
+ */
+message InsetsAnimationControlImplProto {
+ optional bool is_cancelled = 1;
+ optional bool is_finished = 2;
+ optional string tmp_matrix = 3;
+ optional string pending_insets = 4;
+ optional float pending_fraction = 5;
+ optional bool shown_on_finish = 6;
+ optional float current_alpha = 7;
+ optional float pending_alpha = 8;
+}
\ No newline at end of file
diff --git a/core/proto/android/view/insetscontroller.proto b/core/proto/android/view/insetscontroller.proto
new file mode 100644
index 0000000..a8bf431
--- /dev/null
+++ b/core/proto/android/view/insetscontroller.proto
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+syntax = "proto2";
+
+import "frameworks/base/core/proto/android/view/insetsstate.proto";
+import "frameworks/base/core/proto/android/view/insetsanimationcontrolimpl.proto";
+
+package android.view;
+
+option java_multiple_files = true;
+
+/**
+ * Represents a {@link android.view.InsetsController} object.
+ */
+message InsetsControllerProto {
+ optional InsetsStateProto state = 1;
+ repeated InsetsAnimationControlImplProto control = 2;
+}
\ No newline at end of file
diff --git a/core/proto/android/view/insetssource.proto b/core/proto/android/view/insetssource.proto
new file mode 100644
index 0000000..41b9f43
--- /dev/null
+++ b/core/proto/android/view/insetssource.proto
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+syntax = "proto2";
+
+import "frameworks/base/core/proto/android/graphics/rect.proto";
+
+package android.view;
+
+option java_multiple_files = true;
+
+/**
+ * Represents a {@link android.view.InsetsSource} object.
+ */
+message InsetsSourceProto {
+ optional string type = 1;
+ optional .android.graphics.RectProto frame = 2;
+ optional .android.graphics.RectProto visible_frame = 3;
+ optional bool visible = 4;
+}
\ No newline at end of file
diff --git a/core/proto/android/view/insetssourceconsumer.proto b/core/proto/android/view/insetssourceconsumer.proto
new file mode 100644
index 0000000..487e06c
--- /dev/null
+++ b/core/proto/android/view/insetssourceconsumer.proto
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+syntax = "proto2";
+
+import "frameworks/base/core/proto/android/view/insetssourcecontrol.proto";
+import "frameworks/base/core/proto/android/graphics/rect.proto";
+
+package android.view;
+
+option java_multiple_files = true;
+
+/**
+ * Represents a {@link android.view.InsetsSourceConsumer} object.
+ */
+message InsetsSourceConsumerProto {
+ optional string internal_insets_type = 1;
+ optional bool has_window_focus = 2;
+ optional bool is_requested_visible = 3;
+ optional InsetsSourceControlProto source_control = 4;
+ optional .android.graphics.RectProto pending_frame = 5;
+ optional .android.graphics.RectProto pending_visible_frame = 6;
+}
\ No newline at end of file
diff --git a/core/proto/android/view/insetssourcecontrol.proto b/core/proto/android/view/insetssourcecontrol.proto
new file mode 100644
index 0000000..3ac3cbf
--- /dev/null
+++ b/core/proto/android/view/insetssourcecontrol.proto
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+syntax = "proto2";
+
+import "frameworks/base/core/proto/android/graphics/point.proto";
+import "frameworks/base/core/proto/android/view/surfacecontrol.proto";
+
+package android.view;
+
+option java_multiple_files = true;
+
+/**
+ * Represents a {@link android.view.InsetsSourceControl} object.
+ */
+message InsetsSourceControlProto {
+ optional string type = 1;
+ optional .android.graphics.PointProto position = 2;
+ optional SurfaceControlProto leash = 3;
+}
\ No newline at end of file
diff --git a/core/proto/android/view/insetsstate.proto b/core/proto/android/view/insetsstate.proto
new file mode 100644
index 0000000..9e9933d
--- /dev/null
+++ b/core/proto/android/view/insetsstate.proto
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+syntax = "proto2";
+
+import "frameworks/base/core/proto/android/view/insetssource.proto";
+import "frameworks/base/core/proto/android/graphics/rect.proto";
+
+package android.view;
+
+option java_multiple_files = true;
+
+/**
+ * Represents a {@link android.view.InsetsState} object.
+ */
+message InsetsStateProto {
+ repeated InsetsSourceProto sources = 1;
+ optional .android.graphics.RectProto display_frame = 2;
+}
\ No newline at end of file
diff --git a/core/proto/android/view/viewrootimpl.proto b/core/proto/android/view/viewrootimpl.proto
new file mode 100644
index 0000000..0abe5e06
--- /dev/null
+++ b/core/proto/android/view/viewrootimpl.proto
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+syntax = "proto2";
+
+import "frameworks/base/core/proto/android/graphics/rect.proto";
+import "frameworks/base/core/proto/android/view/displaycutout.proto";
+import "frameworks/base/core/proto/android/view/windowlayoutparams.proto";
+
+package android.view;
+
+option java_multiple_files = true;
+
+/**
+ * Represents a {@link android.view.ViewRootImpl} object.
+ */
+message ViewRootImplProto {
+ optional string view = 1;
+ optional int32 display_id = 2;
+ optional bool app_visible = 3;
+ optional int32 width = 4;
+ optional int32 height = 5;
+ optional bool is_animating = 6;
+ optional .android.graphics.RectProto visible_rect = 7;
+ optional bool is_drawing = 8;
+ optional bool added = 9;
+ optional .android.graphics.RectProto win_frame = 10;
+ optional DisplayCutoutProto pending_display_cutout = 11;
+ optional string last_window_insets = 12;
+ optional string soft_input_mode = 13;
+ optional int32 scroll_y = 14;
+ optional int32 cur_scroll_y = 15;
+ optional bool removed = 16;
+ optional .android.view.WindowLayoutParamsProto window_attributes = 17;
+}
\ No newline at end of file
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3cd0f03..d4cc636 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5474,15 +5474,17 @@
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
- <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader">
+ <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
+ android:exported="false">
<intent-filter>
- <action android:name="android.intent.action.LOAD_DATA" />
+ <action android:name="android.intent.action.LOAD_DATA"/>
</intent-filter>
</service>
<provider
android:name="com.android.server.textclassifier.IconsContentProvider"
android:authorities="com.android.textclassifier.icons"
+ android:singleUser="true"
android:enabled="true"
android:exported="true">
</provider>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index fe7b0ae..ac808df 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -42,6 +42,7 @@
<item><xliff:g id="id">@string/status_bar_phone_evdo_signal</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_phone_signal</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_secure</xliff:g></item>
+ <item><xliff:g id="id">@string/status_bar_media</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_managed_profile</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_cast</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_screen_record</xliff:g></item>
@@ -96,6 +97,7 @@
<string translatable="false" name="status_bar_airplane">airplane</string>
<string translatable="false" name="status_bar_sensors_off">sensors_off</string>
<string translatable="false" name="status_bar_screen_record">screen_record</string>
+ <string translatable="false" name="status_bar_media">media</string>
<!-- Flag indicating whether the surface flinger has limited
alpha compositing functionality in hardware. If set, the window
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7744e9e..758a4f7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2916,6 +2916,7 @@
<java-symbol type="string" name="status_bar_camera" />
<java-symbol type="string" name="status_bar_sensors_off" />
<java-symbol type="string" name="status_bar_screen_record" />
+ <java-symbol type="string" name="status_bar_media" />
<!-- Locale picker -->
<java-symbol type="id" name="locale_search_menu" />
diff --git a/core/tests/utiltests/src/android/util/AtomicFileTest.java b/core/tests/utiltests/src/android/util/AtomicFileTest.java
index 547d4d8..a7d3b22 100644
--- a/core/tests/utiltests/src/android/util/AtomicFileTest.java
+++ b/core/tests/utiltests/src/android/util/AtomicFileTest.java
@@ -17,6 +17,7 @@
package android.util;
import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertTrue;
import android.app.Instrumentation;
import android.content.Context;
@@ -45,11 +46,14 @@
private static final String BASE_NAME = "base";
private static final String NEW_NAME = BASE_NAME + ".new";
private static final String LEGACY_BACKUP_NAME = BASE_NAME + ".bak";
+ // The string isn't actually used, but we just need a different identifier.
+ private static final String BASE_NAME_DIRECTORY = BASE_NAME + ".dir";
private enum WriteAction {
FINISH,
FAIL,
- ABORT
+ ABORT,
+ READ_FINISH
}
private static final byte[] BASE_BYTES = "base".getBytes(StandardCharsets.UTF_8);
@@ -82,6 +86,7 @@
@Parameterized.Parameters(name = "{0}")
public static Object[][] data() {
return new Object[][] {
+ // Standard tests.
{ "none + none = none", null, null, null },
{ "none + finish = new", null, WriteAction.FINISH, NEW_BYTES },
{ "none + fail = none", null, WriteAction.FAIL, null },
@@ -140,6 +145,50 @@
{ "base & new & bak + abort = bak",
new String[] { BASE_NAME, NEW_NAME, LEGACY_BACKUP_NAME }, WriteAction.ABORT,
LEGACY_BACKUP_BYTES },
+ // Compatibility when there is a directory in the place of base file, by replacing
+ // no base with base.dir.
+ { "base.dir + none = none", new String[] { BASE_NAME_DIRECTORY }, null, null },
+ { "base.dir + finish = new", new String[] { BASE_NAME_DIRECTORY },
+ WriteAction.FINISH, NEW_BYTES },
+ { "base.dir + fail = none", new String[] { BASE_NAME_DIRECTORY }, WriteAction.FAIL,
+ null },
+ { "base.dir + abort = none", new String[] { BASE_NAME_DIRECTORY },
+ WriteAction.ABORT, null },
+ { "base.dir & new + none = none", new String[] { BASE_NAME_DIRECTORY, NEW_NAME },
+ null, null },
+ { "base.dir & new + finish = new", new String[] { BASE_NAME_DIRECTORY, NEW_NAME },
+ WriteAction.FINISH, NEW_BYTES },
+ { "base.dir & new + fail = none", new String[] { BASE_NAME_DIRECTORY, NEW_NAME },
+ WriteAction.FAIL, null },
+ { "base.dir & new + abort = none", new String[] { BASE_NAME_DIRECTORY, NEW_NAME },
+ WriteAction.ABORT, null },
+ { "base.dir & bak + none = bak",
+ new String[] { BASE_NAME_DIRECTORY, LEGACY_BACKUP_NAME }, null,
+ LEGACY_BACKUP_BYTES },
+ { "base.dir & bak + finish = new",
+ new String[] { BASE_NAME_DIRECTORY, LEGACY_BACKUP_NAME },
+ WriteAction.FINISH, NEW_BYTES },
+ { "base.dir & bak + fail = bak",
+ new String[] { BASE_NAME_DIRECTORY, LEGACY_BACKUP_NAME }, WriteAction.FAIL,
+ LEGACY_BACKUP_BYTES },
+ { "base.dir & bak + abort = bak",
+ new String[] { BASE_NAME_DIRECTORY, LEGACY_BACKUP_NAME }, WriteAction.ABORT,
+ LEGACY_BACKUP_BYTES },
+ { "base.dir & new & bak + none = bak",
+ new String[] { BASE_NAME_DIRECTORY, NEW_NAME, LEGACY_BACKUP_NAME }, null,
+ LEGACY_BACKUP_BYTES },
+ { "base.dir & new & bak + finish = new",
+ new String[] { BASE_NAME_DIRECTORY, NEW_NAME, LEGACY_BACKUP_NAME },
+ WriteAction.FINISH, NEW_BYTES },
+ { "base.dir & new & bak + fail = bak",
+ new String[] { BASE_NAME_DIRECTORY, NEW_NAME, LEGACY_BACKUP_NAME },
+ WriteAction.FAIL, LEGACY_BACKUP_BYTES },
+ { "base.dir & new & bak + abort = bak",
+ new String[] { BASE_NAME_DIRECTORY, NEW_NAME, LEGACY_BACKUP_NAME },
+ WriteAction.ABORT, LEGACY_BACKUP_BYTES },
+ // Compatibility when openRead() is called between startWrite() and finishWrite() -
+ // the write should still succeed if it's the first write.
+ { "none + read & finish = new", null, WriteAction.READ_FINISH, NEW_BYTES },
};
}
@@ -165,6 +214,9 @@
case LEGACY_BACKUP_NAME:
writeBytes(mLegacyBackupFile, LEGACY_BACKUP_BYTES);
break;
+ case BASE_NAME_DIRECTORY:
+ assertTrue(mBaseFile.mkdir());
+ break;
default:
throw new AssertionError(fileName);
}
@@ -185,6 +237,11 @@
case ABORT:
// Neither finishing nor failing is called upon abort.
break;
+ case READ_FINISH:
+ // We are only using this action when there is no base file.
+ assertThrows(FileNotFoundException.class, atomicFile::openRead);
+ atomicFile.finishWrite(outputStream);
+ break;
default:
throw new AssertionError(mWriteAction);
}
@@ -196,7 +253,7 @@
assertArrayEquals(mExpectedBytes, readAllBytes(inputStream));
}
} else {
- assertThrows(FileNotFoundException.class, () -> atomicFile.openRead());
+ assertThrows(FileNotFoundException.class, atomicFile::openRead);
}
}
diff --git a/graphics/java/android/graphics/PorterDuff.java b/graphics/java/android/graphics/PorterDuff.java
index 1275cb9..eb940e2 100644
--- a/graphics/java/android/graphics/PorterDuff.java
+++ b/graphics/java/android/graphics/PorterDuff.java
@@ -30,8 +30,6 @@
/**
* {@usesMathJax}
*
- * <h3>Porter-Duff</h3>
- *
* <p>The name of the parent class is an homage to the work of Thomas Porter and
* Tom Duff, presented in their seminal 1984 paper titled "Compositing Digital Images".
* In this paper, the authors describe 12 compositing operators that govern how to
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index acd90a7..6df62c0 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -369,6 +369,13 @@
* {@link Activity#RESULT_OK} will be returned if a credential was
* successfully installed, otherwise {@link
* Activity#RESULT_CANCELED} will be returned.
+ *
+ * <p>Starting from {@link android.os.Build.VERSION_CODES#R}, the intent returned by this
+ * method cannot be used for installing CA certificates. Since CA certificates can only be
+ * installed via Settings, the app should provide the user with a file containing the
+ * CA certificate. One way to do this would be to use the {@link android.provider.MediaStore}
+ * API to write the certificate to the {@link android.provider.MediaStore.Downloads}
+ * collection.
*/
@NonNull
public static Intent createInstallIntent() {
diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java
index 357c333..b44d7bb 100644
--- a/media/java/android/media/AudioManagerInternal.java
+++ b/media/java/android/media/AudioManagerInternal.java
@@ -29,13 +29,13 @@
public abstract class AudioManagerInternal {
public abstract void adjustSuggestedStreamVolumeForUid(int streamType, int direction,
- int flags, String callingPackage, int uid);
+ int flags, String callingPackage, int uid, int pid);
public abstract void adjustStreamVolumeForUid(int streamType, int direction, int flags,
- String callingPackage, int uid);
+ String callingPackage, int uid, int pid);
public abstract void setStreamVolumeForUid(int streamType, int direction, int flags,
- String callingPackage, int uid);
+ String callingPackage, int uid, int pid);
public abstract void setRingerModeDelegate(RingerModeDelegate delegate);
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 1729027..66787c2 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Gedeaktiveer"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Geaktiveer"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Jou toestel moet herselflaai om hierdie verandering toe te pas. Herselflaai nou of kanselleer."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Werk-<xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index fad7a82..ead0ebf 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"ተሰናክሏል"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ነቅቷል"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"የእርስዎን መሣሪያ ይህ ለው ለማመልከት እንደገና መነሣት አለበት። አሁን እንደገና ያስነሡ ወይም ይተዉት።"</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"የስራ <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index b3cc832..c1f4fd9 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -195,7 +195,7 @@
</string-array>
<string name="choose_profile" msgid="343803890897657450">"اختيار ملف شخصي"</string>
<string name="category_personal" msgid="6236798763159385225">"شخصي"</string>
- <string name="category_work" msgid="4014193632325996115">"العمل"</string>
+ <string name="category_work" msgid="4014193632325996115">"للعمل"</string>
<string name="development_settings_title" msgid="140296922921597393">"خيارات مطور البرامج"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"تفعيل خيارات المطورين"</string>
<string name="development_settings_summary" msgid="8718917813868735095">"تعيين خيارات تطوير التطبيق"</string>
@@ -279,7 +279,7 @@
<string name="private_dns_mode_off" msgid="7065962499349997041">"غير مفعّل"</string>
<string name="private_dns_mode_opportunistic" msgid="1947864819060442354">"تلقائي"</string>
<string name="private_dns_mode_provider" msgid="3619040641762557028">"اسم مضيف مزوّد \"نظام أسماء النطاقات الخاص\""</string>
- <string name="private_dns_mode_provider_hostname_hint" msgid="6564868953748514595">"يُرجى إدخال اسم مضيف \"مزوّد نظام أسماء النطاقات\""</string>
+ <string name="private_dns_mode_provider_hostname_hint" msgid="6564868953748514595">"يُرجى إدخال اسم مضيف DNS"</string>
<string name="private_dns_mode_provider_failure" msgid="8356259467861515108">"تعذّر الاتصال"</string>
<string name="wifi_display_certification_summary" msgid="8111151348106907513">"عرض خيارات شهادة عرض شاشة لاسلكي"</string>
<string name="wifi_verbose_logging_summary" msgid="4993823188807767892">"زيادة مستوى تسجيل Wi-Fi، وعرض لكل SSID RSSI في منتقي Wi-Fi"</string>
@@ -318,10 +318,10 @@
<string name="hdcp_checking_title" msgid="3155692785074095986">"التحقق من HDCP"</string>
<string name="hdcp_checking_dialog_title" msgid="7691060297616217781">"تعيين سلوك التحقق من HDCP"</string>
<string name="debug_debugging_category" msgid="535341063709248842">"تصحيح الأخطاء"</string>
- <string name="debug_app" msgid="8903350241392391766">"تحديد التطبيق لتصحيحه"</string>
+ <string name="debug_app" msgid="8903350241392391766">"اختيار التطبيق لتصحيحه"</string>
<string name="debug_app_not_set" msgid="1934083001283807188">"لم يتم تعيين تطبيق لتصحيحه"</string>
<string name="debug_app_set" msgid="6599535090477753651">"تطبيق التصحيح: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
- <string name="select_application" msgid="2543228890535466325">"تحديد تطبيق"</string>
+ <string name="select_application" msgid="2543228890535466325">"اختيار تطبيق"</string>
<string name="no_application" msgid="9038334538870247690">"لا شيء"</string>
<string name="wait_for_debugger" msgid="7461199843335409809">"انتظار برنامج التصحيح"</string>
<string name="wait_for_debugger_summary" msgid="6846330006113363286">"ينتظر التطبيق قيد التصحيح انضمام برنامج التصحيح قبل التنفيذ"</string>
@@ -557,6 +557,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"غير مفعّل"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"مفعّل"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"يجب إعادة تشغيل جهازك ليتم تطبيق هذا التغيير. يمكنك إعادة التشغيل الآن أو إلغاء التغيير."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> المخصّص للعمل"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index ad344c3..b5d642a 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -549,14 +549,11 @@
<string name="guest_new_guest" msgid="3482026122932643557">"অতিথি যোগ কৰক"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"অতিথি আঁতৰাওক"</string>
<string name="guest_nickname" msgid="6332276931583337261">"অতিথি"</string>
- <!-- no translation found for cached_apps_freezer_device_default (2616594131750144342) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_disabled (4816382260660472042) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_enabled (8866703500183051546) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_reboot_dialog_text (695330563489230096) -->
- <skip />
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"ডিভাইচ ডিফ’ল্ট"</string>
+ <string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"অক্ষম কৰা আছে"</string>
+ <string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"সক্ষম কৰা আছে"</string>
+ <string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"এই সলনিটো কার্যকৰী হ’বলৈ আপোনাৰ ডিভাইচটো ৰিবুট কৰিবই লাগিব। এতিয়াই ৰিবুট কৰক অথবা বাতিল কৰক।"</string>
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"কৰ্মস্থান <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index ab7ea8c..aecaf70 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -155,7 +155,7 @@
<string name="launch_defaults_some" msgid="3631650616557252926">"Bəzi susmaya görələr təyin edilib"</string>
<string name="launch_defaults_none" msgid="8049374306261262709">"Susmaya görələr təyin edilməyib."</string>
<string name="tts_settings" msgid="8130616705989351312">"Mətndən-danışığa parametrləri"</string>
- <string name="tts_settings_title" msgid="7602210956640483039">"Mətndən-nitqə çıxışı"</string>
+ <string name="tts_settings_title" msgid="7602210956640483039">"Mətndən-nitqə daxiletmə"</string>
<string name="tts_default_rate_title" msgid="3964187817364304022">"Nitq diapazonu"</string>
<string name="tts_default_rate_summary" msgid="3781937042151716987">"Mətnin səsləndirilmə sürəti"</string>
<string name="tts_default_pitch_title" msgid="6988592215554485479">"Pitç"</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Deaktiv"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktiv"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Bu dəyişikliyin tətbiq edilməsi üçün cihaz yenidən başladılmalıdır. İndi yenidən başladın və ya ləğv edin."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"İş <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index da47bf2..092bd42 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -554,6 +554,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Onemogućeno"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Omogućeno"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Morate da restartujete uređaj da bi se ova promena primenila. Restartujte ga odmah ili otkažite."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> za posao"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index 2761d47..d610973 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -195,7 +195,7 @@
</string-array>
<string name="choose_profile" msgid="343803890897657450">"Выбраць профіль"</string>
<string name="category_personal" msgid="6236798763159385225">"Асабісты"</string>
- <string name="category_work" msgid="4014193632325996115">"Рабочы"</string>
+ <string name="category_work" msgid="4014193632325996115">"Працоўны"</string>
<string name="development_settings_title" msgid="140296922921597393">"Параметры распрацоўшчыка"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Уключыць параметры распрацоўшчыка"</string>
<string name="development_settings_summary" msgid="8718917813868735095">"Налада параметраў для распрацоўкі прыкладанняў"</string>
@@ -555,6 +555,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Выключана"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Уключана"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Перазагрузіце прыладу, каб прымяніць гэта змяненне. Перазагрузіце ці скасуйце."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> (праца)"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 8150710..5c3fe75 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -419,7 +419,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалия (червено – зелено)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалия (синьо – жълто)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Корекция на цветове"</string>
- <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1284746051652993443">"Корекцията на цветове ви позволява да коригирате това, как цветовете се показват на устройството ви"</string>
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1284746051652993443">"Функцията „Корекция на цветове“ ви позволява да коригирате това, как цветовете се показват на устройството ви"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Заменено от „<xliff:g id="TITLE">%1$s</xliff:g>“"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"Още около <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -554,4 +554,6 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Активирано"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"За да бъде приложена тази промяна, устройството ви трябва да бъде рестартирано. Рестартирайте сега или анулирайте."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> за работа"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 7bae6ee..27d3707 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -195,7 +195,7 @@
</string-array>
<string name="choose_profile" msgid="343803890897657450">"প্রোফাইল বেছে নিন"</string>
<string name="category_personal" msgid="6236798763159385225">"ব্যক্তিগত"</string>
- <string name="category_work" msgid="4014193632325996115">"কর্মক্ষেত্র"</string>
+ <string name="category_work" msgid="4014193632325996115">"অফিস"</string>
<string name="development_settings_title" msgid="140296922921597393">"ডেভেলপার বিকল্প"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"ডেভেলপার বিকল্প সক্ষম করুন"</string>
<string name="development_settings_summary" msgid="8718917813868735095">"অ্যাপ্লিকেশান উন্নয়নের জন্য বিকল্পগুলি সেট করুন"</string>
@@ -485,7 +485,7 @@
<string name="ims_reg_title" msgid="8197592958123671062">"IMS রেজিস্ট্রেশনের স্থিতি"</string>
<string name="ims_reg_status_registered" msgid="884916398194885457">"রেজিস্টার করা"</string>
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"রেজিস্টার করা নয়"</string>
- <string name="status_unavailable" msgid="5279036186589861608">"অনুপলব্ধ"</string>
+ <string name="status_unavailable" msgid="5279036186589861608">"অনুপলভ্য"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC র্যান্ডমাইজ করা হয়েছে"</string>
<plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
<item quantity="one">%1$dটি ডিভাইস কানেক্ট</item>
@@ -549,14 +549,11 @@
<string name="guest_new_guest" msgid="3482026122932643557">"অতিথি যোগ করুন"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"অতিথি সরান"</string>
<string name="guest_nickname" msgid="6332276931583337261">"অতিথি"</string>
- <!-- no translation found for cached_apps_freezer_device_default (2616594131750144342) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_disabled (4816382260660472042) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_enabled (8866703500183051546) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_reboot_dialog_text (695330563489230096) -->
- <skip />
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"ডিভাইসের ডিফল্ট"</string>
+ <string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"বন্ধ করা আছে"</string>
+ <string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"চালু করা আছে"</string>
+ <string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"এই পরিবর্তনটি প্রয়োগ করার জন্য আপনার ডিভাইসটি অবশ্যই রিবুট করতে হবে। এখন রিবুট করুন বা বাতিল করুন।"</string>
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"অফিস <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-bs/arrays.xml b/packages/SettingsLib/res/values-bs/arrays.xml
index 6489cef..6595c22 100644
--- a/packages/SettingsLib/res/values-bs/arrays.xml
+++ b/packages/SettingsLib/res/values-bs/arrays.xml
@@ -40,7 +40,7 @@
<item msgid="8339720953594087771">"Povezivanje na mrežu <xliff:g id="NETWORK_NAME">%1$s</xliff:g>..."</item>
<item msgid="3028983857109369308">"Autentifikacija s mrežom <xliff:g id="NETWORK_NAME">%1$s</xliff:g>…"</item>
<item msgid="4287401332778341890">"Dobivanje IP adrese iz mreže <xliff:g id="NETWORK_NAME">%1$s</xliff:g>…"</item>
- <item msgid="1043944043827424501">"Povezano na mrežu <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</item>
+ <item msgid="1043944043827424501">"Povezano s mrežom <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</item>
<item msgid="7445993821842009653">"Suspendirano"</item>
<item msgid="1175040558087735707">"Prekidanje veze s mrežom <xliff:g id="NETWORK_NAME">%1$s</xliff:g>…"</item>
<item msgid="699832486578171722">"Isključen"</item>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index fe7f8c1..a9d9e70 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -452,7 +452,7 @@
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Brzo punjenje"</string>
<string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Sporo punjenje"</string>
<string name="battery_info_status_discharging" msgid="6962689305413556485">"Ne puni se"</string>
- <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Priključen, trenutno se ne može puniti"</string>
+ <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Priključeno, trenutno se ne može puniti"</string>
<string name="battery_info_status_full" msgid="4443168946046847468">"Puna"</string>
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Pod kontrolom administratora"</string>
<string name="disabled" msgid="8017887509554714950">"Onemogućeno"</string>
@@ -554,5 +554,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Onemogućeno"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Omogućeno"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Morate ponovo pokrenuti uređaj da se ova promjena primijeni. Ponovo pokrenite odmah ili otkažite."</string>
- <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> za posao"</string>
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Poslovna aplikacija <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 7dad950..5ed0f1b 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -222,7 +222,7 @@
<string name="adb_device_fingerprint_title_format" msgid="291504822917843701">"Empremta digital del dispositiu: <xliff:g id="FINGERPRINT_PARAM">%1$s</xliff:g>"</string>
<string name="adb_wireless_connection_failed_title" msgid="664211177427438438">"No s\'ha pogut connectar"</string>
<string name="adb_wireless_connection_failed_message" msgid="9213896700171602073">"Assegura\'t que <xliff:g id="DEVICE_NAME">%1$s</xliff:g> estigui connectat a la xarxa correcta"</string>
- <string name="adb_pairing_device_dialog_title" msgid="7141739231018530210">"Vincular amb el dispositiu"</string>
+ <string name="adb_pairing_device_dialog_title" msgid="7141739231018530210">"Vincula amb el dispositiu"</string>
<string name="adb_pairing_device_dialog_pairing_code_label" msgid="3639239786669722731">"Codi de vinculació Wi‑Fi"</string>
<string name="adb_pairing_device_dialog_failed_title" msgid="3426758947882091735">"No s\'ha pogut vincular"</string>
<string name="adb_pairing_device_dialog_failed_msg" msgid="6611097519661997148">"Assegura\'t que el dispositiu estigui connectat a la mateixa xarxa."</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Desactivat"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activat"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Has de reiniciar el teu dispositiu perquè s\'apliquin els canvis. Reinicia\'l ara o cancel·la."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> de la feina"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index 51548f5..d4d3735 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -555,6 +555,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Vypnuto"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Zapnuto"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Aby se tato změna projevila, je třeba zařízení restartovat. Restartujte zařízení nebo zrušte akci."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Pracovní <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 88f8e92..89dbf25 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -433,7 +433,7 @@
<string name="power_discharge_by" msgid="4113180890060388350">"Bør holde indtil ca. <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
<string name="power_discharge_by_only" msgid="92545648425937000">"Bør holde indtil ca. <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_discharge_by_only_short" msgid="5883041507426914446">"Indtil <xliff:g id="TIME">%1$s</xliff:g>"</string>
- <string name="power_suggestion_battery_run_out" msgid="6332089307827787087">"Enheden løber muligvis tør for batteri inden <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_suggestion_battery_run_out" msgid="6332089307827787087">"Batteriet aflades muligvis inden <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_less_than_duration_only" msgid="8956656616031395152">"Der er mindre end <xliff:g id="THRESHOLD">%1$s</xliff:g> tilbage"</string>
<string name="power_remaining_less_than_duration" msgid="318215464914990578">"Der er mindre end <xliff:g id="THRESHOLD">%1$s</xliff:g> tilbage (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
<string name="power_remaining_more_than_subtext" msgid="446388082266121894">"Der er mere end <xliff:g id="TIME_REMAINING">%1$s</xliff:g> tilbage (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Deaktiveret"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktiveret"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Din enhed skal genstartes for at denne enhed bliver anvendt. Genstart nu, eller annuller."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> – arbejde"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index edd1580..c2030b1 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -231,7 +231,7 @@
<string name="adb_qrcode_pairing_device_failed_msg" msgid="6936292092592914132">"Das Gerät konnte nicht gekoppelt werden. Der QR-Code war nicht korrekt oder das Gerät ist nicht mit demselben Netzwerk verbunden."</string>
<string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP-Adresse & Port"</string>
<string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"QR-Code scannen"</string>
- <string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Scanne einen QR-Code, um ein Gerät über WLAN zu koppeln"</string>
+ <string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Scanne einen QR-Code, um das Gerät über WLAN zu koppeln"</string>
<string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"Bitte stell eine WLAN-Verbindung her"</string>
<string name="keywords_adb_wireless" msgid="6507505581882171240">"ADB, Debug, Dev"</string>
<string name="bugreport_in_power" msgid="8664089072534638709">"Verknüpfung zu Fehlerbericht"</string>
@@ -507,7 +507,7 @@
<string name="zen_mode_duration_settings_title" msgid="1553451650289651489">"Dauer"</string>
<string name="zen_mode_duration_always_prompt_title" msgid="3212996860498119555">"Jedes Mal fragen"</string>
<string name="zen_mode_forever" msgid="3339224497605461291">"Bis zur Deaktivierung"</string>
- <string name="time_unit_just_now" msgid="3006134267292728099">"gerade eben"</string>
+ <string name="time_unit_just_now" msgid="3006134267292728099">"Gerade eben"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Smartphone-Lautsprecher"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Verbindung kann nicht hergestellt werden. Schalte das Gerät aus & und wieder ein."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Netzbetriebenes Audiogerät"</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Deaktiviert"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktiviert"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Damit diese Änderung übernommen wird, musst du dein Gerät neu starten. Du kannst es jetzt neu starten oder den Vorgang abbrechen."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> (geschäftlich)"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index c5c0e07..bfc183e 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -194,8 +194,8 @@
<item msgid="581904787661470707">"Ταχύτατη"</item>
</string-array>
<string name="choose_profile" msgid="343803890897657450">"Επιλογή προφίλ"</string>
- <string name="category_personal" msgid="6236798763159385225">"Προσωπικό"</string>
- <string name="category_work" msgid="4014193632325996115">"Εργασία"</string>
+ <string name="category_personal" msgid="6236798763159385225">"Προσωπικός"</string>
+ <string name="category_work" msgid="4014193632325996115">"Εργασίας"</string>
<string name="development_settings_title" msgid="140296922921597393">"Επιλογές για προγραμματιστές"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Ενεργοποίηση επιλογών για προγραμματιστές"</string>
<string name="development_settings_summary" msgid="8718917813868735095">"Ορισμός επιλογών για ανάπτυξη εφαρμογής"</string>
@@ -554,4 +554,6 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Ενεργή"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Για να εφαρμοστεί αυτή η αλλαγή, θα πρέπει να επανεκκινήσετε τη συσκευή σας. Επανεκκίνηση τώρα ή ακύρωση."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Εργασία <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index a1c8b04..9d869ba 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -229,7 +229,7 @@
<string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"Pair device over Wi‑Fi by scanning a QR code"</string>
<string name="adb_wireless_verifying_qrcode_text" msgid="6123192424916029207">"Pairing device…"</string>
<string name="adb_qrcode_pairing_device_failed_msg" msgid="6936292092592914132">"Failed to pair the device. Either the QR code was incorrect, or the device is not connected to the same network."</string>
- <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP address & port"</string>
+ <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP address and port"</string>
<string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"Scan QR code"</string>
<string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Pair device over Wi‑Fi by scanning a QR code"</string>
<string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"Please connect to a Wi‑Fi network"</string>
@@ -554,4 +554,6 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Work <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index a1c8b04..9d869ba 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -229,7 +229,7 @@
<string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"Pair device over Wi‑Fi by scanning a QR code"</string>
<string name="adb_wireless_verifying_qrcode_text" msgid="6123192424916029207">"Pairing device…"</string>
<string name="adb_qrcode_pairing_device_failed_msg" msgid="6936292092592914132">"Failed to pair the device. Either the QR code was incorrect, or the device is not connected to the same network."</string>
- <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP address & port"</string>
+ <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP address and port"</string>
<string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"Scan QR code"</string>
<string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Pair device over Wi‑Fi by scanning a QR code"</string>
<string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"Please connect to a Wi‑Fi network"</string>
@@ -554,4 +554,6 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Work <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index a1c8b04..9d869ba 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -229,7 +229,7 @@
<string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"Pair device over Wi‑Fi by scanning a QR code"</string>
<string name="adb_wireless_verifying_qrcode_text" msgid="6123192424916029207">"Pairing device…"</string>
<string name="adb_qrcode_pairing_device_failed_msg" msgid="6936292092592914132">"Failed to pair the device. Either the QR code was incorrect, or the device is not connected to the same network."</string>
- <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP address & port"</string>
+ <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP address and port"</string>
<string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"Scan QR code"</string>
<string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Pair device over Wi‑Fi by scanning a QR code"</string>
<string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"Please connect to a Wi‑Fi network"</string>
@@ -554,4 +554,6 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Work <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index a1c8b04..9d869ba 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -229,7 +229,7 @@
<string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"Pair device over Wi‑Fi by scanning a QR code"</string>
<string name="adb_wireless_verifying_qrcode_text" msgid="6123192424916029207">"Pairing device…"</string>
<string name="adb_qrcode_pairing_device_failed_msg" msgid="6936292092592914132">"Failed to pair the device. Either the QR code was incorrect, or the device is not connected to the same network."</string>
- <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP address & port"</string>
+ <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP address and port"</string>
<string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"Scan QR code"</string>
<string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Pair device over Wi‑Fi by scanning a QR code"</string>
<string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"Please connect to a Wi‑Fi network"</string>
@@ -554,4 +554,6 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Work <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index 38ac8f1..7765824 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -554,4 +554,6 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Work <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index c6d0d56..6a76beb 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -61,7 +61,7 @@
<string name="speed_label_medium" msgid="9078405312828606976">"Media"</string>
<string name="speed_label_fast" msgid="2677719134596044051">"Rápida"</string>
<string name="speed_label_very_fast" msgid="8215718029533182439">"Muy rápida"</string>
- <string name="wifi_passpoint_expired" msgid="6540867261754427561">"Vencido"</string>
+ <string name="wifi_passpoint_expired" msgid="6540867261754427561">"Vencida"</string>
<string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
<string name="bluetooth_disconnected" msgid="7739366554710388701">"Desconectado"</string>
<string name="bluetooth_disconnecting" msgid="7638892134401574338">"Desconectando…"</string>
@@ -153,7 +153,7 @@
<string name="unknown" msgid="3544487229740637809">"Desconocido"</string>
<string name="running_process_item_user_label" msgid="3988506293099805796">"Usuario: <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
<string name="launch_defaults_some" msgid="3631650616557252926">"Configuraciones predeterminadas establecidas"</string>
- <string name="launch_defaults_none" msgid="8049374306261262709">"No hay configuraciones predeterminadas establecidas."</string>
+ <string name="launch_defaults_none" msgid="8049374306261262709">"No se establecieron configuraciones predeterminadas"</string>
<string name="tts_settings" msgid="8130616705989351312">"Configuración de texto a voz"</string>
<string name="tts_settings_title" msgid="7602210956640483039">"Salida de texto a voz"</string>
<string name="tts_default_rate_title" msgid="3964187817364304022">"Velocidad de voz"</string>
@@ -213,7 +213,7 @@
<string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Para ver y usar los dispositivos disponibles, activa la depuración inalámbrica"</string>
<string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"Vincular dispositivo mediante código QR"</string>
<string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"Vincular dispositivos nuevos mediante escáner de código QR"</string>
- <string name="adb_pair_method_code_title" msgid="1122590300445142904">"Vincular dispositivo mediante código de sincroniz."</string>
+ <string name="adb_pair_method_code_title" msgid="1122590300445142904">"Vincular dispositivo con código de sincronización"</string>
<string name="adb_pair_method_code_summary" msgid="6370414511333685185">"Vincular dispositivos nuevos mediante código de seis dígitos"</string>
<string name="adb_paired_devices_title" msgid="5268997341526217362">"Dispositivos vinculados"</string>
<string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"Conectado actualmente"</string>
@@ -235,7 +235,7 @@
<string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"Conéctate a una red Wi-Fi"</string>
<string name="keywords_adb_wireless" msgid="6507505581882171240">"adb, debug, dev"</string>
<string name="bugreport_in_power" msgid="8664089072534638709">"Acceso directo para informes de errores"</string>
- <string name="bugreport_in_power_summary" msgid="1885529649381831775">"Mostrar un botón en el menú de encendido para realizar un informe de errores"</string>
+ <string name="bugreport_in_power_summary" msgid="1885529649381831775">"Muestra un botón en el menú de encendido para realizar un informe de errores"</string>
<string name="keep_screen_on" msgid="1187161672348797558">"Permanecer activo"</string>
<string name="keep_screen_on_summary" msgid="1510731514101925829">"La pantalla nunca quedará inactiva mientras el dispositivo se esté cargando."</string>
<string name="bt_hci_snoop_log" msgid="7291287955649081448">"Registro de Bluetooth HCI"</string>
@@ -331,7 +331,7 @@
<string name="media_category" msgid="8122076702526144053">"Multimedia"</string>
<string name="debug_monitoring_category" msgid="1597387133765424994">"Supervisión"</string>
<string name="strict_mode" msgid="889864762140862437">"Modo estricto"</string>
- <string name="strict_mode_summary" msgid="1838248687233554654">"Destello por op. de apps en la conversación principal"</string>
+ <string name="strict_mode_summary" msgid="1838248687233554654">"Destello por op. de apps en el subproceso principal"</string>
<string name="pointer_location" msgid="7516929526199520173">"Ubicación del puntero"</string>
<string name="pointer_location_summary" msgid="957120116989798464">"Superponer capa en pant. para mostrar puntos tocados"</string>
<string name="show_touches" msgid="8437666942161289025">"Mostrar presiones"</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Inhabilitado"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Habilitado"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Debes reiniciar el dispositivo para que se aplique el cambio. Reinícialo ahora o cancela la acción."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> de trabajo"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index 55b5cab..010b85b 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -61,7 +61,7 @@
<string name="speed_label_medium" msgid="9078405312828606976">"Media"</string>
<string name="speed_label_fast" msgid="2677719134596044051">"Rápida"</string>
<string name="speed_label_very_fast" msgid="8215718029533182439">"Muy rápida"</string>
- <string name="wifi_passpoint_expired" msgid="6540867261754427561">"Caducado"</string>
+ <string name="wifi_passpoint_expired" msgid="6540867261754427561">"Caducada"</string>
<string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
<string name="bluetooth_disconnected" msgid="7739366554710388701">"Desconectado"</string>
<string name="bluetooth_disconnecting" msgid="7638892134401574338">"Desconectando…"</string>
@@ -506,7 +506,7 @@
<string name="alarm_template_far" msgid="6382760514842998629">"Fecha: <xliff:g id="WHEN">%1$s</xliff:g>"</string>
<string name="zen_mode_duration_settings_title" msgid="1553451650289651489">"Duración"</string>
<string name="zen_mode_duration_always_prompt_title" msgid="3212996860498119555">"Preguntar siempre"</string>
- <string name="zen_mode_forever" msgid="3339224497605461291">"Hasta que se desactive"</string>
+ <string name="zen_mode_forever" msgid="3339224497605461291">"Hasta que lo desactives"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"justo ahora"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Altavoz del teléfono"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"No se ha podido conectar; reinicia el dispositivo"</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Inhabilitado"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Habilitado"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Es necesario reiniciar tu dispositivo para que se apliquen los cambios. Reiniciar ahora o cancelar."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> de trabajo"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index 5a11590..ad2a561 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -419,7 +419,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaalia (punane-roheline)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaalia (sinine-kollane)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Värvide korrigeerimine"</string>
- <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1284746051652993443">"Värvikorrigeerimine võimaldab kohandada seadmes kuvatavaid värve"</string>
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1284746051652993443">"Värvide korrigeerimine võimaldab kohandada seadmes kuvatavaid värve"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Alistas <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"Ligikaudu <xliff:g id="TIME_REMAINING">%1$s</xliff:g> jäänud"</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Keelatud"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Lubatud"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Selle muudatuse rakendamiseks tuleb seade taaskäivitada. Taaskäivitage kohe või tühistage."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Töö: <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 1aed7e8..1ceb738 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -554,4 +554,6 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Gaituta"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Aldaketa aplikatzeko, berrabiarazi egin behar da gailua. Berrabiaraz ezazu orain, edo utzi bertan behera."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Laneko <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-fa/arrays.xml b/packages/SettingsLib/res/values-fa/arrays.xml
index 3b05075..94caa74 100644
--- a/packages/SettingsLib/res/values-fa/arrays.xml
+++ b/packages/SettingsLib/res/values-fa/arrays.xml
@@ -40,7 +40,7 @@
<item msgid="8339720953594087771">"در حال اتصال به <xliff:g id="NETWORK_NAME">%1$s</xliff:g>…"</item>
<item msgid="3028983857109369308">"در حال راستیآزمایی با <xliff:g id="NETWORK_NAME">%1$s</xliff:g>..."</item>
<item msgid="4287401332778341890">"درحال دریافت نشانی IP از <xliff:g id="NETWORK_NAME">%1$s</xliff:g>…"</item>
- <item msgid="1043944043827424501">"متصل شد به <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</item>
+ <item msgid="1043944043827424501">"به <xliff:g id="NETWORK_NAME">%1$s</xliff:g> متصل شد"</item>
<item msgid="7445993821842009653">"معلق"</item>
<item msgid="1175040558087735707">"اتصال قطع شد از <xliff:g id="NETWORK_NAME">%1$s</xliff:g>..."</item>
<item msgid="699832486578171722">"اتصال قطع شد"</item>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index a46d8cd..d3f1c56 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -94,12 +94,12 @@
<string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"صدای HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
<string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"صدای HD"</string>
<string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"سمعکها"</string>
- <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"متصل به سمعکها"</string>
+ <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"به سمعک متصل شد"</string>
<string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"به رسانه صوتی متصل شد"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"به تلفن صوتی متصل شد"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"به سرور انتقال فایل متصل شد"</string>
<string name="bluetooth_map_profile_summary_connected" msgid="4141725591784669181">"به نقشه متصل شد"</string>
- <string name="bluetooth_sap_profile_summary_connected" msgid="1280297388033001037">"متصل به SAP"</string>
+ <string name="bluetooth_sap_profile_summary_connected" msgid="1280297388033001037">"به SAP متصل شد"</string>
<string name="bluetooth_opp_profile_summary_not_connected" msgid="3959741824627764954">"به سرور انتقال فایل متصل نیست"</string>
<string name="bluetooth_hid_profile_summary_connected" msgid="3923653977051684833">"به دستگاه ورودی متصل شد"</string>
<string name="bluetooth_pan_user_profile_summary_connected" msgid="380469653827505727">"برای دسترسی به اینترنت، به دستگاه متصل شد"</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"غیرفعال"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"فعال"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"برای اعمال این تغییر، دستگاهتان باید راهاندازی مجدد شود. اکنون راهاندازی مجدد کنید یا لغو کنید."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> محل کار"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-fi/arrays.xml b/packages/SettingsLib/res/values-fi/arrays.xml
index af278cb..edfd951 100644
--- a/packages/SettingsLib/res/values-fi/arrays.xml
+++ b/packages/SettingsLib/res/values-fi/arrays.xml
@@ -156,7 +156,7 @@
<item msgid="5001852592115448348">", aktiivinen (puhelin)"</item>
</string-array>
<string-array name="select_logd_size_titles">
- <item msgid="1191094707770726722">"Ei käytössä"</item>
+ <item msgid="1191094707770726722">"Ei päällä"</item>
<item msgid="7839165897132179888">"64 kt"</item>
<item msgid="2715700596495505626">"256 kt"</item>
<item msgid="7099386891713159947">"1 Mt"</item>
@@ -164,13 +164,13 @@
<item msgid="8243549501764402572">"16 Mt"</item>
</string-array>
<string-array name="select_logd_size_lowram_titles">
- <item msgid="1145807928339101085">"Ei käytössä"</item>
+ <item msgid="1145807928339101085">"Ei päällä"</item>
<item msgid="4064786181089783077">"64 kt"</item>
<item msgid="3052710745383602630">"256 kt"</item>
<item msgid="3691785423374588514">"1 Mt"</item>
</string-array>
<string-array name="select_logd_size_summaries">
- <item msgid="409235464399258501">"Ei käytössä"</item>
+ <item msgid="409235464399258501">"Ei päällä"</item>
<item msgid="4195153527464162486">"64 kt / lokipuskuri"</item>
<item msgid="7464037639415220106">"256 kt / lokipuskuri"</item>
<item msgid="8539423820514360724">"1 Mt / lokipuskuri"</item>
@@ -178,13 +178,13 @@
<item msgid="7892098981256010498">"16 Mt / lokipuskuri"</item>
</string-array>
<string-array name="select_logpersist_titles">
- <item msgid="704720725704372366">"Ei käytössä"</item>
+ <item msgid="704720725704372366">"Ei päällä"</item>
<item msgid="6014837961827347618">"Kaikki"</item>
<item msgid="7387060437894578132">"Kaikki paitsi radio"</item>
<item msgid="7300881231043255746">"vain kernel"</item>
</string-array>
<string-array name="select_logpersist_summaries">
- <item msgid="97587758561106269">"Ei käytössä"</item>
+ <item msgid="97587758561106269">"Ei päällä"</item>
<item msgid="7126170197336963369">"Kaikki lokipuskurit"</item>
<item msgid="7167543126036181392">"Kaikki paitsi radiolokipuskurit"</item>
<item msgid="5135340178556563979">"vain kernel-lokipuskuri"</item>
@@ -247,7 +247,7 @@
<item msgid="5023908510820531131">"Kohteessa <xliff:g id="AS_TYPED_COMMAND">adb shell dumpsys gfxinfo</xliff:g>"</item>
</string-array>
<string-array name="debug_hw_overdraw_entries">
- <item msgid="1968128556747588800">"Ei käytössä"</item>
+ <item msgid="1968128556747588800">"Ei päällä"</item>
<item msgid="3033215374382962216">"Näytä päällekkäiset alueet"</item>
<item msgid="3474333938380896988">"Näytä alueet puna-vihersokeille näkyvinä"</item>
</string-array>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 8e82cb2..5ab01c4b 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -153,7 +153,7 @@
<string name="unknown" msgid="3544487229740637809">"Tuntematon"</string>
<string name="running_process_item_user_label" msgid="3988506293099805796">"Käyttäjä: <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
<string name="launch_defaults_some" msgid="3631650616557252926">"Joitakin oletuksia on asetettu"</string>
- <string name="launch_defaults_none" msgid="8049374306261262709">"Oletuksia ei asetettu"</string>
+ <string name="launch_defaults_none" msgid="8049374306261262709">"Ei oletuksia valittuina"</string>
<string name="tts_settings" msgid="8130616705989351312">"Tekstistä puheeksi -asetukset"</string>
<string name="tts_settings_title" msgid="7602210956640483039">"Tekstistä puheeksi -toisto"</string>
<string name="tts_default_rate_title" msgid="3964187817364304022">"Puheen nopeus"</string>
@@ -397,7 +397,7 @@
<item msgid="1282170165150762976">"Digitaaliselle sisällölle parhaiten sopivat värit"</item>
</string-array>
<string name="inactive_apps_title" msgid="5372523625297212320">"Valmiustilasovellukset"</string>
- <string name="inactive_app_inactive_summary" msgid="3161222402614236260">"Ei käytössä. Ota käyttöön koskettamalla."</string>
+ <string name="inactive_app_inactive_summary" msgid="3161222402614236260">"Ei päällä. Ota käyttöön koskettamalla."</string>
<string name="inactive_app_active_summary" msgid="8047630990208722344">"Aktiivinen. Vaihda koskettamalla."</string>
<string name="standby_bucket_summary" msgid="5128193447550429600">"Sovelluksen valmiusluokka: <xliff:g id="BUCKET"> %s</xliff:g>"</string>
<string name="runningservices_settings_title" msgid="6460099290493086515">"Käynnissä olevat palvelut"</string>
@@ -533,8 +533,8 @@
<string name="user_add_user_title" msgid="5457079143694924885">"Lisätäänkö uusi käyttäjä?"</string>
<string name="user_add_user_message_long" msgid="1527434966294733380">"Voit jakaa tämän laitteen muiden kanssa luomalla lisää käyttäjiä. Kullakin käyttäjällä on oma tilansa, jota he voivat muokata esimerkiksi omilla sovelluksilla ja taustakuvilla. Käyttäjät voivat myös muokata laiteasetuksia, kuten Wi‑Fi-asetuksia, jotka vaikuttavat laitteen kaikkiin käyttäjiin.\n\nKun lisäät uuden käyttäjän, hänen tulee määrittää oman tilansa asetukset.\n\nKaikki käyttäjät voivat päivittää muiden käyttäjien sovelluksia. Esteettömyysominaisuuksia tai ‑palveluita ei välttämättä siirretä uudelle käyttäjälle."</string>
<string name="user_add_user_message_short" msgid="3295959985795716166">"Kun lisäät uuden käyttäjän, hänen tulee määrittää oman tilansa asetukset.\n\nKaikki käyttäjät voivat päivittää sovelluksia muille käyttäjille."</string>
- <string name="user_setup_dialog_title" msgid="8037342066381939995">"Määritetäänkö käyttäjän asetukset nyt?"</string>
- <string name="user_setup_dialog_message" msgid="269931619868102841">"Varmista, että käyttäjä voi vastaanottaa laitteen ja määrittää oman tilansa."</string>
+ <string name="user_setup_dialog_title" msgid="8037342066381939995">"Lisätäänkö käyttäjä nyt?"</string>
+ <string name="user_setup_dialog_message" msgid="269931619868102841">"Varmista, että käyttäjä voi ottaa laitteen nyt ja määrittää oman tilansa."</string>
<string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"Määritetäänkö profiilin asetukset nyt?"</string>
<string name="user_setup_button_setup_now" msgid="1708269547187760639">"Määritä nyt"</string>
<string name="user_setup_button_setup_later" msgid="8712980133555493516">"Ei nyt"</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Ei käytössä"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Käytössä"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Laitteesi on käynnistettävä uudelleen, jotta muutos tulee voimaan. Käynnistä uudelleen nyt tai peruuta."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> (työ)"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index a144444..26e5042 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -195,7 +195,7 @@
</string-array>
<string name="choose_profile" msgid="343803890897657450">"Sélectionnez un profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Personnel"</string>
- <string name="category_work" msgid="4014193632325996115">"Travail"</string>
+ <string name="category_work" msgid="4014193632325996115">"Professionnel"</string>
<string name="development_settings_title" msgid="140296922921597393">"Options pour les concepteurs"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Activer les options pour les concepteurs"</string>
<string name="development_settings_summary" msgid="8718917813868735095">"Définir les options pour le développement de l\'application"</string>
@@ -231,7 +231,7 @@
<string name="adb_qrcode_pairing_device_failed_msg" msgid="6936292092592914132">"Échec de l\'association de l\'appareil Soit le code QR est incorrect, soit l\'appareil n\'est pas connecté au même réseau."</string>
<string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"Adresse IP et port"</string>
<string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"Numériser le code QR"</string>
- <string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Associer un appareil par Wi-Fi en numérisant un code QR"</string>
+ <string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Associer l\'appareil par Wi-Fi en numérisant un code QR"</string>
<string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"Veuillez vous connecter à un réseau Wi-Fi"</string>
<string name="keywords_adb_wireless" msgid="6507505581882171240">"adb, débogage, concepteur"</string>
<string name="bugreport_in_power" msgid="8664089072534638709">"Raccourci de rapport de bogue"</string>
@@ -496,7 +496,7 @@
<string name="cancel" msgid="5665114069455378395">"Annuler"</string>
<string name="okay" msgid="949938843324579502">"OK"</string>
<string name="zen_mode_enable_dialog_turn_on" msgid="6418297231575050426">"Activer"</string>
- <string name="zen_mode_settings_turn_on_dialog_title" msgid="2760567063190790696">"Activer la fonction « Ne pas déranger »"</string>
+ <string name="zen_mode_settings_turn_on_dialog_title" msgid="2760567063190790696">"Activer le mode Ne pas déranger"</string>
<string name="zen_mode_settings_summary_off" msgid="3832876036123504076">"Jamais"</string>
<string name="zen_interruption_level_priority" msgid="5392140786447823299">"Priorités seulement"</string>
<string name="zen_mode_and_condition" msgid="8877086090066332516">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
@@ -554,4 +554,6 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activé"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Votre appareil doit être redémarré pour que ce changement prenne effet. Redémarrez-le maintenant ou annulez la modification."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> (travail)"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index f7bfad8..b8fc50d 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -231,7 +231,7 @@
<string name="adb_qrcode_pairing_device_failed_msg" msgid="6936292092592914132">"Échec de l\'association à l\'appareil. Le code QR est incorrect, ou l\'appareil n\'est pas connecté au même réseau."</string>
<string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"Adresse IP et port"</string>
<string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"Scanner un code QR"</string>
- <string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Associer l\'appareil via le Wi‑Fi à l\'aide d\'un code QR"</string>
+ <string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Associez l\'appareil via le Wi‑Fi à l\'aide d\'un code QR"</string>
<string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"Connectez-vous à un réseau Wi-Fi"</string>
<string name="keywords_adb_wireless" msgid="6507505581882171240">"adb, débogage, dev"</string>
<string name="bugreport_in_power" msgid="8664089072534638709">"Raccourci vers rapport de bug"</string>
@@ -433,7 +433,7 @@
<string name="power_discharge_by" msgid="4113180890060388350">"Devrait durer jusqu\'à environ <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
<string name="power_discharge_by_only" msgid="92545648425937000">"Devrait durer jusqu\'à environ <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_discharge_by_only_short" msgid="5883041507426914446">"Jusqu\'à <xliff:g id="TIME">%1$s</xliff:g>"</string>
- <string name="power_suggestion_battery_run_out" msgid="6332089307827787087">"La batterie risque d\'être épuisée à <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_suggestion_battery_run_out" msgid="6332089307827787087">"La batterie risque d\'être épuisée d\'ici <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_less_than_duration_only" msgid="8956656616031395152">"Il reste moins de <xliff:g id="THRESHOLD">%1$s</xliff:g>"</string>
<string name="power_remaining_less_than_duration" msgid="318215464914990578">"Il reste moins de <xliff:g id="THRESHOLD">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
<string name="power_remaining_more_than_subtext" msgid="446388082266121894">"Il reste plus de <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Désactivé"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activé"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Vous devez redémarrer l\'appareil pour que cette modification soit appliquée. Redémarrez maintenant ou annulez l\'opération."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> (travail)"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index 08d2eae..8dc58ef 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -207,7 +207,7 @@
<string name="enable_adb_summary" msgid="3711526030096574316">"Modo de depuración de erros cando o USB está conectado"</string>
<string name="clear_adb_keys" msgid="3010148733140369917">"Revogar as autorizacións de depuración por USB"</string>
<string name="enable_adb_wireless" msgid="6973226350963971018">"Depuración sen fíos"</string>
- <string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Modo de depuración de erros ao conectarse a wifi"</string>
+ <string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Modo de depuración de erros ao conectarse á wifi"</string>
<string name="adb_wireless_error" msgid="721958772149779856">"Produciuse un erro"</string>
<string name="adb_wireless_settings" msgid="2295017847215680229">"Depuración sen fíos"</string>
<string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Para ver e usar os dispositivos dispoñibles, activa a depuración sen fíos"</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Desactivado"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activado"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"É necesario reiniciar o teu dispositivo para aplicar este cambio. Reiníciao agora ou cancela o cambio."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Aplicación <xliff:g id="APP_NAME">%s</xliff:g> do traballo"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index b88126b..9f69a2c 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -468,7 +468,7 @@
<string name="charge_length_format" msgid="6941645744588690932">"<xliff:g id="ID_1">%1$s</xliff:g> પહેલાં"</string>
<string name="remaining_length_format" msgid="4310625772926171089">"<xliff:g id="ID_1">%1$s</xliff:g> બાકી"</string>
<string name="screen_zoom_summary_small" msgid="6050633151263074260">"નાનું"</string>
- <string name="screen_zoom_summary_default" msgid="1888865694033865408">"ડિફોલ્ટ"</string>
+ <string name="screen_zoom_summary_default" msgid="1888865694033865408">"ડિફૉલ્ટ"</string>
<string name="screen_zoom_summary_large" msgid="4706951482598978984">"મોટું"</string>
<string name="screen_zoom_summary_very_large" msgid="7317423942896999029">"વધુ મોટું"</string>
<string name="screen_zoom_summary_extremely_large" msgid="1438045624562358554">"સૌથી મોટું"</string>
@@ -534,7 +534,7 @@
<string name="user_add_user_message_long" msgid="1527434966294733380">"તમે વધારાના વપરાશકર્તાઓ બનાવીને અન્ય લોકો સાથે આ ડિવાઇસને શેર કરી શકો છો. દરેક વપરાશકર્તા પાસે તેમની પોતાની સ્પેસ છે, જેને તેઓ ઍપ, વૉલપેપર, વગેરે સાથે કસ્ટમાઇઝ કરી શકે છે. વપરાશકર્તાઓ પ્રત્યેક વ્યક્તિને અસર કરતી હોય તેવી ડિવાઇસ સેટિંગ જેમ કે વાઇ-ફાઇને પણ સમાયોજિત કરી શકે છે.\n\nજ્યારે તમે કોઈ નવા વપરાશકર્તાને ઉમેરો છો, ત્યારે તે વ્યક્તિને તેમની સ્પેસ સેટ કરવાની જરૂર પડે છે.\n\nકોઈપણ વપરાશકર્તા અન્ય બધા વપરાશકર્તાઓ માટે ઍપને અપડેટ કરી શકે છે. નવા વપરાશકર્તાને ઍક્સેસિબિલિટી સેટિંગ અને સેવાઓ ટ્રાન્સફર ન પણ થાય."</string>
<string name="user_add_user_message_short" msgid="3295959985795716166">"જ્યારે તમે કોઈ નવા વપરાશકર્તાને ઉમેરો છો, ત્યારે તે વ્યક્તિને તેમનું સ્થાન સેટ અપ કરવાની જરૂર પડે છે.\n\nકોઈપણ વપરાશકર્તા બધા અન્ય વપરાશકર્તાઓ માટે એપ્લિકેશન્સને અપડેટ કરી શકે છે."</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"અત્યારે જ વપરાશકર્તાને સેટ અપ કરીએ?"</string>
- <string name="user_setup_dialog_message" msgid="269931619868102841">"ખાતરી કરો કે વ્યક્તિ ઉપકરણ લેવા અને તેમના સ્થાનનું સેટ અપ કરવા માટે ઉપલબ્ધ છે"</string>
+ <string name="user_setup_dialog_message" msgid="269931619868102841">"ખાતરી કરો કે વ્યક્તિ ડિવાઇસ લેવા અને તેમના સ્થાનનું સેટ અપ કરવા માટે ઉપલબ્ધ છે"</string>
<string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"હવે પ્રોફાઇલ સેટ કરીએ?"</string>
<string name="user_setup_button_setup_now" msgid="1708269547187760639">"હવે સેટ કરો"</string>
<string name="user_setup_button_setup_later" msgid="8712980133555493516">"હમણાં નહીં"</string>
@@ -549,14 +549,11 @@
<string name="guest_new_guest" msgid="3482026122932643557">"અતિથિ ઉમેરો"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"અતિથિને કાઢી નાખો"</string>
<string name="guest_nickname" msgid="6332276931583337261">"અતિથિ"</string>
- <!-- no translation found for cached_apps_freezer_device_default (2616594131750144342) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_disabled (4816382260660472042) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_enabled (8866703500183051546) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_reboot_dialog_text (695330563489230096) -->
- <skip />
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"ડિવાઇસ ડિફૉલ્ટ"</string>
+ <string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"બંધ છે"</string>
+ <string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ચાલુ છે"</string>
+ <string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"આ ફેરફારને લાગુ કરવા માટે તમારા ડિવાઇસને રીબૂટ કરવાની જરૂર છે. હમણાં જ રીબૂટ કરો કે રદ કરો."</string>
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"ઑફિસ <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 7dda73d..dc8e28b 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -284,7 +284,7 @@
<string name="wifi_display_certification_summary" msgid="8111151348106907513">"वायरलेस डिसप्ले सर्टिफ़िकेशन के विकल्प दिखाएं"</string>
<string name="wifi_verbose_logging_summary" msgid="4993823188807767892">"वाई-फ़ाई लॉगिंग का स्तर बढ़ाएं, वाई-फ़ाई पिकर में प्रति SSID RSSI दिखाएं"</string>
<string name="wifi_scan_throttling_summary" msgid="2577105472017362814">"बैटरी की खपत कम और नेटवर्क की परफ़ॉर्मेंस बेहतर होती है"</string>
- <string name="wifi_enhanced_mac_randomization_summary" msgid="1210663439867489931">"जब यह मोड चालू होता है, तब नेटवर्क से कनेक्ट होने पर हर बार इस डिवाइस का MAC पता बदल सकता है. ऐसा तब होता है, जब डिवाइस किसी ऐसे नेटवर्क से जुड़ता है जिस पर MAC पते को बिना किसी तय नियम के बदलने की सुविधा चालू होती है."</string>
+ <string name="wifi_enhanced_mac_randomization_summary" msgid="1210663439867489931">"जब यह मोड चालू होता है, तब नेटवर्क से कनेक्ट होने पर हर बार इस डिवाइस का मैक पता बदल सकता है. ऐसा तब होता है, जब डिवाइस किसी ऐसे नेटवर्क से जुड़ता है जिस पर मैक पते को बिना किसी तय नियम के बदलने की सुविधा चालू होती है."</string>
<string name="wifi_metered_label" msgid="8737187690304098638">"डेटा इस्तेमाल करने की सीमा तय की गई है"</string>
<string name="wifi_unmetered_label" msgid="6174142840934095093">"डेटा इस्तेमाल करने की सीमा तय नहीं की गई है"</string>
<string name="select_logd_size_title" msgid="1604578195914595173">"लॉगर बफ़र आकार"</string>
@@ -418,7 +418,7 @@
<string name="daltonizer_mode_deuteranomaly" msgid="3507284319584683963">"लाल-हरे रंग की पहचान न कर पाना (लाल-हरा)"</string>
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"लाल रंग पहचान न पाना (लाल-हरा)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"नीला रंग पहचान न पाना (नीला-पीला)"</string>
- <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"रंग सुधार"</string>
+ <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"रंग में सुधार करने की सुविधा"</string>
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="1284746051652993443">"रंग में सुधार करने की सुविधा, आपके डिवाइस पर दिखने वाले रंगों में बदलाव करने में मदद करती है"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> के द्वारा ओवरराइड किया गया"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"बंद है"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"चालू है"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"बदली गई सेटिंग को लागू करने के लिए, अपने डिवाइस को फिर से चालू करें. डिवाइस को फिर से चालू करें या रद्द करें."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"ऑफ़िस वाला <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 1db40a8..f887839 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -251,7 +251,7 @@
<string name="wifi_display_certification" msgid="1805579519992520381">"Certifikacija bežičnog prikaza"</string>
<string name="wifi_verbose_logging" msgid="1785910450009679371">"Omogući opširnu prijavu na Wi-Fi"</string>
<string name="wifi_scan_throttling" msgid="2985624788509913617">"Usporavanje traženja Wi-Fija"</string>
- <string name="wifi_enhanced_mac_randomization" msgid="5437378364995776979">"Nasum. odabir MAC-a poboljšan Wi‑Fi‑jem"</string>
+ <string name="wifi_enhanced_mac_randomization" msgid="5437378364995776979">"Nasum. odabir MAC-a poboljšan Wi‑Fijem"</string>
<string name="mobile_data_always_on" msgid="8275958101875563572">"Mobilni podaci uvijek aktivni"</string>
<string name="tethering_hardware_offload" msgid="4116053719006939161">"Hardversko ubrzanje za modemsko povezivanje"</string>
<string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Prikaži Bluetooth uređaje bez naziva"</string>
@@ -534,8 +534,8 @@
<string name="user_add_user_title" msgid="5457079143694924885">"Dodati novog korisnika?"</string>
<string name="user_add_user_message_long" msgid="1527434966294733380">"Da biste ovaj uređaj dijelili s drugima, možete napraviti dodatne korisnike. Svaki korisnik ima svoj prostor koji može prilagoditi pomoću vlastitih aplikacija, pozadine i tako dalje. Korisnici mogu prilagoditi i postavke uređaja koje utječu na sve ostale korisnike, na primjer Wi‑Fi.\n\nKada dodate novog korisnika, ta osoba mora postaviti svoj prostor.\n\nBilo koji korisnik može ažurirati aplikacije za sve ostale korisnike. Postavke i usluge pristupačnosti možda se neće prenijeti na novog korisnika."</string>
<string name="user_add_user_message_short" msgid="3295959985795716166">"Kada dodate novog korisnika, ta osoba mora postaviti vlastiti prostor.\n\nBilo koji korisnik može ažurirati aplikacije za sve ostale korisnike."</string>
- <string name="user_setup_dialog_title" msgid="8037342066381939995">"Postaviti korisnika sada?"</string>
- <string name="user_setup_dialog_message" msgid="269931619868102841">"Provjerite može li osoba uzeti uređaj i postaviti svoj prostor"</string>
+ <string name="user_setup_dialog_title" msgid="8037342066381939995">"Želite li postaviti korisnika?"</string>
+ <string name="user_setup_dialog_message" msgid="269931619868102841">"Uređaj morate dati toj osobi da sama postavi svoj prostor"</string>
<string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"Želite li sada postaviti profil?"</string>
<string name="user_setup_button_setup_now" msgid="1708269547187760639">"Postavi sada"</string>
<string name="user_setup_button_setup_later" msgid="8712980133555493516">"Ne sad"</string>
@@ -555,4 +555,6 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Omogućeno"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Uređaj se mora ponovno pokrenuti da bi se ta promjena primijenila. Ponovo pokrenite uređaj odmah ili odustanite."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> za posao"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 4b2132b..d7d2694 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -433,7 +433,7 @@
<string name="power_discharge_by" msgid="4113180890060388350">"Nagyjából eddig bírja: <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
<string name="power_discharge_by_only" msgid="92545648425937000">"Nagyjából eddig bírja: <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_discharge_by_only_short" msgid="5883041507426914446">"Eddig: <xliff:g id="TIME">%1$s</xliff:g>"</string>
- <string name="power_suggestion_battery_run_out" msgid="6332089307827787087">"Az akkumulátor lemerülhet a következő időpontig: <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_suggestion_battery_run_out" msgid="6332089307827787087">"Az akkumulátor lemerülhet: <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_less_than_duration_only" msgid="8956656616031395152">"Kevesebb mint <xliff:g id="THRESHOLD">%1$s</xliff:g> van hátra"</string>
<string name="power_remaining_less_than_duration" msgid="318215464914990578">"Kevesebb mint <xliff:g id="THRESHOLD">%1$s</xliff:g> van hátra (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
<string name="power_remaining_more_than_subtext" msgid="446388082266121894">"Több mint <xliff:g id="TIME_REMAINING">%1$s</xliff:g> van hátra (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Letiltva"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Engedélyezve"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Az eszközt újra kell indítani, hogy a módosítás megtörténjen. Indítsa újra most, vagy vesse el a módosítást."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Munkahelyi <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 11da054..a5ef6b5 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -433,7 +433,7 @@
<string name="power_discharge_by" msgid="4113180890060388350">"Լիցքը (<xliff:g id="LEVEL">%2$s</xliff:g>) պետք է որ բավականացնի մինչև <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_discharge_by_only" msgid="92545648425937000">"Լիցքը պետք է որ բավականացնի մինչև <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_discharge_by_only_short" msgid="5883041507426914446">"Մինչև <xliff:g id="TIME">%1$s</xliff:g>"</string>
- <string name="power_suggestion_battery_run_out" msgid="6332089307827787087">"Մարտկոցի լիցքը կարող է սպառվել մինչև <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_suggestion_battery_run_out" msgid="6332089307827787087">"Լիցքը կարող է սպառվել մինչև <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_less_than_duration_only" msgid="8956656616031395152">"Մնացել է <xliff:g id="THRESHOLD">%1$s</xliff:g>-ից քիչ"</string>
<string name="power_remaining_less_than_duration" msgid="318215464914990578">"Մնացել է <xliff:g id="THRESHOLD">%1$s</xliff:g>-ից քիչ (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
<string name="power_remaining_more_than_subtext" msgid="446388082266121894">"Մնացել է ավելի քան <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
@@ -452,7 +452,7 @@
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Արագ լիցքավորում"</string>
<string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Դանդաղ լիցքավորում"</string>
<string name="battery_info_status_discharging" msgid="6962689305413556485">"Չի լիցքավորվում"</string>
- <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Միացված է հոսանքի աղբյուրին, սակայն այս պահին չի կարող լիցքավորվել"</string>
+ <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Միացված է հոսանքին, այս պահին չի կարող լիցքավորվել"</string>
<string name="battery_info_status_full" msgid="4443168946046847468">"Լիցքավորված է"</string>
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Վերահսկվում է ադմինիստրատորի կողմից"</string>
<string name="disabled" msgid="8017887509554714950">"Կասեցված է"</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Անջատված է"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Միացված է"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Սարքն անհրաժեշտ է վերագործարկել, որպեսզի փոփոխությունը կիրառվի։ Վերագործարկեք հիմա կամ չեղարկեք փոփոխությունը։"</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Աշխատանքային <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 3b02fbf..04ca2f6 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -195,7 +195,7 @@
</string-array>
<string name="choose_profile" msgid="343803890897657450">"Pilih profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Pribadi"</string>
- <string name="category_work" msgid="4014193632325996115">"Kantor"</string>
+ <string name="category_work" msgid="4014193632325996115">"Kerja"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opsi developer"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Aktifkan opsi developer"</string>
<string name="development_settings_summary" msgid="8718917813868735095">"Menyetel opsi untuk pengembangan apl"</string>
@@ -433,7 +433,7 @@
<string name="power_discharge_by" msgid="4113180890060388350">"Akan bertahan kira-kira sampai pukul <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
<string name="power_discharge_by_only" msgid="92545648425937000">"Akan bertahan kira-kira sampai pukul <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_discharge_by_only_short" msgid="5883041507426914446">"Hingga <xliff:g id="TIME">%1$s</xliff:g>"</string>
- <string name="power_suggestion_battery_run_out" msgid="6332089307827787087">"Baterai mungkin habis pada <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_suggestion_battery_run_out" msgid="6332089307827787087">"Baterai mungkin habis pada pukul <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_less_than_duration_only" msgid="8956656616031395152">"Tersisa kurang dari <xliff:g id="THRESHOLD">%1$s</xliff:g>"</string>
<string name="power_remaining_less_than_duration" msgid="318215464914990578">"Tersisa kurang dari <xliff:g id="THRESHOLD">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
<string name="power_remaining_more_than_subtext" msgid="446388082266121894">"Tersisa lebih dari <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Nonaktif"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktif"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Perangkat Anda harus di-reboot agar perubahan ini diterapkan. Reboot sekarang atau batalkan."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> kerja"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index 6560de0..ce60eb5 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Slökkt"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Virkt"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Endurræsa þarf tækið til að þessi breyting taki gildi. Endurræstu núna eða hættu við."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> í vinnu"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index ece0ba4..bef644b 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Non attivo"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Attivo"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Devi riavviare il dispositivo per applicare questa modifica. Riavvia ora o annulla."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"App <xliff:g id="APP_NAME">%s</xliff:g> di lavoro"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 23e701d..ed79632 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -555,6 +555,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"מושבת"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"מופעל"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"צריך להפעיל מחדש את המכשיר כדי להחיל את השינוי. יש להפעיל מחדש עכשיו או לבטל."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> של עבודה"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 7640722..3f08a6a 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -210,7 +210,7 @@
<string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Wi-Fi 接続時にデバッグモード"</string>
<string name="adb_wireless_error" msgid="721958772149779856">"エラー"</string>
<string name="adb_wireless_settings" msgid="2295017847215680229">"ワイヤレス デバッグ"</string>
- <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"利用可能なデバイスを確認して使用するには、ワイヤレス デバッグをオンにしてください"</string>
+ <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"利用可能なデバイスを確認して使用するには、ワイヤレス デバッグを ON にしてください"</string>
<string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"QR コードによるデバイスのペア設定"</string>
<string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"QR コードスキャナを使って新しいデバイスをペア設定します"</string>
<string name="adb_pair_method_code_title" msgid="1122590300445142904">"ペア設定コードによるデバイスのペア設定"</string>
@@ -235,7 +235,7 @@
<string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"Wi-Fi ネットワークに接続してください"</string>
<string name="keywords_adb_wireless" msgid="6507505581882171240">"adb, デバッグ, dev"</string>
<string name="bugreport_in_power" msgid="8664089072534638709">"バグレポートのショートカット"</string>
- <string name="bugreport_in_power_summary" msgid="1885529649381831775">"電源メニューにバグレポートを取得するボタンを表示する"</string>
+ <string name="bugreport_in_power_summary" msgid="1885529649381831775">"電源ボタン メニューにバグレポートを取得するボタンを表示する"</string>
<string name="keep_screen_on" msgid="1187161672348797558">"スリープモードにしない"</string>
<string name="keep_screen_on_summary" msgid="1510731514101925829">"充電中に画面をスリープにしない"</string>
<string name="bt_hci_snoop_log" msgid="7291287955649081448">"Bluetooth HCI スヌープログ"</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"無効"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"有効"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"この変更を適用するには、デバイスの再起動が必要です。今すぐ再起動してください。キャンセルすることもできます。"</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"仕事の<xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 985188f..2ebc3ee 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -554,4 +554,6 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ჩართული"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ამ ცვლილების ასამოქმედებლად თქვენი მოწყობილობა უნდა გადაიტვირთოს. გადატვირთეთ ახლავე ან გააუქმეთ."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"სამსახურის <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index a5308e4..91e5488 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -42,7 +42,7 @@
<string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> арқылы жалғанған"</string>
<string name="available_via_passpoint" msgid="1716000261192603682">"%1$s арқылы қолжетімді"</string>
<string name="tap_to_sign_up" msgid="5356397741063740395">"Тіркелу үшін түртіңіз."</string>
- <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Интернетпен байланыс жоқ."</string>
+ <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Интернетпен байланыс жоқ"</string>
<string name="private_dns_broken" msgid="1984159464346556931">"Жеке DNS серверіне кіру мүмкін емес."</string>
<string name="wifi_limited_connection" msgid="1184778285475204682">"Шектеулі байланыс"</string>
<string name="wifi_status_no_internet" msgid="3799933875988829048">"Интернетпен байланыс жоқ"</string>
@@ -61,7 +61,7 @@
<string name="speed_label_medium" msgid="9078405312828606976">"Орташа"</string>
<string name="speed_label_fast" msgid="2677719134596044051">"Жылдам"</string>
<string name="speed_label_very_fast" msgid="8215718029533182439">"Өте жылдам"</string>
- <string name="wifi_passpoint_expired" msgid="6540867261754427561">"Мерзімі өтті."</string>
+ <string name="wifi_passpoint_expired" msgid="6540867261754427561">"Мерзімі өтті"</string>
<string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
<string name="bluetooth_disconnected" msgid="7739366554710388701">"Ажыратылған"</string>
<string name="bluetooth_disconnecting" msgid="7638892134401574338">"Ажыратылуда…"</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Өшірулі"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Қосулы"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Бұл өзгеріс күшіне енуі үшін, құрылғыны қайта жүктеу керек. Қазір қайта жүктеңіз не бас тартыңыз."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> (жұмыс)"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 0ff48e1..143f5f9 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -445,8 +445,8 @@
<string name="power_remaining_duration_shutdown_imminent" product="tablet" msgid="7703677921000858479">"ថេប្លេតអាចនឹងបិទក្នុងពេលបន្តិចទៀត (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
<string name="power_remaining_duration_shutdown_imminent" product="device" msgid="4374784375644214578">"ឧបករណ៍អាចនឹងបិទក្នុងពេលបន្តិចទៀត (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
<string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
- <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"<xliff:g id="TIME">%1$s</xliff:g> ទៀតទើបត្រូវសាក"</string>
- <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ទៀតទើបត្រូវសាក"</string>
+ <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"<xliff:g id="TIME">%1$s</xliff:g> ទៀតទើបសាកថ្មពេញ"</string>
+ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ទៀតទើបសាកថ្មពេញ"</string>
<string name="battery_info_status_unknown" msgid="268625384868401114">"មិនស្គាល់"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"កំពុងបញ្ចូលថ្ម"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"កំពុងសាកថ្មយ៉ាងឆាប់រហ័ស"</string>
@@ -537,7 +537,7 @@
<string name="user_setup_dialog_message" msgid="269931619868102841">"សូមប្រាកដថាអ្នកប្រើប្រាស់នេះអាចយកឧបករណ៍ និងរៀបចំទំហំផ្ទុករបស់គេបាន"</string>
<string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"រៀបចំប្រវត្តិរូបឥឡូវ?"</string>
<string name="user_setup_button_setup_now" msgid="1708269547187760639">"រៀបចំឥឡូវ"</string>
- <string name="user_setup_button_setup_later" msgid="8712980133555493516">"កុំអាល"</string>
+ <string name="user_setup_button_setup_later" msgid="8712980133555493516">"កុំទាន់"</string>
<string name="user_add_user_type_title" msgid="551279664052914497">"បន្ថែម"</string>
<string name="user_new_user_name" msgid="60979820612818840">"អ្នកប្រើថ្មី"</string>
<string name="user_new_profile_name" msgid="2405500423304678841">"ប្រវត្តិរូបថ្មី"</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"បានបិទ"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"បានបើក"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ត្រូវតែចាប់ផ្ដើមឧបករណ៍របស់អ្នកឡើងវិញ ទើបការផ្លាស់ប្ដូរនេះត្រូវបានអនុវត្ត។ ចាប់ផ្ដើមឡើងវិញឥឡូវនេះ ឬបោះបង់។"</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> សម្រាប់ការងារ"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index 52e3b99..1df141c 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -549,14 +549,11 @@
<string name="guest_new_guest" msgid="3482026122932643557">"ಅತಿಥಿಯನ್ನು ಸೇರಿಸಿ"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"ಅತಿಥಿಯನ್ನು ತೆಗೆದುಹಾಕಿ"</string>
<string name="guest_nickname" msgid="6332276931583337261">"ಅತಿಥಿ"</string>
- <!-- no translation found for cached_apps_freezer_device_default (2616594131750144342) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_disabled (4816382260660472042) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_enabled (8866703500183051546) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_reboot_dialog_text (695330563489230096) -->
- <skip />
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"ಸಾಧನದ ಡೀಫಾಲ್ಟ್"</string>
+ <string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
+ <string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
+ <string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ಈ ಬದಲಾವಣೆ ಅನ್ವಯವಾಗಲು ನಿಮ್ಮ ಸಾಧನವನ್ನು ರೀಬೂಟ್ ಮಾಡಬೇಕು. ಇದೀಗ ರೀಬೂಟ್ ಮಾಡಿ ಅಥವಾ ರದ್ದುಗೊಳಿಸಿ."</string>
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"ಉದ್ಯೋಗ <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index b8d587b..86e1650 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -495,7 +495,7 @@
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"시간 줄이기"</string>
<string name="cancel" msgid="5665114069455378395">"취소"</string>
<string name="okay" msgid="949938843324579502">"확인"</string>
- <string name="zen_mode_enable_dialog_turn_on" msgid="6418297231575050426">"켜기"</string>
+ <string name="zen_mode_enable_dialog_turn_on" msgid="6418297231575050426">"사용 설정"</string>
<string name="zen_mode_settings_turn_on_dialog_title" msgid="2760567063190790696">"방해 금지 모드 사용 설정"</string>
<string name="zen_mode_settings_summary_off" msgid="3832876036123504076">"사용 안함"</string>
<string name="zen_interruption_level_priority" msgid="5392140786447823299">"중요 알림만 허용"</string>
@@ -530,7 +530,7 @@
<string name="user_add_profile_item_summary" msgid="5418602404308968028">"내 계정의 앱 및 콘텐츠에 대한 액세스를 제한할 수 있습니다."</string>
<string name="user_add_user_item_title" msgid="2394272381086965029">"사용자"</string>
<string name="user_add_profile_item_title" msgid="3111051717414643029">"제한된 프로필"</string>
- <string name="user_add_user_title" msgid="5457079143694924885">"새 사용자를 추가할까요?"</string>
+ <string name="user_add_user_title" msgid="5457079143694924885">"신규 사용자를 추가할까요?"</string>
<string name="user_add_user_message_long" msgid="1527434966294733380">"추가 사용자를 만들어 다른 사용자와 기기를 공유할 수 있습니다. 각 사용자는 앱, 배경화면 등으로 맞춤설정할 수 있는 자신만의 공간을 갖게 됩니다. 또한 모든 사용자에게 영향을 미치는 Wi‑Fi와 같은 기기 설정도 조정할 수 있습니다.\n\n추가된 신규 사용자는 자신의 공간을 설정해야 합니다.\n\n모든 사용자가 앱을 업데이트할 수 있으며, 업데이트는 다른 사용자에게도 적용됩니다. 접근성 설정 및 서비스는 신규 사용자에게 이전되지 않을 수도 있습니다."</string>
<string name="user_add_user_message_short" msgid="3295959985795716166">"추가된 새로운 사용자는 자신의 공간을 설정해야 합니다.\n\n모든 사용자는 다른 사용자들을 위하여 앱을 업데이트할 수 있습니다."</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"지금 사용자를 설정하시겠습니까?"</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"사용 중지됨"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"사용 설정됨"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"변경사항을 적용하려면 기기를 재부팅해야 합니다. 지금 재부팅하거나 취소하세요."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"직장용 <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 3c57c79..925f4a7 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -224,7 +224,7 @@
<string name="adb_wireless_connection_failed_message" msgid="9213896700171602073">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> туура тармакка туташып турганын текшериңиз"</string>
<string name="adb_pairing_device_dialog_title" msgid="7141739231018530210">"Түзмөктү жупташтыруу"</string>
<string name="adb_pairing_device_dialog_pairing_code_label" msgid="3639239786669722731">"Wi‑Fi жупташтыруучу коду"</string>
- <string name="adb_pairing_device_dialog_failed_title" msgid="3426758947882091735">"Жупташтырылган жок"</string>
+ <string name="adb_pairing_device_dialog_failed_title" msgid="3426758947882091735">"Туташкан жок"</string>
<string name="adb_pairing_device_dialog_failed_msg" msgid="6611097519661997148">"Түзмөк бир тармакка туташып турушу керек."</string>
<string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"QR кодун скандап, түзмөктү Wi‑Fi аркылуу жупташтырыңыз"</string>
<string name="adb_wireless_verifying_qrcode_text" msgid="6123192424916029207">"Түзмөк жупташтырылууда…"</string>
@@ -302,7 +302,7 @@
<string name="tethering_hardware_offload_summary" msgid="7801345335142803029">"Мүмкүнчүлүккө жараша, модем режиминде аппарат тезирээк иштей баштайт"</string>
<string name="adb_warning_title" msgid="7708653449506485728">"USB аркылуу жөндөөгө уруксат бересизби?"</string>
<string name="adb_warning_message" msgid="8145270656419669221">"USB-жөндөө - өндүрүү максатында гана түзүлгөн. Аны компүтериңиз менен түзмөгүңүздүн ортосунда берилиштерди алмашуу, түзмөгүңүзгө колдонмолорду эскертүүсүз орнотуу жана лог берилиштерин окуу үчүн колдонсоңуз болот."</string>
- <string name="adbwifi_warning_title" msgid="727104571653031865">"Мүчүлүштүктөрдү Wi-Fi аркылуу оңдоого уруксат берилсинби?"</string>
+ <string name="adbwifi_warning_title" msgid="727104571653031865">"Мүчүлүштүктөрдү Wi-Fi аркылуу оңдоого уруксат бересизби?"</string>
<string name="adbwifi_warning_message" msgid="8005936574322702388">"Мүчүлүштүктөрдү Wi-Fi аркылуу аныктоо – өндүрүү максатында гана түзүлгөн. Аны компьютериңиз менен түзмөгүңүздүн ортосунда маалыматты алмашуу, колдонмолорду түзмөгүңүзгө эскертүүсүз орнотуу жана маалыматтар таржымалын окуу үчүн колдонсоңуз болот."</string>
<string name="adb_keys_warning_message" msgid="2968555274488101220">"Сиз мурун USB жөндөөлөрүнө уруксат берген бардык компүтерлердин жеткиси жокко чыгарылсынбы?"</string>
<string name="dev_settings_warning_title" msgid="8251234890169074553">"Жөндөөлөрдү өзгөртүү"</string>
@@ -445,8 +445,8 @@
<string name="power_remaining_duration_shutdown_imminent" product="tablet" msgid="7703677921000858479">"Планшет бир аздан кийин өчүп калышы мүмкүн (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
<string name="power_remaining_duration_shutdown_imminent" product="device" msgid="4374784375644214578">"Түзмөк бир аздан кийин өчүп калышы мүмкүн (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
<string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
- <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"<xliff:g id="TIME">%1$s</xliff:g> кийин кубатталат"</string>
- <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> кийин кубатталат"</string>
+ <string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"<xliff:g id="TIME">%1$s</xliff:g> кийин толук кубатталып бүтөт"</string>
+ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> кийин толук кубатталып бүтөт"</string>
<string name="battery_info_status_unknown" msgid="268625384868401114">"Белгисиз"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Кубатталууда"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ыкчам кубатталууда"</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Өчүк"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Күйүк"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Бул өзгөртүүнү колдонуу үчүн түзмөктү өчүрүп күйгүзүңүз. Азыр өчүрүп күйгүзүңүз же жокко чыгарыңыз."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Жумуш <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 02f73b5..3bf1996 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -554,4 +554,6 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ເປີດການນຳໃຊ້ແລ້ວ"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ທ່ານຕ້ອງປິດເປີດອຸປະກອນຄືນໃໝ່ເພື່ອນຳໃຊ້ການປ່ຽນແປງນີ້. ປິດເປີດໃໝ່ດຽວນີ້ ຫຼື ຍົກເລີກ."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"ບ່ອນເຮັດວຽກ <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index dc6347a..153c995 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -555,6 +555,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Išjungta"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Įgalinta"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Kad pakeitimas būtų pritaikytas, įrenginį reikia paleisti iš naujo. Dabar paleiskite iš naujo arba atšaukite."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Darbo „<xliff:g id="APP_NAME">%s</xliff:g>“"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index 0a185d0..f6e7f35 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -168,7 +168,7 @@
<string name="tts_play_example_summary" msgid="634044730710636383">"Atskaņot īsu runas sintēzes demonstrāciju"</string>
<string name="tts_install_data_title" msgid="1829942496472751703">"Instalēt balss datus"</string>
<string name="tts_install_data_summary" msgid="3608874324992243851">"Instalēt runas sintēzei nepieciešamos balss datus"</string>
- <string name="tts_engine_security_warning" msgid="3372432853837988146">"Lietojot šo runas sintēzes programmu, var tikt apkopots viss ierunātais teksts, tostarp tāda personīgā informācija kā paroles un kredītkaršu numuri. Tā ir no <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g> programmas. Vai iespējot šīs runas sintēzes programmas lietošanu?"</string>
+ <string name="tts_engine_security_warning" msgid="3372432853837988146">"Lietojot šo runas sintēzes programmu, var tikt vākts viss ierunātais teksts, tostarp tāda personīgā informācija kā paroles un kredītkaršu numuri. Tā ir no <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g> programmas. Vai iespējot šīs runas sintēzes programmas lietošanu?"</string>
<string name="tts_engine_network_required" msgid="8722087649733906851">"Lai izmantotu teksta pārveidošanu runā šajā valodā, ir nepieciešams aktīvs tīkla savienojums."</string>
<string name="tts_default_sample_string" msgid="6388016028292967973">"Šis ir runas sintēzes piemērs."</string>
<string name="tts_status_title" msgid="8190784181389278640">"Noklusējuma valodas statuss"</string>
@@ -554,6 +554,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Atspējots"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Iespējots"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Lai šīs izmaiņas tiktu piemērotas, nepieciešama ierīces atkārtota palaišana. Atkārtoti palaidiet to tūlīt vai atceliet izmaiņas."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Darbā: <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index e0cc38d..d55f1af 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Оневозможено"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Овозможено"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"За да се примени променава, уредот мора да се рестартира. Рестартирајте сега или откажете."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Работна <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index 1a3f068..c5267d6 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -549,14 +549,11 @@
<string name="guest_new_guest" msgid="3482026122932643557">"അതിഥിയെ ചേർക്കുക"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"അതിഥിയെ നീക്കം ചെയ്യുക"</string>
<string name="guest_nickname" msgid="6332276931583337261">"അതിഥി"</string>
- <!-- no translation found for cached_apps_freezer_device_default (2616594131750144342) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_disabled (4816382260660472042) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_enabled (8866703500183051546) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_reboot_dialog_text (695330563489230096) -->
- <skip />
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"ഉപകരണത്തിന്റെ ഡിഫോൾട്ട് പ്രവർത്തനം"</string>
+ <string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"പ്രവർത്തനരഹിതമാക്കി"</string>
+ <string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"പ്രവർത്തനക്ഷമമാക്കി"</string>
+ <string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ഈ മാറ്റം ബാധകമാകുന്നതിന് നിങ്ങളുടെ ഉപകരണം റീബൂട്ട് ചെയ്യേണ്ടതുണ്ട്. ഇപ്പോൾ റീബൂട്ട് ചെയ്യുകയോ റദ്ദാക്കുകയോ ചെയ്യുക."</string>
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"ഔദ്യോഗികം <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index a342862..0a01f84 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -419,7 +419,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномаль (улаан-ногоон)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомаль (цэнхэр-шар)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Өнгө тохируулах"</string>
- <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1284746051652993443">"Өнгө залруулга нь төхөөрөмж дээрээ өнгийг хэрхэн үзүүлэхийг тохируулах боломжийг танд олгодог"</string>
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1284746051652993443">"Өнгө тохируулга нь танд төхөөрөмж дээрээ өнгө хэрхэн харагдахыг тохируулах боломжийг олгодог"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Давхарласан <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"Ойролцоогоор <xliff:g id="TIME_REMAINING">%1$s</xliff:g> үлдсэн"</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Идэвхгүй болгосон"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Идэвхжүүлсэн"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Энэ өөрчлөлтийг хэрэгжүүлэхийн тулд таны төхөөрөмжийг дахин асаах ёстой. Одоо дахин асаах эсвэл болино уу."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Ажлын <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 0895cd4..075c748 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -243,7 +243,7 @@
<string name="oem_unlock_enable" msgid="5334869171871566731">"OEM अनलॉक करणे"</string>
<string name="oem_unlock_enable_summary" msgid="5857388174390953829">"बूटलोडर अनलॉक करण्यासाठी अनुमती द्या"</string>
<string name="confirm_enable_oem_unlock_title" msgid="8249318129774367535">"OEM अनलॉक करण्यास अनुमती द्यायची?"</string>
- <string name="confirm_enable_oem_unlock_text" msgid="854131050791011970">"चेतावणी: हे सेटिंग चालू असताना या डिव्हाइस वर डिव्हाइस संरक्षण वैशिष्ट्ये काम करणार नाहीत."</string>
+ <string name="confirm_enable_oem_unlock_text" msgid="854131050791011970">"चेतावणी: हे सेटिंग सुरू असताना या डिव्हाइस वर डिव्हाइस संरक्षण वैशिष्ट्ये काम करणार नाहीत."</string>
<string name="mock_location_app" msgid="6269380172542248304">"बनावट स्थान अॅप निवडा"</string>
<string name="mock_location_app_not_set" msgid="6972032787262831155">"कोणताही बनावट स्थान अॅप सेट केला नाही"</string>
<string name="mock_location_app_set" msgid="4706722469342913843">"बनावट स्थान अॅप: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
@@ -298,7 +298,7 @@
<string name="allow_mock_location" msgid="2102650981552527884">"बनावट स्थानांना अनुमती द्या"</string>
<string name="allow_mock_location_summary" msgid="179780881081354579">"बनावट स्थानांना अनुमती द्या"</string>
<string name="debug_view_attributes" msgid="3539609843984208216">"दृश्य विशेषता तपासणी सुरू करा"</string>
- <string name="mobile_data_always_on_summary" msgid="1112156365594371019">"वाय-फाय चालू असतानाही मोबाइल डेटा नेहमी सुरू ठेवा (नेटवर्क जलदरीत्या स्विच करण्यासाठी)."</string>
+ <string name="mobile_data_always_on_summary" msgid="1112156365594371019">"वाय-फाय सुरू असतानाही मोबाइल डेटा नेहमी सुरू ठेवा (नेटवर्क जलदरीत्या स्विच करण्यासाठी)."</string>
<string name="tethering_hardware_offload_summary" msgid="7801345335142803029">"उपलब्ध असल्यास टेदरिंग हार्डवेअर ॲक्सिलरेशन वापरा"</string>
<string name="adb_warning_title" msgid="7708653449506485728">"USB डीबग करण्यास अनुमती द्यायची?"</string>
<string name="adb_warning_message" msgid="8145270656419669221">"USB डीबग करण्याचा हेतू फक्त विकास उद्देशांसाठी आहे. याचा वापर तुमचा कॉंप्युटर आणि तुमचे डिव्हाइस यांच्या दरम्यान डेटा कॉपी करण्यासाठी करा, सूचनेशिवाय तुमच्या डिव्हाइस वर अॅप्स इंस्टॉल करा आणि लॉग डेटा वाचा."</string>
@@ -409,7 +409,7 @@
<string name="convert_to_file_encryption_enabled" msgid="840757431284311754">"रूपांतरित करा..."</string>
<string name="convert_to_file_encryption_done" msgid="8965831011811180627">"फाईल आधीपासून एंक्रिप्ट होती"</string>
<string name="title_convert_fbe" msgid="5780013350366495149">"फाईल आधारित कूटबद्धीकरणावर रूपांतरित करणे"</string>
- <string name="convert_to_fbe_warning" msgid="34294381569282109">"फाईल आधारित कूटबद्धीकरणावर डेटा विभाजक रूपांतरित करा.\n !!चेतावणी!! हे आपल्या सर्व डेटास मिटवेल.\n हे वैशिष्ट्य अल्फा आहे आणि कदाचित योग्यरित्या कार्य करू शकत नाही.\n सुरु ठेवण्यासाठी \'पुसा आणि रूपांतरित करा...\' दाबा."</string>
+ <string name="convert_to_fbe_warning" msgid="34294381569282109">"फाईल आधारित कूटबद्धीकरणावर डेटा विभाजक रूपांतरित करा.\n !!चेतावणी!! हे आपल्या सर्व डेटास मिटवेल.\n हे वैशिष्ट्य अल्फा आहे आणि कदाचित योग्यरित्या कार्य करू शकत नाही.\n सुरू ठेवण्यासाठी \'पुसा आणि रूपांतरित करा...\' दाबा."</string>
<string name="button_convert_fbe" msgid="1159861795137727671">"पुसा आणि रुपांतरित करा..."</string>
<string name="picture_color_mode" msgid="1013807330552931903">"चित्र रंग मोड"</string>
<string name="picture_color_mode_desc" msgid="151780973768136200">"sRGB वापरा"</string>
@@ -495,8 +495,8 @@
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"कमी वेळ."</string>
<string name="cancel" msgid="5665114069455378395">"रद्द करा"</string>
<string name="okay" msgid="949938843324579502">"ठीक आहे"</string>
- <string name="zen_mode_enable_dialog_turn_on" msgid="6418297231575050426">"चालू करा"</string>
- <string name="zen_mode_settings_turn_on_dialog_title" msgid="2760567063190790696">"व्यत्यय आणू नका चालू करा"</string>
+ <string name="zen_mode_enable_dialog_turn_on" msgid="6418297231575050426">"सुरू करा"</string>
+ <string name="zen_mode_settings_turn_on_dialog_title" msgid="2760567063190790696">"व्यत्यय आणू नका सुरू करा"</string>
<string name="zen_mode_settings_summary_off" msgid="3832876036123504076">"कधीही नाही"</string>
<string name="zen_interruption_level_priority" msgid="5392140786447823299">"केवळ प्राधान्य"</string>
<string name="zen_mode_and_condition" msgid="8877086090066332516">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
@@ -549,14 +549,11 @@
<string name="guest_new_guest" msgid="3482026122932643557">"अतिथी जोडा"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"अतिथी काढून टाका"</string>
<string name="guest_nickname" msgid="6332276931583337261">"अतिथी"</string>
- <!-- no translation found for cached_apps_freezer_device_default (2616594131750144342) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_disabled (4816382260660472042) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_enabled (8866703500183051546) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_reboot_dialog_text (695330563489230096) -->
- <skip />
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"डिव्हाइस डीफॉल्ट"</string>
+ <string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"बंद केले आहे"</string>
+ <string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"सुरू केले आहे"</string>
+ <string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"हा बदल लागू करण्यासाठी तुमचे डिव्हाइस रीबूट करणे आवश्यक आहे. आता रीबूट करा किंवा रद्द करा."</string>
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"कार्य <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index a1c6782..cdf32a7 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Dilumpuhkan"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Didayakan"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Peranti anda mesti dibut semula supaya perubahan ini berlaku. But semula sekarang atau batalkan."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Kerja <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 0edc100..2bd3b45 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -153,7 +153,7 @@
<string name="unknown" msgid="3544487229740637809">"မသိ"</string>
<string name="running_process_item_user_label" msgid="3988506293099805796">"အသုံးပြုသူ- <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
<string name="launch_defaults_some" msgid="3631650616557252926">"မူရင်းအချို့ သတ်မှတ်ပြီး"</string>
- <string name="launch_defaults_none" msgid="8049374306261262709">"ပုံမှန်သတ်မှတ်ထားခြင်းမရှိ"</string>
+ <string name="launch_defaults_none" msgid="8049374306261262709">"မူရင်း သတ်မှတ်မထားပါ။"</string>
<string name="tts_settings" msgid="8130616705989351312">"စာသားမှစကားပြောပြောင်း ဆက်တင်များ"</string>
<string name="tts_settings_title" msgid="7602210956640483039">"စာသားမှ စကားပြောသို့ အထွက်"</string>
<string name="tts_default_rate_title" msgid="3964187817364304022">"စကားပြောနှုန်း"</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"ပိတ်ထားသည်"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ဖွင့်ထားသည်"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ဤအပြောင်းအလဲ ထည့်သွင်းရန် သင့်စက်ကို ပြန်လည်စတင်ရမည်။ ယခု ပြန်လည်စတင်ပါ သို့မဟုတ် ပယ်ဖျက်ပါ။"</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"အလုပ် <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index d683070..de5bed6 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Slått av"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Slått på"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Enheten din må startes på nytt for at denne endringen skal tre i kraft. Start på nytt nå eller avbryt."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Jobb-<xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index ee22c60..bfe295a 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -399,7 +399,7 @@
<string name="inactive_apps_title" msgid="5372523625297212320">"स्ट्यान्डबाई एपहरू"</string>
<string name="inactive_app_inactive_summary" msgid="3161222402614236260">"निष्क्रिय। टगल गर्न ट्याप गर्नुहोस्।"</string>
<string name="inactive_app_active_summary" msgid="8047630990208722344">"सक्रिय। टगल गर्न ट्याप गर्नुहोस्।"</string>
- <string name="standby_bucket_summary" msgid="5128193447550429600">"अनुप्रयोगको स्ट्यान्डबाई अवस्था:<xliff:g id="BUCKET"> %s</xliff:g>"</string>
+ <string name="standby_bucket_summary" msgid="5128193447550429600">"एपको स्ट्यान्डबाई अवस्था:<xliff:g id="BUCKET"> %s</xliff:g>"</string>
<string name="runningservices_settings_title" msgid="6460099290493086515">"चलिरहेका सेवाहरू"</string>
<string name="runningservices_settings_summary" msgid="1046080643262665743">"हाल चालु भइरहेका सेवाहरू हेर्नुहोस् र नियन्त्रण गर्नुहोस्"</string>
<string name="select_webview_provider_title" msgid="3917815648099445503">"WebView कार्यान्वयन"</string>
@@ -549,14 +549,11 @@
<string name="guest_new_guest" msgid="3482026122932643557">"अतिथि थप्नुहोस्"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"अतिथि हटाउनुहोस्"</string>
<string name="guest_nickname" msgid="6332276931583337261">"अतिथि"</string>
- <!-- no translation found for cached_apps_freezer_device_default (2616594131750144342) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_disabled (4816382260660472042) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_enabled (8866703500183051546) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_reboot_dialog_text (695330563489230096) -->
- <skip />
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"पूर्वनिर्धारित यन्त्र"</string>
+ <string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"असक्षम पारिएको छ"</string>
+ <string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"सक्षम पारिएको छ"</string>
+ <string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"यो परिवर्तन लागू गर्न तपाईंको यन्त्र अनिवार्य रूपमा रिबुट गर्नु पर्छ। अहिले रिबुट गर्नुहोस् वा रद्द गर्नुहोस्।"</string>
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"कार्यालयको प्रोफाइल <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index dc5f8f2..a822719 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -554,4 +554,6 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Ingeschakeld"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Je apparaat moet opnieuw worden opgestart om deze wijziging toe te passen. Start nu opnieuw op of annuleer de wijziging."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> voor werk"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 18cc9e3..ab9fbc3 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -235,7 +235,7 @@
<string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"ଦୟାକରି ଏକ ୱାଇ-ଫାଇ ନେଟୱାର୍କରେ ସଂଯୋଗ କରନ୍ତୁ"</string>
<string name="keywords_adb_wireless" msgid="6507505581882171240">"adb, ଡିବଗ୍, dev"</string>
<string name="bugreport_in_power" msgid="8664089072534638709">"ବଗ୍ ରିପୋର୍ଟ ସର୍ଟକଟ୍"</string>
- <string name="bugreport_in_power_summary" msgid="1885529649381831775">"ବଗ୍ ରିପୋର୍ଟ ଦେବାପାଇଁ ପାୱାର୍ ମେନୁରେ ଏକ ବଟନ୍ ଦେଖନ୍ତୁ"</string>
+ <string name="bugreport_in_power_summary" msgid="1885529649381831775">"ବଗ୍ ରିପୋର୍ଟ ଦେବା ପାଇଁ ପାୱାର୍ ମେନୁରେ ଏକ ବଟନ୍ ଦେଖାନ୍ତୁ"</string>
<string name="keep_screen_on" msgid="1187161672348797558">"ଜାଗ୍ରତ ରଖନ୍ତୁ"</string>
<string name="keep_screen_on_summary" msgid="1510731514101925829">"ଚାର୍ଜ ହେବାବେଳେ ସ୍କ୍ରୀନ୍ ଆଦୌ ବନ୍ଦ ହେବନାହିଁ"</string>
<string name="bt_hci_snoop_log" msgid="7291287955649081448">"ବ୍ଲୁଟୂଥ୍ HCI ସ୍ନୁପ୍ ଲଗ୍ ସକ୍ଷମ କରନ୍ତୁ"</string>
@@ -245,7 +245,7 @@
<string name="confirm_enable_oem_unlock_title" msgid="8249318129774367535">"OEM ଅନଲକ୍ କରିବା ଅନୁମତି ଦେବେ?"</string>
<string name="confirm_enable_oem_unlock_text" msgid="854131050791011970">"ଚେତାବନୀ: ଏହି ସେଟିଙ୍ଗ ଚାଲୁ ଥିବାବେଳେ ଡିଭାଇସ୍ର ସୁରକ୍ଷା ବୈଶିଷ୍ଟ୍ୟ କାମ କରିବ ନାହିଁ"</string>
<string name="mock_location_app" msgid="6269380172542248304">"ମକ୍ ଲୋକେସନ୍ ଆପ୍ର ଚୟନ କରନ୍ତୁ"</string>
- <string name="mock_location_app_not_set" msgid="6972032787262831155">"କୌଣସି ନକଲି ଲୋକେଶନ ଆପ୍ ସେଟ୍ କରାଯାଇନାହିଁ"</string>
+ <string name="mock_location_app_not_set" msgid="6972032787262831155">"କୌଣସି ମକ୍ ଲୋକେସନ ଆପ୍ ସେଟ୍ କରାଯାଇନାହିଁ"</string>
<string name="mock_location_app_set" msgid="4706722469342913843">"ମକ୍ ଲୋକେସନ୍ ଆପ୍: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="debug_networking_category" msgid="6829757985772659599">"ନେଟ୍ୱର୍କିଙ୍ଗ"</string>
<string name="wifi_display_certification" msgid="1805579519992520381">"ୱାୟରଲେସ୍ ଡିସ୍ପ୍ଲେ ସାର୍ଟିଫିକେସନ୍"</string>
@@ -549,14 +549,11 @@
<string name="guest_new_guest" msgid="3482026122932643557">"ଅତିଥି ଯୋଗ କରନ୍ତୁ"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"ଅତିଥିଙ୍କୁ କାଢ଼ି ଦିଅନ୍ତୁ"</string>
<string name="guest_nickname" msgid="6332276931583337261">"ଅତିଥି"</string>
- <!-- no translation found for cached_apps_freezer_device_default (2616594131750144342) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_disabled (4816382260660472042) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_enabled (8866703500183051546) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_reboot_dialog_text (695330563489230096) -->
- <skip />
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"ଡିଭାଇସ୍ ଡିଫଲ୍ଟ"</string>
+ <string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"ଅକ୍ଷମ କରାଯାଇଛି"</string>
+ <string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ସକ୍ଷମ କରାଯାଇଛି"</string>
+ <string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ଏହି ପରିବର୍ତ୍ତନ ଲାଗୁ କରିବା ପାଇଁ ଆପଣଙ୍କ ଡିଭାଇସକୁ ନିଶ୍ଚିତ ରୂପେ ରିବୁଟ୍ କରାଯିବା ଆବଶ୍ୟକ। ବର୍ତ୍ତମାନ ରିବୁଟ୍ କରନ୍ତୁ କିମ୍ବା ବାତିଲ୍ କରନ୍ତୁ।"</string>
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"ୱାର୍କ <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index b502f3f..35d8cba 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -549,14 +549,11 @@
<string name="guest_new_guest" msgid="3482026122932643557">"ਮਹਿਮਾਨ ਸ਼ਾਮਲ ਕਰੋ"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"ਮਹਿਮਾਨ ਹਟਾਓ"</string>
<string name="guest_nickname" msgid="6332276931583337261">"ਮਹਿਮਾਨ"</string>
- <!-- no translation found for cached_apps_freezer_device_default (2616594131750144342) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_disabled (4816382260660472042) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_enabled (8866703500183051546) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_reboot_dialog_text (695330563489230096) -->
- <skip />
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"ਪੂਰਵ-ਨਿਰਧਾਰਤ ਡੀਵਾਈਸ"</string>
+ <string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"ਬੰਦ ਕੀਤਾ ਗਿਆ"</string>
+ <string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ਚਾਲੂ ਕੀਤਾ ਗਿਆ"</string>
+ <string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ਇਸ ਤਬਦੀਲੀ ਨੂੰ ਲਾਗੂ ਕਰਨ ਲਈ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਨੂੰ ਰੀਬੂਟ ਕਰਨਾ ਲਾਜ਼ਮੀ ਹੈ। ਹੁਣੇ ਰੀਬੂਟ ਕਰੋ ਜਾਂ ਰੱਦ ਕਰੋ।"</string>
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"ਕੰਮ <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index 8a29bf0..54ed131 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -195,7 +195,7 @@
</string-array>
<string name="choose_profile" msgid="343803890897657450">"Wybierz profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Osobiste"</string>
- <string name="category_work" msgid="4014193632325996115">"Do pracy"</string>
+ <string name="category_work" msgid="4014193632325996115">"Służbowe"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opcje programistyczne"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Włącz opcje dla programistów"</string>
<string name="development_settings_summary" msgid="8718917813868735095">"Ustaw opcje związane z programowaniem aplikacji."</string>
@@ -555,6 +555,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Wyłączono"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Włączono"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Wprowadzenie zmiany wymaga ponownego uruchomienia urządzenia. Uruchom ponownie teraz lub anuluj."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> (do pracy)"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 58a13cd..c6dc1d3 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -61,7 +61,7 @@
<string name="speed_label_medium" msgid="9078405312828606976">"Média"</string>
<string name="speed_label_fast" msgid="2677719134596044051">"Rápida"</string>
<string name="speed_label_very_fast" msgid="8215718029533182439">"Muito rápida"</string>
- <string name="wifi_passpoint_expired" msgid="6540867261754427561">"Expirado"</string>
+ <string name="wifi_passpoint_expired" msgid="6540867261754427561">"Expirada"</string>
<string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
<string name="bluetooth_disconnected" msgid="7739366554710388701">"Desconectado"</string>
<string name="bluetooth_disconnecting" msgid="7638892134401574338">"Desconectando…"</string>
@@ -554,4 +554,6 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Ativado"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"É necessário reinicializar o dispositivo para que a mudança seja aplicada. Faça isso agora ou cancele."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"App <xliff:g id="APP_NAME">%s</xliff:g> de trabalho"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index ca85151..999e684 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -212,9 +212,9 @@
<string name="adb_wireless_settings" msgid="2295017847215680229">"Depuração sem fios"</string>
<string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Para ver e utilizar dispositivos disponíveis, ative a depuração sem fios."</string>
<string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"Sincronize o dispositivo com o código QR"</string>
- <string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"Sincronize novos dispositivos com o leitor de códigos QR."</string>
+ <string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"Sincroniza novos dispositivos com o leitor de códigos QR."</string>
<string name="adb_pair_method_code_title" msgid="1122590300445142904">"Sincronize dispositivo com código de sincronização"</string>
- <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"Sincronize novos dispositivos através do código de seis dígitos."</string>
+ <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"Sincroniza novos dispositivos através do código de seis dígitos."</string>
<string name="adb_paired_devices_title" msgid="5268997341526217362">"Dispositivos sincronizados"</string>
<string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"Atualmente ligado."</string>
<string name="adb_wireless_device_details_title" msgid="7129369670526565786">"Detalhes do dispositivo"</string>
@@ -554,4 +554,6 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Ativada"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"É necessário reiniciar o dispositivo para aplicar esta alteração. Reinicie agora ou cancele."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> de trabalho"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 58a13cd..c6dc1d3 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -61,7 +61,7 @@
<string name="speed_label_medium" msgid="9078405312828606976">"Média"</string>
<string name="speed_label_fast" msgid="2677719134596044051">"Rápida"</string>
<string name="speed_label_very_fast" msgid="8215718029533182439">"Muito rápida"</string>
- <string name="wifi_passpoint_expired" msgid="6540867261754427561">"Expirado"</string>
+ <string name="wifi_passpoint_expired" msgid="6540867261754427561">"Expirada"</string>
<string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
<string name="bluetooth_disconnected" msgid="7739366554710388701">"Desconectado"</string>
<string name="bluetooth_disconnecting" msgid="7638892134401574338">"Desconectando…"</string>
@@ -554,4 +554,6 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Ativado"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"É necessário reinicializar o dispositivo para que a mudança seja aplicada. Faça isso agora ou cancele."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"App <xliff:g id="APP_NAME">%s</xliff:g> de trabalho"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index f967c8c..ba2f36f 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -554,6 +554,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Dezactivat"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activat"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Pentru ca modificarea să se aplice, trebuie să reporniți dispozitivul. Reporniți-l acum sau anulați."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> de serviciu"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index e41b3b7..8ec3875 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -210,7 +210,7 @@
<string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Режим отладки при подключении к сети Wi‑Fi"</string>
<string name="adb_wireless_error" msgid="721958772149779856">"Ошибка"</string>
<string name="adb_wireless_settings" msgid="2295017847215680229">"Отладка по Wi-Fi"</string>
- <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Чтобы посмотреть и использовать доступные устройства, включите отладку по Wi-Fi"</string>
+ <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Чтобы увидеть и использовать доступные устройства, включите отладку по Wi-Fi."</string>
<string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"Подключить устройство с помощью QR-кода"</string>
<string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"Подключение новых устройств с помощью сканера QR-кодов"</string>
<string name="adb_pair_method_code_title" msgid="1122590300445142904">"Подключить устройство с помощью кода подключения"</string>
@@ -223,7 +223,7 @@
<string name="adb_wireless_connection_failed_title" msgid="664211177427438438">"Не удалось установить подключение"</string>
<string name="adb_wireless_connection_failed_message" msgid="9213896700171602073">"Убедитесь, что устройство \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\" подключено к нужной сети."</string>
<string name="adb_pairing_device_dialog_title" msgid="7141739231018530210">"Подключение к устройству"</string>
- <string name="adb_pairing_device_dialog_pairing_code_label" msgid="3639239786669722731">"Код подключения к сети Wi‑Fi"</string>
+ <string name="adb_pairing_device_dialog_pairing_code_label" msgid="3639239786669722731">"Код подключения по сети Wi‑Fi"</string>
<string name="adb_pairing_device_dialog_failed_title" msgid="3426758947882091735">"Не удалось подключить устройство"</string>
<string name="adb_pairing_device_dialog_failed_msg" msgid="6611097519661997148">"Устройство должно быть подключено к той же самой сети."</string>
<string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"Подключение устройства через Wi‑Fi с использованием QR-кода"</string>
@@ -555,6 +555,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Отключено"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Включено"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Чтобы изменение вступило в силу, необходимо перезапустить устройство. Вы можете сделать это сейчас или позже."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Рабочее приложение \"<xliff:g id="APP_NAME">%s</xliff:g>\""</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 5710985..01c3634 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"අබල කළා"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"සබලයි"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"මෙම වෙනස යෙදීමට ඔබේ උපාංගය නැවත පණ ගැන්විය යුතුය. දැන් නැවත පණ ගන්වන්න හෝ අවලංගු කරන්න."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"කාර්යාල <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index b3848a9..b5cbf43 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -61,7 +61,7 @@
<string name="speed_label_medium" msgid="9078405312828606976">"Stredná"</string>
<string name="speed_label_fast" msgid="2677719134596044051">"Rýchla"</string>
<string name="speed_label_very_fast" msgid="8215718029533182439">"Veľmi rýchla"</string>
- <string name="wifi_passpoint_expired" msgid="6540867261754427561">"Platnosť vypršala"</string>
+ <string name="wifi_passpoint_expired" msgid="6540867261754427561">"Vypršalo"</string>
<string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
<string name="bluetooth_disconnected" msgid="7739366554710388701">"Odpojený"</string>
<string name="bluetooth_disconnecting" msgid="7638892134401574338">"Prebieha odpájanie..."</string>
@@ -195,7 +195,7 @@
</string-array>
<string name="choose_profile" msgid="343803890897657450">"Výber profilu"</string>
<string name="category_personal" msgid="6236798763159385225">"Osobné"</string>
- <string name="category_work" msgid="4014193632325996115">"Práca"</string>
+ <string name="category_work" msgid="4014193632325996115">"Pracovné"</string>
<string name="development_settings_title" msgid="140296922921597393">"Pre vývojárov"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Povolenie možností vývojára"</string>
<string name="development_settings_summary" msgid="8718917813868735095">"Možnosti nastavenia vývoja aplikácií"</string>
@@ -508,7 +508,7 @@
<string name="alarm_template_far" msgid="6382760514842998629">"o <xliff:g id="WHEN">%1$s</xliff:g>"</string>
<string name="zen_mode_duration_settings_title" msgid="1553451650289651489">"Trvanie"</string>
<string name="zen_mode_duration_always_prompt_title" msgid="3212996860498119555">"Vždy sa opýtať"</string>
- <string name="zen_mode_forever" msgid="3339224497605461291">"Dokiaľ túto funkciu nevypnete"</string>
+ <string name="zen_mode_forever" msgid="3339224497605461291">"Dokým funkciu nevypnete"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Teraz"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Reproduktor telefónu"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Pri pripájaní sa vyskytol problém. Zariadenie vypnite a znova zapnite."</string>
@@ -555,6 +555,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Vypnuté"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Zapnuté"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Táto zmena sa uplatní až po reštartovaní zariadenia. Zariadenie reštartujte alebo zmenu zrušte."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Pracovná aplikácia <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 658c192..86cccb8 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -214,7 +214,7 @@
<string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"Seznanjanje naprave s kodo QR"</string>
<string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"Seznanitev novih naprav z optičnim bralnikom kod QR"</string>
<string name="adb_pair_method_code_title" msgid="1122590300445142904">"Seznanjanje naprave s kodo za seznanjanje"</string>
- <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"Seznanjanje novih naprav s šestmestno kodo"</string>
+ <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"Seznanitev novih naprav s šestmestno kodo"</string>
<string name="adb_paired_devices_title" msgid="5268997341526217362">"Seznanjene naprave"</string>
<string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"Trenutno povezano"</string>
<string name="adb_wireless_device_details_title" msgid="7129369670526565786">"Podrobnosti o napravi"</string>
@@ -509,7 +509,7 @@
<string name="zen_mode_duration_settings_title" msgid="1553451650289651489">"Trajanje"</string>
<string name="zen_mode_duration_always_prompt_title" msgid="3212996860498119555">"Vedno vprašaj"</string>
<string name="zen_mode_forever" msgid="3339224497605461291">"Dokler ne izklopite"</string>
- <string name="time_unit_just_now" msgid="3006134267292728099">"pravkar"</string>
+ <string name="time_unit_just_now" msgid="3006134267292728099">"Pravkar"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Zvočnik telefona"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Težava pri povezovanju. Napravo izklopite in znova vklopite."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Žična zvočna naprava"</string>
@@ -556,4 +556,6 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Omogočeno"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Napravo je treba znova zagnati, da bo ta sprememba uveljavljena. Znova zaženite zdaj ali prekličite."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> za delo"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
+ <skip />
</resources>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index 17d2d50..bd3353c 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -207,7 +207,7 @@
<string name="enable_adb_summary" msgid="3711526030096574316">"Korrigjo gabimet e modalitetit kur UBS-ja është e lidhur"</string>
<string name="clear_adb_keys" msgid="3010148733140369917">"Anulo autorizimet e korrigjimeve të gabimeve të USB-së"</string>
<string name="enable_adb_wireless" msgid="6973226350963971018">"Korrigjimi përmes Wi-Fi"</string>
- <string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Regjimi i korrigjimit kur Wi‑Fi është i lidhur"</string>
+ <string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Modaliteti i korrigjimit kur Wi‑Fi është i lidhur"</string>
<string name="adb_wireless_error" msgid="721958772149779856">"Gabim"</string>
<string name="adb_wireless_settings" msgid="2295017847215680229">"Korrigjimi përmes Wi-Fi"</string>
<string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Për të parë dhe përdorur pajisjet e disponueshme, aktivizo korrigjimin përmes Wi-Fi"</string>
@@ -446,7 +446,7 @@
<string name="power_remaining_duration_shutdown_imminent" product="device" msgid="4374784375644214578">"Pajisja mund të fiket së shpejti (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
<string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_remaining_charging_duration_only" msgid="7415639699283965818">"<xliff:g id="TIME">%1$s</xliff:g> të mbetura deri në karikim"</string>
- <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> deri sa të karikohen"</string>
+ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> derisa të karikohet"</string>
<string name="battery_info_status_unknown" msgid="268625384868401114">"I panjohur"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Po karikohet"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Po ngarkon me shpejtësi"</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Joaktiv"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktiv"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Pajisja jote duhet të riniset që ky ndryshim të zbatohet. Rinise tani ose anuloje."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> për punën"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index e0a7d0b..29f23b4 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -554,6 +554,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Онемогућено"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Омогућено"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Морате да рестартујете уређај да би се ова промена применила. Рестартујте га одмах или откажите."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> за посао"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index f6745b2..a40100e 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -183,19 +183,19 @@
<string name="tts_reset_speech_pitch_title" msgid="7149398585468413246">"Återställ tonhöjden för tal"</string>
<string name="tts_reset_speech_pitch_summary" msgid="6822904157021406449">"Återställ tonhöjden för talad text till standardinställningen."</string>
<string-array name="tts_rate_entries">
- <item msgid="9004239613505400644">"Mycket långsamt"</item>
- <item msgid="1815382991399815061">"Långsamt"</item>
- <item msgid="3075292553049300105">"Normalt"</item>
- <item msgid="1158955023692670059">"Snabbt"</item>
+ <item msgid="9004239613505400644">"Mycket långsam"</item>
+ <item msgid="1815382991399815061">"Långsam"</item>
+ <item msgid="3075292553049300105">"Normal"</item>
+ <item msgid="1158955023692670059">"Snabb"</item>
<item msgid="5664310435707146591">"Snabbare"</item>
- <item msgid="5491266922147715962">"Mycket snabbt"</item>
- <item msgid="7659240015901486196">"Supersnabbt"</item>
- <item msgid="7147051179282410945">"Turbosnabbt"</item>
+ <item msgid="5491266922147715962">"Mycket snabb"</item>
+ <item msgid="7659240015901486196">"Supersnabb"</item>
+ <item msgid="7147051179282410945">"Turbosnabb"</item>
<item msgid="581904787661470707">"Snabbast"</item>
</string-array>
<string name="choose_profile" msgid="343803890897657450">"Välj profil"</string>
- <string name="category_personal" msgid="6236798763159385225">"Personligt"</string>
- <string name="category_work" msgid="4014193632325996115">"Arbetet"</string>
+ <string name="category_personal" msgid="6236798763159385225">"Privat"</string>
+ <string name="category_work" msgid="4014193632325996115">"Jobb"</string>
<string name="development_settings_title" msgid="140296922921597393">"Utvecklaralternativ"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Aktivera utvecklaralternativ"</string>
<string name="development_settings_summary" msgid="8718917813868735095">"Ange alternativ för apputveckling"</string>
@@ -213,9 +213,9 @@
<string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Aktivera trådlös felsökning om du vill se tillgängliga enheter"</string>
<string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"Parkoppla enheten med en QR-kod"</string>
<string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"Parkoppla nya enheter med QR-kodsläsare"</string>
- <string name="adb_pair_method_code_title" msgid="1122590300445142904">"Koppla enheten med en kopplingskod"</string>
- <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"Koppla nya enheter med en sexsiffrig kod"</string>
- <string name="adb_paired_devices_title" msgid="5268997341526217362">"Kopplade enheter"</string>
+ <string name="adb_pair_method_code_title" msgid="1122590300445142904">"Parkoppla enheten med en parkopplingskod"</string>
+ <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"Parkoppla nya enheter med en sexsiffrig kod"</string>
+ <string name="adb_paired_devices_title" msgid="5268997341526217362">"Parkopplade enheter"</string>
<string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"Anslutna just nu"</string>
<string name="adb_wireless_device_details_title" msgid="7129369670526565786">"Enhetsinformation"</string>
<string name="adb_device_forget" msgid="193072400783068417">"Glöm"</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Inaktiverat"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktiverat"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Enheten måste startas om för att ändringen ska börja gälla. Starta om nu eller avbryt."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> för arbetet"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-sw/arrays.xml b/packages/SettingsLib/res/values-sw/arrays.xml
index a29b74e..af39356 100644
--- a/packages/SettingsLib/res/values-sw/arrays.xml
+++ b/packages/SettingsLib/res/values-sw/arrays.xml
@@ -40,7 +40,7 @@
<item msgid="8339720953594087771">"Inaunganisha kwa <xliff:g id="NETWORK_NAME">%1$s</xliff:g>…"</item>
<item msgid="3028983857109369308">"Uhalalishaji kwa <xliff:g id="NETWORK_NAME">%1$s</xliff:g>…"</item>
<item msgid="4287401332778341890">"Inamiliki anwani ya IP kutoka <xliff:g id="NETWORK_NAME">%1$s</xliff:g>…"</item>
- <item msgid="1043944043827424501">" Umeunganishwa kwa<xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</item>
+ <item msgid="1043944043827424501">"Umeunganishwa kwa <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</item>
<item msgid="7445993821842009653">"Imesimamishwa"</item>
<item msgid="1175040558087735707">"inakatisha muunganisho kutoka <xliff:g id="NETWORK_NAME">%1$s</xliff:g>…"</item>
<item msgid="699832486578171722">"Muunganisho Umekatika"</item>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 7df39d5..fedf1c85 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -195,7 +195,7 @@
</string-array>
<string name="choose_profile" msgid="343803890897657450">"Chagua wasifu"</string>
<string name="category_personal" msgid="6236798763159385225">"Ya Kibinafsi"</string>
- <string name="category_work" msgid="4014193632325996115">"Kazini"</string>
+ <string name="category_work" msgid="4014193632325996115">"Ya Kazini"</string>
<string name="development_settings_title" msgid="140296922921597393">"Chaguo za wasanidi"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Washa chaguo za wasanidi programu"</string>
<string name="development_settings_summary" msgid="8718917813868735095">"Weka chaguo kwa ajili ya maendeleo ya programu"</string>
@@ -485,7 +485,7 @@
<string name="ims_reg_title" msgid="8197592958123671062">"Hali ya usajili wa IMS"</string>
<string name="ims_reg_status_registered" msgid="884916398194885457">"Imesajiliwa"</string>
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Haijasajiliwa"</string>
- <string name="status_unavailable" msgid="5279036186589861608">"Haipatikani"</string>
+ <string name="status_unavailable" msgid="5279036186589861608">"Hamna"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"Imechagua anwani ya MAC kwa nasibu"</string>
<plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
<item quantity="other">Imeunganisha vifaa %1$d</item>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Imezimwa"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Imewashwa"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Ni lazima uwashe tena kifaa chako ili mabadiliko haya yatekelezwe. Washa tena sasa au ughairi."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Ya kazini <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index fa922ea..345f0a7 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"முடக்கப்பட்டது"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"இயக்கப்பட்டது"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"இந்த மாற்றங்கள் செயல்படுத்தப்பட உங்கள் சாதனத்தை மறுபடி தொடங்க வேண்டும். இப்போதே மறுபடி தொடங்கவும் அல்லது ரத்துசெய்யவும்."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"பணியிடம் <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 0e36c5f..c4fda01 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -153,9 +153,9 @@
<string name="unknown" msgid="3544487229740637809">"తెలియదు"</string>
<string name="running_process_item_user_label" msgid="3988506293099805796">"వినియోగదారు: <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
<string name="launch_defaults_some" msgid="3631650616557252926">"కొన్ని డిఫాల్ట్లు సెట్ చేయబడ్డాయి"</string>
- <string name="launch_defaults_none" msgid="8049374306261262709">"ఆటోమేటిక్ ఆప్షన్లు వేటినీ సెట్ చేయలేదు"</string>
+ <string name="launch_defaults_none" msgid="8049374306261262709">"ఆటోమేటిక్ ఆప్షన్లు ఏవీ సెట్ చేయలేదు"</string>
<string name="tts_settings" msgid="8130616705989351312">"వచనం నుండి ప్రసంగం సెట్టింగ్లు"</string>
- <string name="tts_settings_title" msgid="7602210956640483039">"వచనం నుండి మాట అవుట్పుట్"</string>
+ <string name="tts_settings_title" msgid="7602210956640483039">"టెక్స్ట్-టు-స్పీచ్ అవుట్పుట్"</string>
<string name="tts_default_rate_title" msgid="3964187817364304022">"ప్రసంగం రేట్"</string>
<string name="tts_default_rate_summary" msgid="3781937042151716987">"వచనాన్ని చదివి వినిపించాల్సిన వేగం"</string>
<string name="tts_default_pitch_title" msgid="6988592215554485479">"పిచ్"</string>
@@ -195,7 +195,7 @@
</string-array>
<string name="choose_profile" msgid="343803890897657450">"ప్రొఫైల్ను ఎంచుకోండి"</string>
<string name="category_personal" msgid="6236798763159385225">"వ్యక్తిగతం"</string>
- <string name="category_work" msgid="4014193632325996115">"కార్యాలయం"</string>
+ <string name="category_work" msgid="4014193632325996115">"ఆఫీస్"</string>
<string name="development_settings_title" msgid="140296922921597393">"డెవలపర్ ఎంపికలు"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"డెవలపర్ ఎంపికలను ప్రారంభించండి"</string>
<string name="development_settings_summary" msgid="8718917813868735095">"అనువర్తన అభివృద్ధి కోసం ఎంపికలను సెట్ చేయండి"</string>
@@ -229,7 +229,7 @@
<string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"QR కోడ్ను స్కాన్ చేయడం ద్వారా Wi-Fiని ఉపయోగించి పరికరాన్ని పెయిర్ చెయ్యండి"</string>
<string name="adb_wireless_verifying_qrcode_text" msgid="6123192424916029207">"పరికరం పెయిర్ చేయబడుతోంది…"</string>
<string name="adb_qrcode_pairing_device_failed_msg" msgid="6936292092592914132">"పరికరాన్ని పెయిర్ చేయడం విఫలమైంది. QR కోడ్ తప్పుగా ఉండడం గాని, లేదా పరికరం అదే నెట్వర్క్కు కనెక్ట్ అయి లేకపోవడం గాని జరిగింది."</string>
- <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP చిరునామా & పోర్ట్"</string>
+ <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP అడ్రస్ & పోర్ట్"</string>
<string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"QR కోడ్ను స్కాన్ చేయండి"</string>
<string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"QR కోడ్ను స్కాన్ చేయడం ద్వారా Wi-Fiని ఉపయోగించి పరికరాన్ని పెయిర్ చెయ్యండి"</string>
<string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"దయచేసి Wi-Fi నెట్వర్క్కు కనెక్ట్ చేయండి"</string>
@@ -418,8 +418,8 @@
<string name="daltonizer_mode_deuteranomaly" msgid="3507284319584683963">"డ్యూటెరానోమలీ (ఎరుపు-ఆకుపచ్చ)"</string>
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"ప్రొటానోమలీ (ఎరుపు-ఆకుపచ్చ రంగు)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ట్రైటనోమలీ (నీలం-పసుపు రంగు)"</string>
- <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"రంగు సవరణ"</string>
- <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1284746051652993443">"రంగులు సరి చేసే ఫీచర్ సాయంతో, మీ పరికరంలో రంగులు కనిపించే పద్ధతిని మీరు సర్దుబాటు చేయగలుగుతారు"</string>
+ <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"కలర్ సరిచేయడం"</string>
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1284746051652993443">"\'కలర్ సరిచేయడం\' అనే ఫీచర్ సాయంతో, మీ పరికరంలో రంగులు కనిపించే పద్ధతిని మీరు మార్చగలుగుతారు"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ద్వారా భర్తీ చేయబడింది"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> సమయం మిగిలి ఉంది"</string>
@@ -533,7 +533,7 @@
<string name="user_add_user_title" msgid="5457079143694924885">"కొత్త వినియోగదారుని జోడించాలా?"</string>
<string name="user_add_user_message_long" msgid="1527434966294733380">"అదనపు యూజర్లను సృష్టించడం ద్వారా మీరు ఈ దేవైజ్ను ఇతరులతో షేర్ చేయవచ్చు. ప్రతి యూజర్కు వారికంటూ ప్రత్యేక స్థలం ఉంటుంది, వారు ఆ స్థలాన్ని యాప్లు, వాల్పేపర్ మొదలైనవాటితో అనుకూలీకరించవచ్చు. యూజర్లు ప్రతి ఒక్కరిపై ప్రభావం చూపే Wi‑Fi వంటి పరికర సెట్టింగ్లను కూడా సర్దుబాటు చేయవచ్చు.\n\nమీరు కొత్త యూజర్ ను జోడించినప్పుడు, ఆ వ్యక్తి వారికంటూ స్వంత స్థలం సెట్ చేసుకోవాలి.\n\nఏ వినియోగదారు అయినా మిగిలిన అందరు యూజర్ల కోసం యాప్లను అప్డేట్ చేయవచ్చు. యాక్సెస్ సామర్ధ్యం సెట్టింగ్లు మరియు సేవలు కొత్త యూజర్కి బదిలీ కాకపోవచ్చు."</string>
<string name="user_add_user_message_short" msgid="3295959985795716166">"మీరు కొత్త వినియోగదారుని జోడించినప్పుడు, ఆ వ్యక్తి తన స్థలాన్ని సెటప్ చేసుకోవాలి.\n\nఏ వినియోగదారు అయినా మిగతా అందరు వినియోగదారుల కోసం యాప్లను అప్డేట్ చేయగలరు."</string>
- <string name="user_setup_dialog_title" msgid="8037342066381939995">"ఇప్పుడు వినియోగదారుని సెటప్ చేయాలా?"</string>
+ <string name="user_setup_dialog_title" msgid="8037342066381939995">"యూజర్ను ఇప్పుడే సెటప్ చేయాలా?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"పరికరాన్ని తీసుకోవడానికి వ్యక్తి అందుబాటులో ఉన్నారని నిర్ధారించుకొని, ఆపై వారికి నిల్వ స్థలాన్ని సెటప్ చేయండి"</string>
<string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"ఇప్పుడు ప్రొఫైల్ను సెటప్ చేయాలా?"</string>
<string name="user_setup_button_setup_now" msgid="1708269547187760639">"ఇప్పుడే సెటప్ చేయి"</string>
@@ -549,14 +549,11 @@
<string name="guest_new_guest" msgid="3482026122932643557">"అతిథిని జోడించండి"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"అతిథిని తీసివేయండి"</string>
<string name="guest_nickname" msgid="6332276931583337261">"అతిథి"</string>
- <!-- no translation found for cached_apps_freezer_device_default (2616594131750144342) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_disabled (4816382260660472042) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_enabled (8866703500183051546) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_reboot_dialog_text (695330563489230096) -->
- <skip />
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"పరికర ఆటోమేటిక్ సెట్టింగ్"</string>
+ <string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"డిజేబుల్ చేయబడింది"</string>
+ <string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ఎనేబుల్ చేయబడింది"</string>
+ <string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ఈ మార్పును వర్తింపజేయాలంటే మీరు మీ పరికరాన్ని తప్పనిసరిగా రీబూట్ చేయాలి. ఇప్పుడే రీబూట్ చేయండి లేదా రద్దు చేయండి."</string>
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"ఆఫీసు <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 0e958e9..a9032eb 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"ปิดใช้"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"เปิดใช้"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"คุณต้องรีบูตอุปกรณ์เพื่อให้การเปลี่ยนแปลงนี้มีผล รีบูตเลยหรือยกเลิก"</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> ในโปรไฟล์งาน"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index 5ce89eb..712c06b 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -433,7 +433,7 @@
<string name="power_discharge_by" msgid="4113180890060388350">"Tatagal dapat nang hanggang humigit-kumulang <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
<string name="power_discharge_by_only" msgid="92545648425937000">"Tatagal hanggang mga <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_discharge_by_only_short" msgid="5883041507426914446">"Hanggang <xliff:g id="TIME">%1$s</xliff:g>"</string>
- <string name="power_suggestion_battery_run_out" msgid="6332089307827787087">"Posibleng maubos ang baterya sa loob ng <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_suggestion_battery_run_out" msgid="6332089307827787087">"Baka maubos ang baterya sa loob ng <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_less_than_duration_only" msgid="8956656616031395152">"Wala nang <xliff:g id="THRESHOLD">%1$s</xliff:g> ang natitira"</string>
<string name="power_remaining_less_than_duration" msgid="318215464914990578">"Wala nang <xliff:g id="THRESHOLD">%1$s</xliff:g> ang natitira (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
<string name="power_remaining_more_than_subtext" msgid="446388082266121894">"Mahigit <xliff:g id="TIME_REMAINING">%1$s</xliff:g> pa ang natitira (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Naka-disable"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Na-enable"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Dapat i-reboot ang iyong device para mailapat ang pagbabagong ito. Mag-reboot ngayon o kanselahin."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> sa Trabaho"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 1e887d1..8818498 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Devre dışı"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Etkin"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Bu değişikliğin geçerli olması için cihazının yeniden başlatılması gerekir. Şimdi yeniden başlatın veya iptal edin."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> (İş)"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index a95105b..f6568e3 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -226,12 +226,12 @@
<string name="adb_pairing_device_dialog_pairing_code_label" msgid="3639239786669722731">"Код підключення Wi‑Fi"</string>
<string name="adb_pairing_device_dialog_failed_title" msgid="3426758947882091735">"Помилка підключення"</string>
<string name="adb_pairing_device_dialog_failed_msg" msgid="6611097519661997148">"Переконайтеся, що пристрій підключено до тієї ж мережі."</string>
- <string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"Підключати пристрій через Wi‑Fi за допомогою QR-коду"</string>
+ <string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"Підключіть пристрій через Wi‑Fi за допомогою QR-коду"</string>
<string name="adb_wireless_verifying_qrcode_text" msgid="6123192424916029207">"Підключення пристрою…"</string>
<string name="adb_qrcode_pairing_device_failed_msg" msgid="6936292092592914132">"Не вдалося підключитися до пристрою. Надано неправильний QR-код або пристрій не підключено до тієї ж мережі."</string>
<string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP-адреса та порт"</string>
- <string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"Сканувати QR-код"</string>
- <string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Підключати пристрій через Wi‑Fi за допомогою QR-коду"</string>
+ <string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"Відскануйте QR-код"</string>
+ <string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Підключіть пристрій через Wi‑Fi за допомогою QR-коду"</string>
<string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"Підключіть пристрій до мережі Wi-Fi"</string>
<string name="keywords_adb_wireless" msgid="6507505581882171240">"adb, налагодження, розробка"</string>
<string name="bugreport_in_power" msgid="8664089072534638709">"Ярлик звіту про помилки"</string>
@@ -555,6 +555,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Вимкнено"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Увімкнено"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Щоб застосувати ці зміни, перезапустіть пристрій. Перезапустіть пристрій або скасуйте зміни."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Робочий додаток <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-ur/arrays.xml b/packages/SettingsLib/res/values-ur/arrays.xml
index e056c1c..3776503 100644
--- a/packages/SettingsLib/res/values-ur/arrays.xml
+++ b/packages/SettingsLib/res/values-ur/arrays.xml
@@ -26,7 +26,7 @@
<item msgid="6050951078202663628">"مربوط ہو رہا ہے…"</item>
<item msgid="8356618438494652335">"توثیق ہو رہی ہے…"</item>
<item msgid="2837871868181677206">"IP پتہ حاصل کر رہا ہے…"</item>
- <item msgid="4613015005934755724">"مربوط ہو گیا"</item>
+ <item msgid="4613015005934755724">"منسلک"</item>
<item msgid="3763530049995655072">"معطل شدہ"</item>
<item msgid="7852381437933824454">"منقطع کیا جارہا ہے…"</item>
<item msgid="5046795712175415059">"منقطع ہو گیا"</item>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index 463c161..f13f8fb 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -433,7 +433,7 @@
<string name="power_discharge_by" msgid="4113180890060388350">"تقریباً <xliff:g id="TIME">%1$s</xliff:g> تک بیٹری چلے گی (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
<string name="power_discharge_by_only" msgid="92545648425937000">"تقریباً <xliff:g id="TIME">%1$s</xliff:g> تک بیٹری چلے گی"</string>
<string name="power_discharge_by_only_short" msgid="5883041507426914446">"<xliff:g id="TIME">%1$s</xliff:g> تک"</string>
- <string name="power_suggestion_battery_run_out" msgid="6332089307827787087">"<xliff:g id="TIME">%1$s</xliff:g> تک بیٹری ختم ہو سکتی ہے"</string>
+ <string name="power_suggestion_battery_run_out" msgid="6332089307827787087">"<xliff:g id="TIME">%1$s</xliff:g> تک بیٹری ختم ہو سکتی ہے"</string>
<string name="power_remaining_less_than_duration_only" msgid="8956656616031395152">"<xliff:g id="THRESHOLD">%1$s</xliff:g> سے کم باقی ہے"</string>
<string name="power_remaining_less_than_duration" msgid="318215464914990578">"<xliff:g id="THRESHOLD">%1$s</xliff:g> سے کم باقی ہے (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
<string name="power_remaining_more_than_subtext" msgid="446388082266121894">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> سے زیادہ باقی ہے (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
@@ -549,14 +549,11 @@
<string name="guest_new_guest" msgid="3482026122932643557">"مہمان کو شامل کریں"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"مہمان کو ہٹائیں"</string>
<string name="guest_nickname" msgid="6332276931583337261">"مہمان"</string>
- <!-- no translation found for cached_apps_freezer_device_default (2616594131750144342) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_disabled (4816382260660472042) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_enabled (8866703500183051546) -->
- <skip />
- <!-- no translation found for cached_apps_freezer_reboot_dialog_text (695330563489230096) -->
- <skip />
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"آلہ ڈیفالٹ"</string>
+ <string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"غیر فعال"</string>
+ <string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"فعال"</string>
+ <string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"اس تبدیلی کو لاگو کرنے کے ليے آپ کے آلہ کو ریبوٹ کرنا ضروری ہے۔ ابھی ریبوٹ کریں یا منسوخ کریں۔"</string>
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"دفتر <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 762a246..7b8f627 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Yoqilmagan"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Yoniq"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Oʻzgarishlar qurilma oʻchib yonganda bajariladi. Hoziroq oʻchib yoqish yoki bekor qilish."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Ish <xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 12241f1..35cecc1 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Đã tắt"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Đã bật"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Bạn phải khởi động lại thiết bị để áp dụng sự thay đổi này. Hãy khởi động lại ngay hoặc hủy."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> dành cho công việc"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 0a3ca12..c69ea2d 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -419,7 +419,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"红色弱视(红绿不分)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"蓝色弱视(蓝黄不分)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"色彩校正"</string>
- <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1284746051652993443">"借助颜色校正功能,您可以调整设备上的颜色显示方式"</string>
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1284746051652993443">"借助色彩校正功能,您可以调整设备上的颜色显示方式"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"已被“<xliff:g id="TITLE">%1$s</xliff:g>”覆盖"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"大约还可使用 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"已停用"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"已启用"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"设备必须重新启动才能应用此更改。您可以立即重新启动或取消。"</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"工作资料中的<xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index 495cc22..b0324d9 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -222,7 +222,7 @@
<string name="adb_device_fingerprint_title_format" msgid="291504822917843701">"裝置指紋:<xliff:g id="FINGERPRINT_PARAM">%1$s</xliff:g>"</string>
<string name="adb_wireless_connection_failed_title" msgid="664211177427438438">"連線失敗"</string>
<string name="adb_wireless_connection_failed_message" msgid="9213896700171602073">"請確認<xliff:g id="DEVICE_NAME">%1$s</xliff:g> 已連線至相同的網絡。"</string>
- <string name="adb_pairing_device_dialog_title" msgid="7141739231018530210">"與裝置配對"</string>
+ <string name="adb_pairing_device_dialog_title" msgid="7141739231018530210">"配對裝置"</string>
<string name="adb_pairing_device_dialog_pairing_code_label" msgid="3639239786669722731">"Wi-Fi 配對碼"</string>
<string name="adb_pairing_device_dialog_failed_title" msgid="3426758947882091735">"配對失敗"</string>
<string name="adb_pairing_device_dialog_failed_msg" msgid="6611097519661997148">"請確認裝置已連線至相同的網絡。"</string>
@@ -452,7 +452,7 @@
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"正在快速充電"</string>
<string name="battery_info_status_charging_slow" msgid="3190803837168962319">"正在慢速充電"</string>
<string name="battery_info_status_discharging" msgid="6962689305413556485">"非充電中"</string>
- <string name="battery_info_status_not_charging" msgid="8330015078868707899">"已插入電源插座,但目前無法充電"</string>
+ <string name="battery_info_status_not_charging" msgid="8330015078868707899">"已連接電源插頭,但目前無法充電"</string>
<string name="battery_info_status_full" msgid="4443168946046847468">"電量已滿"</string>
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"已由管理員停用"</string>
<string name="disabled" msgid="8017887509554714950">"已停用"</string>
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"已停用"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"已啟用"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"您的裝置必須重新開機,才能套用此變更。請立即重新開機或取消。"</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"工作設定檔入面嘅「<xliff:g id="APP_NAME">%s</xliff:g>」"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 9445268..0574c7e 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"已停用"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"已啟用"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"裝置必須重新啟動才能套用這項變更。請立即重新啟動或取消變更。"</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"工作資料夾中的<xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 86698f4..b60553a 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -553,6 +553,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Ikhutshaziwe"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Inikwe amandla"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Kufanele idivayisi yakho iqaliswe ukuze lolu shintsho lusebenze. Qalisa manje noma khansela."</string>
- <!-- no translation found for accessibility_work_profile_app_description (5470883112342119165) -->
+ <string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Umsebenzi we-<xliff:g id="APP_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
<skip />
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/display/BrightnessUtils.java b/packages/SettingsLib/src/com/android/settingslib/display/BrightnessUtils.java
index 57d9594..4f86afaa9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/display/BrightnessUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/display/BrightnessUtils.java
@@ -67,7 +67,7 @@
/**
* Version of {@link #convertGammaToLinear} that takes and returns float values.
- * TODO: brightnessfloat Merge with above method later.
+ * TODO(flc): refactor Android Auto to use float version
*
* @param val The slider value.
* @param min The minimum acceptable value for the setting.
@@ -83,9 +83,13 @@
ret = MathUtils.exp((normalizedVal - C) / A) + B;
}
- // HLG is normalized to the range [0, 12], so we need to re-normalize to the range [0, 1]
+ // HLG is normalized to the range [0, 12], ensure that value is within that range,
+ // it shouldn't be out of bounds.
+ final float normalizedRet = MathUtils.constrain(ret, 0, 12);
+
+ // Re-normalize to the range [0, 1]
// in order to derive the correct setting value.
- return MathUtils.lerp(min, max, ret / 12);
+ return MathUtils.lerp(min, max, normalizedRet / 12);
}
/**
@@ -111,16 +115,7 @@
* @return The corresponding slider value
*/
public static final int convertLinearToGamma(int val, int min, int max) {
- // For some reason, HLG normalizes to the range [0, 12] rather than [0, 1]
- final float normalizedVal = MathUtils.norm(min, max, val) * 12;
- final float ret;
- if (normalizedVal <= 1f) {
- ret = MathUtils.sqrt(normalizedVal) * R;
- } else {
- ret = A * MathUtils.log(normalizedVal - B) + C;
- }
-
- return Math.round(MathUtils.lerp(GAMMA_SPACE_MIN, GAMMA_SPACE_MAX, ret));
+ return convertLinearToGammaFloat((float) val, (float) min, (float) max);
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 9e8c70f..197e34d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -145,15 +145,16 @@
/**
* Connect the MediaDevice to transfer media
* @param connectDevice the MediaDevice
+ * @return {@code true} if successfully call, otherwise return {@code false}
*/
- public void connectDevice(MediaDevice connectDevice) {
+ public boolean connectDevice(MediaDevice connectDevice) {
MediaDevice device = null;
synchronized (mMediaDevicesLock) {
device = getMediaDeviceById(mMediaDevices, connectDevice.getId());
}
if (device == null) {
Log.w(TAG, "connectDevice() connectDevice not in the list!");
- return;
+ return false;
}
if (device instanceof BluetoothMediaDevice) {
final CachedBluetoothDevice cachedDevice =
@@ -162,13 +163,13 @@
mOnTransferBluetoothDevice = connectDevice;
device.setState(MediaDeviceState.STATE_CONNECTING);
cachedDevice.connect();
- return;
+ return true;
}
}
if (device == mCurrentConnectedDevice) {
Log.d(TAG, "connectDevice() this device all ready connected! : " + device.getName());
- return;
+ return false;
}
if (mCurrentConnectedDevice != null) {
@@ -181,6 +182,7 @@
} else {
device.connect();
}
+ return true;
}
void dispatchSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/display/BrightnessUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/display/BrightnessUtilsTest.java
index c0924d9..1b20f2c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/display/BrightnessUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/display/BrightnessUtilsTest.java
@@ -17,6 +17,7 @@
package com.android.settingslib.display;
import static com.android.settingslib.display.BrightnessUtils.GAMMA_SPACE_MAX;
+import static com.android.settingslib.display.BrightnessUtils.GAMMA_SPACE_MIN;
import static com.google.common.truth.Truth.assertThat;
@@ -27,26 +28,40 @@
@RunWith(RobolectricTestRunner.class)
public class BrightnessUtilsTest {
- private static final int MIN = 1;
- private static final int MAX = 255;
+ private static final int MIN_INT = 1;
+ private static final int MAX_INT = 255;
+ private static final float MIN_FLOAT = 0.0f;
+ private static final float MAX_FLOAT = 1.0f;
@Test
- public void linearToGamma_minValue_shouldReturn0() {
- assertThat(BrightnessUtils.convertLinearToGamma(MIN, MIN, MAX)).isEqualTo(0);
+ public void linearToGamma_minValue_shouldReturnMin() {
+ assertThat(BrightnessUtils.convertLinearToGamma(MIN_INT, MIN_INT, MAX_INT))
+ .isEqualTo(GAMMA_SPACE_MIN);
+ assertThat(BrightnessUtils.convertLinearToGammaFloat(MIN_FLOAT, MIN_FLOAT, MAX_FLOAT))
+ .isEqualTo(GAMMA_SPACE_MIN);
}
@Test
public void linearToGamma_maxValue_shouldReturnGammaSpaceMax() {
- assertThat(BrightnessUtils.convertLinearToGamma(MAX, MIN, MAX)).isEqualTo(GAMMA_SPACE_MAX);
+ assertThat(BrightnessUtils.convertLinearToGamma(MAX_INT, MIN_INT, MAX_INT))
+ .isEqualTo(GAMMA_SPACE_MAX);
+ assertThat(BrightnessUtils.convertLinearToGammaFloat(MAX_FLOAT, MIN_FLOAT, MAX_FLOAT))
+ .isEqualTo(GAMMA_SPACE_MAX);
}
@Test
public void gammaToLinear_minValue_shouldReturnMin() {
- assertThat(BrightnessUtils.convertGammaToLinear(MIN, MIN, MAX)).isEqualTo(MIN);
+ assertThat(BrightnessUtils.convertGammaToLinear(GAMMA_SPACE_MIN, MIN_INT, MAX_INT))
+ .isEqualTo(MIN_INT);
+ assertThat(BrightnessUtils.convertGammaToLinearFloat(GAMMA_SPACE_MIN, MIN_FLOAT, MAX_FLOAT))
+ .isEqualTo(MIN_FLOAT);
}
@Test
public void gammaToLinear_gammaSpaceValue_shouldReturnMax() {
- assertThat(BrightnessUtils.convertGammaToLinear(GAMMA_SPACE_MAX, MIN, MAX)).isEqualTo(MAX);
+ assertThat(BrightnessUtils.convertGammaToLinear(GAMMA_SPACE_MAX, MIN_INT, MAX_INT))
+ .isEqualTo(MAX_INT);
+ assertThat(BrightnessUtils.convertGammaToLinearFloat(GAMMA_SPACE_MAX, MIN_FLOAT, MAX_FLOAT))
+ .isEqualTo(MAX_FLOAT);
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 365a16c..517071b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -141,7 +141,7 @@
when(currentDevice.getId()).thenReturn(TEST_CURRENT_DEVICE_ID);
mLocalMediaManager.registerCallback(mCallback);
- mLocalMediaManager.connectDevice(device);
+ assertThat(mLocalMediaManager.connectDevice(device)).isTrue();
verify(currentDevice).disconnect();
verify(device).connect();
@@ -154,7 +154,7 @@
mLocalMediaManager.mCurrentConnectedDevice = mInfoMediaDevice1;
mLocalMediaManager.registerCallback(mCallback);
- mLocalMediaManager.connectDevice(mInfoMediaDevice2);
+ assertThat(mLocalMediaManager.connectDevice(mInfoMediaDevice2)).isTrue();
assertThat(mInfoMediaDevice2.getState()).isEqualTo(LocalMediaManager.MediaDeviceState
.STATE_CONNECTING);
@@ -167,7 +167,7 @@
mLocalMediaManager.mCurrentConnectedDevice = mInfoMediaDevice1;
mLocalMediaManager.registerCallback(mCallback);
- mLocalMediaManager.connectDevice(mInfoMediaDevice1);
+ assertThat(mLocalMediaManager.connectDevice(mInfoMediaDevice1)).isFalse();
assertThat(mInfoMediaDevice1.getState()).isNotEqualTo(LocalMediaManager.MediaDeviceState
.STATE_CONNECTING);
@@ -185,7 +185,7 @@
when(cachedDevice.isBusy()).thenReturn(false);
mLocalMediaManager.registerCallback(mCallback);
- mLocalMediaManager.connectDevice(device);
+ assertThat(mLocalMediaManager.connectDevice(device)).isTrue();
verify(cachedDevice).connect();
}
diff --git a/packages/SystemUI/res/drawable/stat_sys_media.xml b/packages/SystemUI/res/drawable/stat_sys_media.xml
new file mode 100644
index 0000000..d48db7b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_media.xml
@@ -0,0 +1,31 @@
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M5,7.81l0,8.38l6,-4.19z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M13,8h2v8h-2z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M17,8h2v8h-2z"
+ android:fillColor="#000000"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/global_actions_grid_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_v2.xml
index ab4732d..7d45de3f 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_v2.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_v2.xml
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/global_actions_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -43,39 +44,50 @@
</LinearLayout>
</com.android.systemui.globalactions.GlobalActionsFlatLayout>
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/global_actions_lock_message_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone">
+ <TextView
+ android:id="@+id/global_actions_lock_message"
+ style="@style/TextAppearance.Control.Title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="@dimen/global_actions_side_margin"
+ android:drawablePadding="12dp"
+ android:gravity="center"
+ android:text="@string/global_action_lock_message"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_bias="0.35"/>
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
<com.android.systemui.globalactions.MinHeightScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
- android:scrollbars="none"
- >
+ android:scrollbars="none">
+
<LinearLayout
android:id="@+id/global_actions_grid_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:orientation="vertical"
- android:clipToPadding="false"
- >
- <LinearLayout
- android:id="@+id/global_actions_panel"
+ android:clipToPadding="false">
+
+ <FrameLayout
+ android:id="@+id/global_actions_wallet"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- >
- <FrameLayout
- android:id="@+id/global_actions_panel_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- />
- </LinearLayout>
+ android:layout_height="wrap_content"/>
<LinearLayout
android:id="@+id/global_actions_controls"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical"
- />
+ android:orientation="vertical"/>
+
</LinearLayout>
</com.android.systemui.globalactions.MinHeightScrollView>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/global_screenshot_action_chip.xml b/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
index e4ae7c1..46396e3 100644
--- a/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
+++ b/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
@@ -20,23 +20,29 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/screenshot_action_chip_margin_right"
+ android:paddingVertical="@dimen/screenshot_action_chip_margin_vertical"
android:layout_gravity="center"
- android:paddingVertical="@dimen/screenshot_action_chip_padding_vertical"
- android:background="@drawable/action_chip_background"
- android:alpha="0.0"
- android:gravity="center">
- <ImageView
- android:id="@+id/screenshot_action_chip_icon"
- android:layout_width="@dimen/screenshot_action_chip_icon_size"
- android:layout_height="@dimen/screenshot_action_chip_icon_size"
- android:layout_marginStart="@dimen/screenshot_action_chip_padding_start"
- android:layout_marginEnd="@dimen/screenshot_action_chip_padding_middle"/>
- <TextView
- android:id="@+id/screenshot_action_chip_text"
+ android:gravity="center"
+ android:alpha="0.0">
+ <LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/screenshot_action_chip_padding_end"
- android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
- android:textSize="@dimen/screenshot_action_chip_text_size"
- android:textColor="@color/global_screenshot_button_text"/>
+ android:paddingVertical="@dimen/screenshot_action_chip_padding_vertical"
+ android:background="@drawable/action_chip_background"
+ android:gravity="center">
+ <ImageView
+ android:id="@+id/screenshot_action_chip_icon"
+ android:layout_width="@dimen/screenshot_action_chip_icon_size"
+ android:layout_height="@dimen/screenshot_action_chip_icon_size"
+ android:layout_marginStart="@dimen/screenshot_action_chip_padding_start"
+ android:layout_marginEnd="@dimen/screenshot_action_chip_padding_middle"/>
+ <TextView
+ android:id="@+id/screenshot_action_chip_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/screenshot_action_chip_padding_end"
+ android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+ android:textSize="@dimen/screenshot_action_chip_text_size"
+ android:textColor="@color/global_screenshot_button_text"/>
+ </LinearLayout>
</com.android.systemui.screenshot.ScreenshotActionChip>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a3d32c1..1481bff 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -314,13 +314,14 @@
<dimen name="screenshot_dismiss_button_margin">8dp</dimen>
<dimen name="screenshot_action_container_offset_y">32dp</dimen>
<dimen name="screenshot_action_container_corner_radius">10dp</dimen>
- <dimen name="screenshot_action_container_padding_vertical">16dp</dimen>
+ <dimen name="screenshot_action_container_padding_vertical">6dp</dimen>
<dimen name="screenshot_action_container_margin_horizontal">8dp</dimen>
<dimen name="screenshot_action_container_padding_left">96dp</dimen>
<dimen name="screenshot_action_container_padding_right">8dp</dimen>
<!-- Radius of the chip background on global screenshot actions -->
<dimen name="screenshot_button_corner_radius">20dp</dimen>
<dimen name="screenshot_action_chip_margin_right">8dp</dimen>
+ <dimen name="screenshot_action_chip_margin_vertical">10dp</dimen>
<dimen name="screenshot_action_chip_padding_vertical">7dp</dimen>
<dimen name="screenshot_action_chip_icon_size">18dp</dimen>
<dimen name="screenshot_action_chip_padding_start">8dp</dimen>
@@ -1027,6 +1028,9 @@
<!-- Amount to shift the layout when exiting/entering for controls activities -->
<dimen name="global_actions_controls_y_translation">20dp</dimen>
+ <!-- Shift quick access wallet down in Global Actions when Controls are unavailable -->
+ <dimen name="global_actions_wallet_top_margin">40dp</dimen>
+
<!-- The maximum offset in either direction that elements are moved horizontally to prevent
burn-in on AOD. -->
<dimen name="burn_in_prevention_offset_x">8dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8bbcfa0..e1df81a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -212,6 +212,13 @@
<!-- Power menu item for taking a screenshot [CHAR LIMIT=20]-->
<string name="global_action_screenshot">Screenshot</string>
+ <!-- Text shown when viewing global actions while phone is locked and additional controls are hidden [CHAR LIMIT=NONE] -->
+ <string name="global_action_lock_message" product="default">Unlock your phone for more options</string>
+ <!-- Text shown when viewing global actions while phone is locked and additional controls are hidden [CHAR LIMIT=NONE] -->
+ <string name="global_action_lock_message" product="tablet">Unlock your tablet for more options</string>
+ <!-- Text shown when viewing global actions while phone is locked and additional controls are hidden [CHAR LIMIT=NONE] -->
+ <string name="global_action_lock_message" product="device">Unlock your device for more options</string>
+
<!-- text to show in place of RemoteInput images when they cannot be shown.
[CHAR LIMIT=50] -->
<string name="remote_input_image_insertion_text">sent an image</string>
@@ -790,6 +797,9 @@
<!-- Accessibility text describing sensors off active. [CHAR LIMIT=NONE] -->
<string name="accessibility_sensors_off_active">Sensors off active</string>
+ <!-- Accessibility text describing that media is playing. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_media_active">Media is active</string>
+
<!-- Content description of the clear button in the notification panel for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_clear_all">Clear all notifications.</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index 0350f2d..114472b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -16,6 +16,7 @@
package com.android.systemui.shared.recents;
+import android.graphics.Rect;
import android.graphics.Region;
import android.os.Bundle;
import android.view.MotionEvent;
@@ -69,4 +70,9 @@
* Sent when some system ui state changes.
*/
void onSystemUiStateChanged(int stateFlags) = 16;
+
+ /**
+ * Sent when the split screen is resized
+ */
+ void onSplitScreenSecondaryBoundsChanged(in Rect bounds, in Rect insets) = 17;
}
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java
index e17d4e6..77abffc 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java
@@ -23,10 +23,13 @@
import android.util.ArraySet;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.statusbar.NotificationInteractionTracker;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.util.time.SystemClock;
+import javax.inject.Inject;
+
/**
* Extends the lifetime of foreground notification services such that they show for at least
* five seconds
@@ -41,9 +44,14 @@
private ArraySet<NotificationEntry> mManagedEntries = new ArraySet<>();
private Handler mHandler = new Handler(Looper.getMainLooper());
private final SystemClock mSystemClock;
+ private final NotificationInteractionTracker mInteractionTracker;
- public ForegroundServiceLifetimeExtender(SystemClock systemClock) {
+ @Inject
+ public ForegroundServiceLifetimeExtender(
+ NotificationInteractionTracker interactionTracker,
+ SystemClock systemClock) {
mSystemClock = systemClock;
+ mInteractionTracker = interactionTracker;
}
@Override
@@ -58,8 +66,9 @@
return false;
}
- long currentTime = mSystemClock.uptimeMillis();
- return currentTime - entry.getCreationTime() < MIN_FGS_TIME_MS;
+ boolean hasInteracted = mInteractionTracker.hasUserInteractedWith(entry.getKey());
+ long aliveTime = mSystemClock.uptimeMillis() - entry.getCreationTime();
+ return aliveTime < MIN_FGS_TIME_MS && !hasInteracted;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
index ef1f4e0..650b9a7 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
@@ -51,6 +51,7 @@
ForegroundServiceController foregroundServiceController,
NotificationEntryManager notificationEntryManager,
NotifPipeline notifPipeline,
+ ForegroundServiceLifetimeExtender fgsLifetimeExtender,
SystemClock systemClock) {
mContext = context;
mForegroundServiceController = foregroundServiceController;
@@ -78,8 +79,7 @@
removeNotification(entry.getSbn());
}
});
- mEntryManager.addNotificationLifetimeExtender(
- new ForegroundServiceLifetimeExtender(systemClock));
+ mEntryManager.addNotificationLifetimeExtender(fgsLifetimeExtender);
notifPipeline.addCollectionListener(new NotifCollectionListener() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index d008088..f1cb667 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -28,7 +28,7 @@
import com.android.internal.os.BinderInternal;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
+import com.android.systemui.dump.DumpHandler;
import com.android.systemui.dump.SystemUIAuxiliaryDumpService;
import java.io.FileDescriptor;
@@ -39,15 +39,15 @@
public class SystemUIService extends Service {
private final Handler mMainHandler;
- private final DumpManager mDumpManager;
+ private final DumpHandler mDumpHandler;
@Inject
public SystemUIService(
@Main Handler mainHandler,
- DumpManager dumpManager) {
+ DumpHandler dumpHandler) {
super();
mMainHandler = mainHandler;
- mDumpManager = dumpManager;
+ mDumpHandler = dumpHandler;
}
@Override
@@ -94,10 +94,10 @@
String[] massagedArgs = args;
if (args.length == 0) {
massagedArgs = new String[] {
- DumpManager.PRIORITY_ARG,
- DumpManager.PRIORITY_ARG_CRITICAL};
+ DumpHandler.PRIORITY_ARG,
+ DumpHandler.PRIORITY_ARG_CRITICAL};
}
- mDumpManager.dump(fd, pw, massagedArgs);
+ mDumpHandler.dump(fd, pw, massagedArgs);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index a578f33..2587369 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -185,9 +185,10 @@
// Used to post to main UI thread
private Handler mHandler = new Handler();
- /** LayoutParams used to add the BubbleStackView to the window maanger. */
+ /** LayoutParams used to add the BubbleStackView to the window manager. */
private WindowManager.LayoutParams mWmLayoutParams;
-
+ /** Whether or not the BubbleStackView has been added to the WindowManager. */
+ private boolean mAddedToWindowManager = false;
// Used for determining view rect for touch interaction
private Rect mTempRect = new Rect();
@@ -595,9 +596,8 @@
if (mStackView == null) {
mStackView = new BubbleStackView(
mContext, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator,
- mSysUiState, mNotificationShadeWindowController);
+ mSysUiState, mNotificationShadeWindowController, this::onAllBubblesAnimatedOut);
mStackView.addView(mBubbleScrim);
- addToWindowManager();
if (mExpandListener != null) {
mStackView.setExpandListener(mExpandListener);
}
@@ -605,10 +605,17 @@
mStackView.setUnbubbleConversationCallback(notificationEntry ->
onUserChangedBubble(notificationEntry, false /* shouldBubble */));
}
+
+ addToWindowManagerMaybe();
}
- /** Adds the BubbleStackView to the WindowManager. */
- private void addToWindowManager() {
+ /** Adds the BubbleStackView to the WindowManager if it's not already there. */
+ private void addToWindowManagerMaybe() {
+ // If the stack is null, or already added, don't add it.
+ if (mStackView == null || mAddedToWindowManager) {
+ return;
+ }
+
mWmLayoutParams = new WindowManager.LayoutParams(
// Fill the screen so we can use translation animations to position the bubble
// stack. We'll use touchable regions to ignore touches that are not on the bubbles
@@ -629,9 +636,37 @@
mWmLayoutParams.packageName = mContext.getPackageName();
mWmLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- mWindowManager.addView(mStackView, mWmLayoutParams);
+ try {
+ mAddedToWindowManager = true;
+ mWindowManager.addView(mStackView, mWmLayoutParams);
+ } catch (IllegalStateException e) {
+ // This means the stack has already been added. This shouldn't happen, since we keep
+ // track of that, but just in case, update the previously added view's layout params.
+ e.printStackTrace();
+ updateWmFlags();
+ }
}
+ /** Removes the BubbleStackView from the WindowManager if it's there. */
+ private void removeFromWindowManagerMaybe() {
+ if (!mAddedToWindowManager) {
+ return;
+ }
+
+ try {
+ mAddedToWindowManager = false;
+ mWindowManager.removeView(mStackView);
+ } catch (IllegalArgumentException e) {
+ // This means the stack has already been removed - it shouldn't happen, but ignore if it
+ // does, since we wanted it removed anyway.
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Updates the BubbleStackView's WindowManager.LayoutParams, and updates the WindowManager with
+ * the new params if the stack has been added.
+ */
private void updateWmFlags() {
if (isStackExpanded()) {
// If we're expanded, we want to be focusable so that the ActivityView can receive focus
@@ -643,7 +678,25 @@
mWmLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
}
- mWindowManager.updateViewLayout(mStackView, mWmLayoutParams);
+ if (mStackView != null && mAddedToWindowManager) {
+ try {
+ mWindowManager.updateViewLayout(mStackView, mWmLayoutParams);
+ } catch (IllegalArgumentException e) {
+ // If the stack is somehow not there, ignore the attempt to update it.
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Called by the BubbleStackView and whenever all bubbles have animated out, and none have been
+ * added in the meantime.
+ */
+ private void onAllBubblesAnimatedOut() {
+ if (mStackView != null) {
+ mStackView.setVisibility(INVISIBLE);
+ removeFromWindowManagerMaybe();
+ }
}
/**
@@ -833,10 +886,9 @@
}
void updateBubble(NotificationEntry notif, boolean suppressFlyout, boolean showInShade) {
- if (mStackView == null) {
- // Lazy init stack view when a bubble is created
- ensureStackViewCreated();
- }
+ // Lazy init stack view when a bubble is created
+ ensureStackViewCreated();
+
// If this is an interruptive notif, mark that it's interrupted
if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) {
notif.setInterruption();
@@ -1196,11 +1248,15 @@
if (mStackView == null) {
return;
}
- if (mStatusBarStateListener.getCurrentState() == SHADE && hasBubbles()) {
- // Bubbles only appear in unlocked shade
- mStackView.setVisibility(hasBubbles() ? VISIBLE : INVISIBLE);
- } else if (mStackView != null) {
+
+ if (mStatusBarStateListener.getCurrentState() != SHADE) {
+ // Bubbles don't appear over the locked shade.
mStackView.setVisibility(INVISIBLE);
+ } else if (hasBubbles()) {
+ // If we're unlocked, show the stack if we have bubbles. If we don't have bubbles, the
+ // stack will be set to INVISIBLE in onAllBubblesAnimatedOut after the bubbles animate
+ // out.
+ mStackView.setVisibility(VISIBLE);
}
mStackView.updateContentDescription();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 88f5eb0..ec45f93 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -362,6 +362,10 @@
new MagnetizedObject.MagnetListener() {
@Override
public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ if (mExpandedAnimationController.getDraggedOutBubble() == null) {
+ return;
+ }
+
animateDesaturateAndDarken(
mExpandedAnimationController.getDraggedOutBubble(), true);
}
@@ -369,6 +373,10 @@
@Override
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
float velX, float velY, boolean wasFlungOut) {
+ if (mExpandedAnimationController.getDraggedOutBubble() == null) {
+ return;
+ }
+
animateDesaturateAndDarken(
mExpandedAnimationController.getDraggedOutBubble(), false);
@@ -383,6 +391,10 @@
@Override
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ if (mExpandedAnimationController.getDraggedOutBubble() == null) {
+ return;
+ }
+
mExpandedAnimationController.dismissDraggedOutBubble(
mExpandedAnimationController.getDraggedOutBubble() /* bubble */,
mDismissTargetContainer.getHeight() /* translationYBy */,
@@ -656,7 +668,8 @@
@Nullable SurfaceSynchronizer synchronizer,
FloatingContentCoordinator floatingContentCoordinator,
SysUiState sysUiState,
- NotificationShadeWindowController notificationShadeWindowController) {
+ NotificationShadeWindowController notificationShadeWindowController,
+ Runnable allBubblesAnimatedOutAction) {
super(context);
mBubbleData = data;
@@ -691,11 +704,18 @@
mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
+ final Runnable onBubbleAnimatedOut = () -> {
+ if (getBubbleCount() == 0) {
+ allBubblesAnimatedOutAction.run();
+ }
+ };
+
mStackAnimationController = new StackAnimationController(
- floatingContentCoordinator, this::getBubbleCount);
+ floatingContentCoordinator, this::getBubbleCount, onBubbleAnimatedOut);
mExpandedAnimationController = new ExpandedAnimationController(
- mDisplaySize, mExpandedViewPadding, res.getConfiguration().orientation);
+ mDisplaySize, mExpandedViewPadding, res.getConfiguration().orientation,
+ onBubbleAnimatedOut);
mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER;
setUpUserEducation();
@@ -871,7 +891,7 @@
}
}
- return false;
+ return true;
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index f57cf42..76ff1af 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -32,6 +32,7 @@
import com.android.systemui.Interpolators;
import com.android.systemui.R;
+import com.android.systemui.util.animation.PhysicsAnimator;
import com.android.systemui.util.magnetictarget.MagnetizedObject;
import com.google.android.collect.Sets;
@@ -69,6 +70,10 @@
*/
private static final float FLING_TO_DISMISS_MIN_VELOCITY = 6000f;
+ private final PhysicsAnimator.SpringConfig mAnimateOutSpringConfig =
+ new PhysicsAnimator.SpringConfig(
+ EXPAND_COLLAPSE_ANIM_STIFFNESS, SpringForce.DAMPING_RATIO_NO_BOUNCY);
+
/** Horizontal offset between bubbles, which we need to know to re-stack them. */
private float mStackOffsetPx;
/** Space between status bar and bubbles in the expanded state. */
@@ -116,10 +121,17 @@
private int mExpandedViewPadding;
+ /**
+ * Callback to run whenever any bubble is animated out. The BubbleStackView will check if the
+ * end of this animation means we have no bubbles left, and notify the BubbleController.
+ */
+ private Runnable mOnBubbleAnimatedOutAction;
+
public ExpandedAnimationController(Point displaySize, int expandedViewPadding,
- int orientation) {
+ int orientation, Runnable onBubbleAnimatedOutAction) {
updateResources(orientation, displaySize);
mExpandedViewPadding = expandedViewPadding;
+ mOnBubbleAnimatedOutAction = onBubbleAnimatedOutAction;
}
/**
@@ -355,8 +367,8 @@
}
animationForChild(bubble)
.withStiffness(SpringForce.STIFFNESS_HIGH)
- .scaleX(1.1f)
- .scaleY(1.1f)
+ .scaleX(0f)
+ .scaleY(0f)
.translationY(bubble.getTranslationY() + translationYBy)
.alpha(0f, after)
.start();
@@ -500,18 +512,17 @@
@Override
void onChildRemoved(View child, int index, Runnable finishRemoval) {
- final PhysicsAnimationLayout.PhysicsPropertyAnimator animator = animationForChild(child);
-
// If we're removing the dragged-out bubble, that means it got dismissed.
if (child.equals(getDraggedOutBubble())) {
mMagnetizedBubbleDraggingOut = null;
finishRemoval.run();
+ mOnBubbleAnimatedOutAction.run();
} else {
- animator.alpha(0f, finishRemoval /* endAction */)
- .withStiffness(SpringForce.STIFFNESS_HIGH)
- .withDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)
- .scaleX(1.1f)
- .scaleY(1.1f)
+ PhysicsAnimator.getInstance(child)
+ .spring(DynamicAnimation.ALPHA, 0f)
+ .spring(DynamicAnimation.SCALE_X, 0f, mAnimateOutSpringConfig)
+ .spring(DynamicAnimation.SCALE_Y, 0f, mAnimateOutSpringConfig)
+ .withEndActions(finishRemoval, mOnBubbleAnimatedOutAction)
.start();
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
index a7d1be1..942b9a7 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
@@ -767,6 +767,10 @@
int targetAnimDuration,
TimeInterpolator targetAnimInterpolator,
Runnable... pathAnimEndActions) {
+ if (mPathAnimator != null) {
+ mPathAnimator.cancel();
+ }
+
mPathAnimator = ObjectAnimator.ofFloat(
this, mCurrentPointOnPathXProperty, mCurrentPointOnPathYProperty, path);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
index 2cfe1dd..69db5c3 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -74,6 +74,10 @@
private static final int FLING_FOLLOW_STIFFNESS = 20000;
public static final float DEFAULT_BOUNCINESS = 0.9f;
+ private final PhysicsAnimator.SpringConfig mAnimateOutSpringConfig =
+ new PhysicsAnimator.SpringConfig(
+ ANIMATE_IN_STIFFNESS, SpringForce.DAMPING_RATIO_NO_BOUNCY);
+
/**
* Friction applied to fling animations. Since the stack must land on one of the sides of the
* screen, we want less friction horizontally so that the stack has a better chance of making it
@@ -248,12 +252,19 @@
/** Returns the number of 'real' bubbles (excluding the overflow bubble). */
private IntSupplier mBubbleCountSupplier;
+ /**
+ * Callback to run whenever any bubble is animated out. The BubbleStackView will check if the
+ * end of this animation means we have no bubbles left, and notify the BubbleController.
+ */
+ private Runnable mOnBubbleAnimatedOutAction;
+
public StackAnimationController(
FloatingContentCoordinator floatingContentCoordinator,
- IntSupplier bubbleCountSupplier) {
+ IntSupplier bubbleCountSupplier,
+ Runnable onBubbleAnimatedOutAction) {
mFloatingContentCoordinator = floatingContentCoordinator;
mBubbleCountSupplier = bubbleCountSupplier;
-
+ mOnBubbleAnimatedOutAction = onBubbleAnimatedOutAction;
}
/**
@@ -448,6 +459,10 @@
float friction,
SpringForce spring,
Float finalPosition) {
+ if (!isActiveController()) {
+ return;
+ }
+
Log.d(TAG, String.format("Flinging %s.",
PhysicsAnimationLayout.getReadablePropertyName(property)));
@@ -652,8 +667,8 @@
public void animateStackDismissal(float translationYBy, Runnable after) {
animationsForChildrenFromIndex(0, (index, animation) ->
animation
- .scaleX(0.5f)
- .scaleY(0.5f)
+ .scaleX(0f)
+ .scaleY(0f)
.alpha(0f)
.translationY(
mLayout.getChildAt(index).getTranslationY() + translationYBy)
@@ -668,7 +683,7 @@
DynamicAnimation.ViewProperty property, SpringForce spring,
float vel, float finalPosition, @Nullable Runnable... after) {
- if (mLayout.getChildCount() == 0) {
+ if (mLayout.getChildCount() == 0 || !isActiveController()) {
return;
}
@@ -760,13 +775,11 @@
@Override
void onChildRemoved(View child, int index, Runnable finishRemoval) {
- // Animate the removing view in the opposite direction of the stack.
- final float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X);
- animationForChild(child)
- .alpha(0f, finishRemoval /* after */)
- .scaleX(ANIMATE_IN_STARTING_SCALE)
- .scaleY(ANIMATE_IN_STARTING_SCALE)
- .translationX(mStackPosition.x - (-xOffset * ANIMATE_TRANSLATION_FACTOR))
+ PhysicsAnimator.getInstance(child)
+ .spring(DynamicAnimation.ALPHA, 0f)
+ .spring(DynamicAnimation.SCALE_X, 0f, mAnimateOutSpringConfig)
+ .spring(DynamicAnimation.SCALE_Y, 0f, mAnimateOutSpringConfig)
+ .withEndActions(finishRemoval, mOnBubbleAnimatedOutAction)
.start();
// If there are other bubbles, pull them into the correct position.
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index a1f4c96..181170b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -89,35 +89,36 @@
contentResolver, CONTROLS_AVAILABLE, DEFAULT_ENABLED, currentUserId) != 0
private set
- private var file = Environment.buildPath(
- context.filesDir,
- ControlsFavoritePersistenceWrapper.FILE_NAME
- )
- private var auxiliaryFile = Environment.buildPath(
- context.filesDir,
- AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME
- )
- private val persistenceWrapper = optionalWrapper.orElseGet {
- ControlsFavoritePersistenceWrapper(
- file,
- executor,
- BackupManager(context)
+ private val persistenceWrapper: ControlsFavoritePersistenceWrapper
+ @VisibleForTesting
+ internal var auxiliaryPersistenceWrapper: AuxiliaryPersistenceWrapper
+
+ init {
+ val userStructure = UserStructure(context, currentUser)
+
+ persistenceWrapper = optionalWrapper.orElseGet {
+ ControlsFavoritePersistenceWrapper(
+ userStructure.file,
+ executor,
+ BackupManager(userStructure.userContext)
+ )
+ }
+
+ auxiliaryPersistenceWrapper = AuxiliaryPersistenceWrapper(
+ userStructure.auxiliaryFile,
+ executor
)
}
- @VisibleForTesting
- internal var auxiliaryPersistenceWrapper = AuxiliaryPersistenceWrapper(auxiliaryFile, executor)
-
private fun setValuesForUser(newUser: UserHandle) {
Log.d(TAG, "Changing to user: $newUser")
currentUser = newUser
- val userContext = context.createContextAsUser(currentUser, 0)
- file = Environment.buildPath(
- userContext.filesDir, ControlsFavoritePersistenceWrapper.FILE_NAME)
- auxiliaryFile = Environment.buildPath(
- userContext.filesDir, AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME)
- persistenceWrapper.changeFileAndBackupManager(file, BackupManager(userContext))
- auxiliaryPersistenceWrapper.changeFile(auxiliaryFile)
+ val userStructure = UserStructure(context, currentUser)
+ persistenceWrapper.changeFileAndBackupManager(
+ userStructure.file,
+ BackupManager(userStructure.userContext)
+ )
+ auxiliaryPersistenceWrapper.changeFile(userStructure.auxiliaryFile)
available = Settings.Secure.getIntForUser(contentResolver, CONTROLS_AVAILABLE,
DEFAULT_ENABLED, newUser.identifier) != 0
resetFavorites(available)
@@ -564,6 +565,20 @@
}
}
+class UserStructure(context: Context, user: UserHandle) {
+ val userContext = context.createContextAsUser(user, 0)
+
+ val file = Environment.buildPath(
+ context.filesDir,
+ ControlsFavoritePersistenceWrapper.FILE_NAME
+ )
+
+ val auxiliaryFile = Environment.buildPath(
+ context.filesDir,
+ AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME
+ )
+}
+
/**
* Relies on immutable data for thread safety. When necessary to update favMap, use reassignment to
* replace it, which will not disrupt any ongoing map traversal.
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index 9055479..10e9137 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -58,7 +58,7 @@
override fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) {
bouncerOrRun {
- val effect = if (isChecked) Vibrations.toggleOnEffect else Vibrations.toggleOffEffect
+ val effect = if (!isChecked) Vibrations.toggleOnEffect else Vibrations.toggleOffEffect
vibrate(effect)
cvh.action(BooleanAction(templateId, !isChecked))
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 52d564d..3aa417a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -59,6 +59,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.globalactions.GlobalActionsPopupMenu
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.phone.ShadeController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Lazy
@@ -79,7 +80,8 @@
@Main val sharedPreferences: SharedPreferences,
val controlActionCoordinator: ControlActionCoordinator,
private val activityStarter: ActivityStarter,
- private val keyguardStateController: KeyguardStateController
+ private val keyguardStateController: KeyguardStateController,
+ private val shadeController: ShadeController
) : ControlsUiController {
companion object {
@@ -254,14 +256,11 @@
intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true)
dismissGlobalActions.run()
- if (!keyguardStateController.isUnlocked()) {
- activityStarter.dismissKeyguardThenExecute({
- context.startActivity(intent)
- true
- }, null, true)
- } else {
+ activityStarter.dismissKeyguardThenExecute({
+ shadeController.collapsePanel(false)
context.startActivity(intent)
- }
+ true
+ }, null, true)
}
private fun showControlsView(items: List<SelectionItem>) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
index 236fa2d..f970152 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
@@ -18,7 +18,6 @@
import android.app.ActivityView
import android.app.Dialog
-import android.content.ComponentName
import android.content.Intent
import android.provider.Settings
import android.view.View
@@ -58,17 +57,13 @@
launchIntent.putExtra(EXTRA_USE_PANEL, true)
// Apply flags to make behaviour match documentLaunchMode=always.
- launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
launchIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
view.startActivity(launchIntent)
}
override fun onActivityViewDestroyed(view: ActivityView) {}
-
- override fun onTaskCreated(taskId: Int, componentName: ComponentName) {}
-
- override fun onTaskRemovalStarted(taskId: Int) {}
}
init {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt
index a97113c..c0f6aab 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt
@@ -20,7 +20,7 @@
import android.os.VibrationEffect.Composition.PRIMITIVE_TICK
object Vibrations {
- private const val TOGGLE_TICK_COUNT = 12
+ private const val TOGGLE_TICK_COUNT = 40
val toggleOnEffect = initToggleOnEffect()
val toggleOffEffect = initToggleOffEffect()
@@ -29,6 +29,7 @@
private fun initToggleOnEffect(): VibrationEffect {
val composition = VibrationEffect.startComposition()
+ composition.addPrimitive(PRIMITIVE_TICK, 0.05f, 200)
var i = 0
while (i++ < TOGGLE_TICK_COUNT) {
composition.addPrimitive(PRIMITIVE_TICK, 0.05f, 0)
@@ -43,7 +44,7 @@
composition.addPrimitive(PRIMITIVE_TICK, 0.05f, 100)
var i = 0
while (i++ < TOGGLE_TICK_COUNT) {
- composition?.addPrimitive(PRIMITIVE_TICK, 0.05f, 0)
+ composition.addPrimitive(PRIMITIVE_TICK, 0.05f, 0)
}
return composition.compose()
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index 5e36704..6572937 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -34,6 +34,7 @@
import com.android.systemui.statusbar.phone.BiometricUnlockController;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.sensors.AsyncSensorManager;
import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.wakelock.DelayedWakeLock;
@@ -56,6 +57,7 @@
private final ProximitySensor mProximitySensor;
private final DelayedWakeLock.Builder mDelayedWakeLockBuilder;
private final Handler mHandler;
+ private final DelayableExecutor mDelayableExecutor;
private final BiometricUnlockController mBiometricUnlockController;
private final BroadcastDispatcher mBroadcastDispatcher;
private final DozeHost mDozeHost;
@@ -68,6 +70,7 @@
DockManager dockManager, @Nullable IWallpaperManager wallpaperManager,
ProximitySensor proximitySensor,
DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler,
+ DelayableExecutor delayableExecutor,
BiometricUnlockController biometricUnlockController,
BroadcastDispatcher broadcastDispatcher, DozeHost dozeHost) {
mFalsingManager = falsingManager;
@@ -83,6 +86,7 @@
mProximitySensor = proximitySensor;
mDelayedWakeLockBuilder = delayedWakeLockBuilder;
mHandler = handler;
+ mDelayableExecutor = delayableExecutor;
mBiometricUnlockController = biometricUnlockController;
mBroadcastDispatcher = broadcastDispatcher;
mDozeHost = dozeHost;
@@ -107,8 +111,8 @@
new DozePauser(mHandler, machine, mAlarmManager, mDozeParameters.getPolicy()),
new DozeFalsingManagerAdapter(mFalsingManager),
createDozeTriggers(dozeService, mAsyncSensorManager, mDozeHost,
- mAlarmManager, config, mDozeParameters, mHandler, wakeLock, machine,
- mDockManager, mDozeLog),
+ mAlarmManager, config, mDozeParameters, mDelayableExecutor, wakeLock,
+ machine, mDockManager, mDozeLog),
createDozeUi(dozeService, mDozeHost, wakeLock, machine, mHandler,
mAlarmManager, mDozeParameters, mDozeLog),
new DozeScreenState(wrappedService, mHandler, mDozeHost, mDozeParameters,
@@ -135,11 +139,11 @@
private DozeTriggers createDozeTriggers(Context context, AsyncSensorManager sensorManager,
DozeHost host, AlarmManager alarmManager, AmbientDisplayConfiguration config,
- DozeParameters params, Handler handler, WakeLock wakeLock, DozeMachine machine,
- DockManager dockManager, DozeLog dozeLog) {
+ DozeParameters params, DelayableExecutor delayableExecutor, WakeLock wakeLock,
+ DozeMachine machine, DockManager dockManager, DozeLog dozeLog) {
boolean allowPulseTriggers = true;
return new DozeTriggers(context, machine, host, alarmManager, config, params,
- sensorManager, handler, wakeLock, allowPulseTriggers, dockManager,
+ sensorManager, delayableExecutor, wakeLock, allowPulseTriggers, dockManager,
mProximitySensor, dozeLog, mBroadcastDispatcher);
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index e1081cd..78f8f67 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -101,7 +101,8 @@
public DozeSensors(Context context, AlarmManager alarmManager, AsyncSensorManager sensorManager,
DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock,
- Callback callback, Consumer<Boolean> proxCallback, DozeLog dozeLog) {
+ Callback callback, Consumer<Boolean> proxCallback, DozeLog dozeLog,
+ ProximitySensor proximitySensor) {
mContext = context;
mAlarmManager = alarmManager;
mSensorManager = sensorManager;
@@ -111,6 +112,7 @@
mProxCallback = proxCallback;
mResolver = mContext.getContentResolver();
mCallback = callback;
+ mProximitySensor = proximitySensor;
boolean alwaysOn = mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT);
mSensors = new TriggerSensor[] {
@@ -173,7 +175,6 @@
dozeLog),
};
- mProximitySensor = new ProximitySensor(context.getResources(), sensorManager);
setProxListening(false); // Don't immediately start listening when we register.
mProximitySensor.register(
proximityEvent -> {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 3510e07..6a55014 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -26,7 +26,6 @@
import android.content.res.Configuration;
import android.hardware.display.AmbientDisplayConfiguration;
import android.metrics.LogMaker;
-import android.os.Handler;
import android.os.SystemClock;
import android.os.UserHandle;
import android.text.format.Formatter;
@@ -43,6 +42,7 @@
import com.android.systemui.dock.DockManager;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.util.Assert;
+import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.sensors.AsyncSensorManager;
import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.wakelock.WakeLock;
@@ -152,9 +152,9 @@
public DozeTriggers(Context context, DozeMachine machine, DozeHost dozeHost,
AlarmManager alarmManager, AmbientDisplayConfiguration config,
- DozeParameters dozeParameters, AsyncSensorManager sensorManager, Handler handler,
- WakeLock wakeLock, boolean allowPulseTriggers, DockManager dockManager,
- ProximitySensor proximitySensor,
+ DozeParameters dozeParameters, AsyncSensorManager sensorManager,
+ DelayableExecutor delayableExecutor, WakeLock wakeLock, boolean allowPulseTriggers,
+ DockManager dockManager, ProximitySensor proximitySensor,
DozeLog dozeLog, BroadcastDispatcher broadcastDispatcher) {
mContext = context;
mMachine = machine;
@@ -165,10 +165,10 @@
mWakeLock = wakeLock;
mAllowPulseTriggers = allowPulseTriggers;
mDozeSensors = new DozeSensors(context, alarmManager, mSensorManager, dozeParameters,
- config, wakeLock, this::onSensor, this::onProximityFar, dozeLog);
+ config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor);
mUiModeManager = mContext.getSystemService(UiModeManager.class);
mDockManager = dockManager;
- mProxCheck = new ProximitySensor.ProximityCheck(proximitySensor, handler);
+ mProxCheck = new ProximitySensor.ProximityCheck(proximitySensor, delayableExecutor);
mDozeLog = dozeLog;
mBroadcastDispatcher = broadcastDispatcher;
}
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
new file mode 100644
index 0000000..fa951fa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dump
+
+import android.content.Context
+import android.os.SystemClock
+import android.os.Trace
+import com.android.systemui.R
+import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_CRITICAL
+import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_HIGH
+import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_NORMAL
+import com.android.systemui.log.LogBuffer
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/**
+ * Oversees SystemUI's output during bug reports (and dumpsys in general)
+ *
+ * Dump output is split into two sections, CRITICAL and NORMAL. In general, the CRITICAL section
+ * contains all dumpables that were registered to the [DumpManager], while the NORMAL sections
+ * contains all [LogBuffer]s (due to their length).
+ *
+ * The CRITICAL and NORMAL sections can be found within a bug report by searching for
+ * "SERVICE com.android.systemui/.SystemUIService" and
+ * "SERVICE com.android.systemui/.dump.SystemUIAuxiliaryDumpService", respectively.
+ *
+ * Finally, some or all of the dump can be triggered on-demand via adb (see below).
+ *
+ * ```
+ * # For the following, let <invocation> be:
+ * $ adb shell dumpsys activity service com.android.systemui/.SystemUIService
+ *
+ * # To dump specific target(s), specify one or more registered names:
+ * $ <invocation> NotifCollection
+ * $ <invocation> StatusBar FalsingManager BootCompleteCacheImpl
+ *
+ * # Log buffers can be dumped in the same way (and can even be mixed in with other dump targets,
+ * # although it's not clear why one would want such a thing):
+ * $ <invocation> NotifLog
+ * $ <invocation> StatusBar NotifLog BootCompleteCacheImpl
+ *
+ * # If passing -t or --tail, shows only the last N lines of any log buffers:
+ * $ <invocation> NotifLog --tail 100
+ *
+ * # Dump targets are matched using String.endsWith(), so dumpables that register using their
+ * # fully-qualified class name can still be dumped using their short name:
+ * $ <invocation> com.android.keyguard.KeyguardUpdateMonitor
+ * $ <invocation> keyguard.KeyguardUpdateMonitor
+ * $ <invocation> KeyguardUpdateMonitor
+ *
+ * # To dump all dumpables or all buffers:
+ * $ <invocation> dumpables
+ * $ <invocation> buffers
+ *
+ * # Finally, the following will simulate what we dump during the CRITICAL and NORMAL sections of a
+ * # bug report:
+ * $ <invocation> bugreport-critical
+ * $ <invocation> bugreport-normal
+ *
+ * # And if you need to be reminded of this list of commands:
+ * $ <invocation> -h
+ * $ <invocation> --help
+ * ```
+ */
+class DumpHandler @Inject constructor(
+ private val context: Context,
+ private val dumpManager: DumpManager,
+ private val logBufferEulogizer: LogBufferEulogizer
+) {
+ /**
+ * Dump the diagnostics! Behavior can be controlled via [args].
+ */
+ fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
+ Trace.beginSection("DumpManager#dump()")
+ val start = SystemClock.uptimeMillis()
+
+ val parsedArgs = try {
+ parseArgs(args)
+ } catch (e: ArgParseException) {
+ pw.println(e.message)
+ return
+ }
+
+ when (parsedArgs.dumpPriority) {
+ PRIORITY_ARG_CRITICAL -> dumpCritical(fd, pw, parsedArgs)
+ PRIORITY_ARG_NORMAL -> dumpNormal(pw, parsedArgs)
+ else -> dumpParameterized(fd, pw, parsedArgs)
+ }
+
+ pw.println()
+ pw.println("Dump took ${SystemClock.uptimeMillis() - start}ms")
+ Trace.endSection()
+ }
+
+ private fun dumpParameterized(fd: FileDescriptor, pw: PrintWriter, args: ParsedArgs) {
+ when (args.command) {
+ "bugreport-critical" -> dumpCritical(fd, pw, args)
+ "bugreport-normal" -> dumpNormal(pw, args)
+ "dumpables" -> dumpDumpables(fd, pw, args)
+ "buffers" -> dumpBuffers(pw, args)
+ "config" -> dumpConfig(pw)
+ "help" -> dumpHelp(pw)
+ else -> dumpTargets(args.nonFlagArgs, fd, pw, args)
+ }
+ }
+
+ private fun dumpCritical(fd: FileDescriptor, pw: PrintWriter, args: ParsedArgs) {
+ dumpManager.dumpDumpables(fd, pw, args.rawArgs)
+ dumpConfig(pw)
+ }
+
+ private fun dumpNormal(pw: PrintWriter, args: ParsedArgs) {
+ dumpManager.dumpBuffers(pw, args.tailLength)
+ logBufferEulogizer.readEulogyIfPresent(pw)
+ }
+
+ private fun dumpDumpables(fw: FileDescriptor, pw: PrintWriter, args: ParsedArgs) {
+ if (args.listOnly) {
+ dumpManager.listDumpables(pw)
+ } else {
+ dumpManager.dumpDumpables(fw, pw, args.rawArgs)
+ }
+ }
+
+ private fun dumpBuffers(pw: PrintWriter, args: ParsedArgs) {
+ if (args.listOnly) {
+ dumpManager.listBuffers(pw)
+ } else {
+ dumpManager.dumpBuffers(pw, args.tailLength)
+ }
+ }
+
+ private fun dumpTargets(
+ targets: List<String>,
+ fd: FileDescriptor,
+ pw: PrintWriter,
+ args: ParsedArgs
+ ) {
+ if (targets.isNotEmpty()) {
+ for (target in targets) {
+ dumpManager.dumpTarget(target, fd, pw, args.rawArgs, args.tailLength)
+ }
+ } else {
+ if (args.listOnly) {
+ pw.println("Dumpables:")
+ dumpManager.listDumpables(pw)
+ pw.println()
+
+ pw.println("Buffers:")
+ dumpManager.listBuffers(pw)
+ } else {
+ pw.println("Nothing to dump :(")
+ }
+ }
+ }
+
+ private fun dumpConfig(pw: PrintWriter) {
+ pw.println("SystemUiServiceComponents configuration:")
+ pw.print("vendor component: ")
+ pw.println(context.resources.getString(R.string.config_systemUIVendorServiceComponent))
+ dumpServiceList(pw, "global", R.array.config_systemUIServiceComponents)
+ dumpServiceList(pw, "per-user", R.array.config_systemUIServiceComponentsPerUser)
+ }
+
+ private fun dumpServiceList(pw: PrintWriter, type: String, resId: Int) {
+ val services: Array<String>? = context.resources.getStringArray(resId)
+ pw.print(type)
+ pw.print(": ")
+ if (services == null) {
+ pw.println("N/A")
+ return
+ }
+ pw.print(services.size)
+ pw.println(" services")
+ for (i in services.indices) {
+ pw.print(" ")
+ pw.print(i)
+ pw.print(": ")
+ pw.println(services[i])
+ }
+ }
+
+ private fun dumpHelp(pw: PrintWriter) {
+ pw.println("Let <invocation> be:")
+ pw.println("$ adb shell dumpsys activity service com.android.systemui/.SystemUIService")
+ pw.println()
+
+ pw.println("Most common usage:")
+ pw.println("$ <invocation> <targets>")
+ pw.println("$ <invocation> NotifLog")
+ pw.println("$ <invocation> StatusBar FalsingManager BootCompleteCacheImpl")
+ pw.println("etc.")
+ pw.println()
+
+ pw.println("Special commands:")
+ pw.println("$ <invocation> dumpables")
+ pw.println("$ <invocation> buffers")
+ pw.println("$ <invocation> bugreport-critical")
+ pw.println("$ <invocation> bugreport-normal")
+ pw.println()
+
+ pw.println("Targets can be listed:")
+ pw.println("$ <invocation> --list")
+ pw.println("$ <invocation> dumpables --list")
+ pw.println("$ <invocation> buffers --list")
+ pw.println()
+
+ pw.println("Show only the most recent N lines of buffers")
+ pw.println("$ <invocation> NotifLog --tail 30")
+ }
+
+ private fun parseArgs(args: Array<String>): ParsedArgs {
+ val mutArgs = args.toMutableList()
+ val pArgs = ParsedArgs(args, mutArgs)
+
+ val iterator = mutArgs.iterator()
+ while (iterator.hasNext()) {
+ val arg = iterator.next()
+ if (arg.startsWith("-")) {
+ iterator.remove()
+ when (arg) {
+ PRIORITY_ARG -> {
+ pArgs.dumpPriority = readArgument(iterator, PRIORITY_ARG) {
+ if (PRIORITY_OPTIONS.contains(it)) {
+ it
+ } else {
+ throw IllegalArgumentException()
+ }
+ }
+ }
+ "-t", "--tail" -> {
+ pArgs.tailLength = readArgument(iterator, arg) {
+ it.toInt()
+ }
+ }
+ "-l", "--list" -> {
+ pArgs.listOnly = true
+ }
+ "-h", "--help" -> {
+ pArgs.command = "help"
+ }
+ else -> {
+ throw ArgParseException("Unknown flag: $arg")
+ }
+ }
+ }
+ }
+
+ if (pArgs.command == null && mutArgs.isNotEmpty() && COMMANDS.contains(mutArgs[0])) {
+ pArgs.command = mutArgs.removeAt(0)
+ }
+
+ return pArgs
+ }
+
+ private fun <T> readArgument(
+ iterator: MutableIterator<String>,
+ flag: String,
+ parser: (arg: String) -> T
+ ): T {
+ if (!iterator.hasNext()) {
+ throw ArgParseException("Missing argument for $flag")
+ }
+ val value = iterator.next()
+
+ return try {
+ parser(value).also { iterator.remove() }
+ } catch (e: Exception) {
+ throw ArgParseException("Invalid argument '$value' for flag $flag")
+ }
+ }
+
+ companion object {
+ const val PRIORITY_ARG = "--dump-priority"
+ const val PRIORITY_ARG_CRITICAL = "CRITICAL"
+ const val PRIORITY_ARG_HIGH = "HIGH"
+ const val PRIORITY_ARG_NORMAL = "NORMAL"
+ }
+}
+
+private val PRIORITY_OPTIONS =
+ arrayOf(PRIORITY_ARG_CRITICAL, PRIORITY_ARG_HIGH, PRIORITY_ARG_NORMAL)
+
+private val COMMANDS = arrayOf("bugreport-critical", "bugreport-normal", "buffers", "dumpables")
+
+private class ParsedArgs(
+ val rawArgs: Array<String>,
+ val nonFlagArgs: List<String>
+) {
+ var dumpPriority: String? = null
+ var tailLength: Int = 0
+ var command: String? = null
+ var listOnly = false
+}
+
+class ArgParseException(message: String) : Exception(message)
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index 59a7a32..a4141b1 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -16,15 +16,8 @@
package com.android.systemui.dump
-import android.content.Context
-import android.os.SystemClock
-import android.os.Trace
import android.util.ArrayMap
import com.android.systemui.Dumpable
-import com.android.systemui.R
-import com.android.systemui.dump.DumpManager.Companion.PRIORITY_ARG_CRITICAL
-import com.android.systemui.dump.DumpManager.Companion.PRIORITY_ARG_HIGH
-import com.android.systemui.dump.DumpManager.Companion.PRIORITY_ARG_NORMAL
import com.android.systemui.log.LogBuffer
import java.io.FileDescriptor
import java.io.PrintWriter
@@ -32,58 +25,16 @@
import javax.inject.Singleton
/**
- * Oversees SystemUI's output during bug reports (and dumpsys in general)
+ * Maintains a registry of things that should be dumped when a bug report is taken
*
* When a bug report is taken, SystemUI dumps various diagnostic information that we hope will be
* useful for the eventual readers of the bug report. Code that wishes to participate in this dump
* should register itself here.
*
- * Dump output is split into two sections, CRITICAL and NORMAL. All dumpables registered via
- * [registerDumpable] appear in the CRITICAL section, while all [LogBuffer]s appear in the NORMAL
- * section (due to their length).
- *
- * The CRITICAL and NORMAL sections can be found within a bug report by searching for
- * "SERVICE com.android.systemui/.SystemUIService" and
- * "SERVICE com.android.systemui/.dump.SystemUIAuxiliaryDumpService", respectively.
- *
- * Finally, some or all of the dump can be triggered on-demand via adb (see below).
- *
- * ```
- * # For the following, let <invocation> be:
- * $ adb shell dumpsys activity service com.android.systemui/.SystemUIService
- *
- * # To dump specific target(s), specify one or more registered names:
- * $ <invocation> NotifCollection
- * $ <invocation> StatusBar FalsingManager BootCompleteCacheImpl
- *
- * # Log buffers can be dumped in the same way (and can even be mixed in with other dump targets,
- * # although it's not clear why one would want such a thing):
- * $ <invocation> NotifLog
- * $ <invocation> StatusBar NotifLog BootCompleteCacheImpl
- *
- * # If passing -t or --tail, shows only the last N lines of any log buffers:
- * $ <invocation> NotifLog --tail 100
- *
- * # Dump targets are matched using String.endsWith(), so dumpables that register using their
- * # fully-qualified class name can still be dumped using their short name:
- * $ <invocation> com.android.keyguard.KeyguardUpdateMonitor
- * $ <invocation> keyguard.KeyguardUpdateMonitor
- * $ <invocation> KeyguardUpdateMonitor
- *
- * # To dump all dumpables or all buffers:
- * $ <invocation> dumpables
- * $ <invocation> buffers
- *
- * Finally, the following will simulate what we dump during the CRITICAL and NORMAL sections of a
- * bug report:
- * $ <invocation> bugreport-critical
- * $ <invocation> bugreport-normal
- * ```
+ * See [DumpHandler] for more information on how and when this information is dumped.
*/
@Singleton
-class DumpManager @Inject constructor(
- private val context: Context
-) {
+class DumpManager @Inject constructor() {
private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = ArrayMap()
private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = ArrayMap()
@@ -97,10 +48,6 @@
*/
@Synchronized
fun registerDumpable(name: String, module: Dumpable) {
- if (RESERVED_NAMES.contains(name)) {
- throw IllegalArgumentException("'$name' is reserved")
- }
-
if (!canAssignToNameLocked(name, module)) {
throw IllegalArgumentException("'$name' is already registered")
}
@@ -128,76 +75,16 @@
}
/**
- * Dump the diagnostics! Behavior can be controlled via [args].
+ * Dumps the first dumpable or buffer whose registered name ends with [target]
*/
@Synchronized
- fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
- Trace.beginSection("DumpManager#dump()")
- val start = SystemClock.uptimeMillis()
-
- val parsedArgs = try {
- parseArgs(args)
- } catch (e: ArgParseException) {
- pw.println(e.message)
- return
- }
-
- when (parsedArgs.dumpPriority) {
- PRIORITY_ARG_CRITICAL -> dumpCriticalLocked(fd, pw, parsedArgs)
- PRIORITY_ARG_NORMAL -> dumpNormalLocked(pw, parsedArgs)
- else -> dumpParameterizedLocked(fd, pw, parsedArgs)
- }
-
- pw.println()
- pw.println("Dump took ${SystemClock.uptimeMillis() - start}ms")
- Trace.endSection()
- }
-
- private fun dumpCriticalLocked(fd: FileDescriptor, pw: PrintWriter, args: ParsedArgs) {
- dumpDumpablesLocked(fd, pw, args)
- dumpConfig(pw)
- }
-
- private fun dumpNormalLocked(pw: PrintWriter, args: ParsedArgs) {
- dumpBuffersLocked(pw, args)
- }
-
- private fun dumpParameterizedLocked(fd: FileDescriptor, pw: PrintWriter, args: ParsedArgs) {
- when (args.command) {
- "bugreport-critical" -> dumpCriticalLocked(fd, pw, args)
- "bugreport-normal" -> dumpNormalLocked(pw, args)
- "dumpables" -> dumpDumpablesLocked(fd, pw, args)
- "buffers" -> dumpBuffersLocked(pw, args)
- else -> dumpTargetsLocked(args.nonFlagArgs, fd, pw, args)
- }
- }
-
- private fun dumpTargetsLocked(
- targets: List<String>,
- fd: FileDescriptor,
- pw: PrintWriter,
- args: ParsedArgs
- ) {
- if (targets.isEmpty()) {
- pw.println("Nothing to dump :(")
- } else {
- for (target in targets) {
- dumpTarget(target, fd, pw, args)
- }
- }
- }
-
- private fun dumpTarget(
+ fun dumpTarget(
target: String,
fd: FileDescriptor,
pw: PrintWriter,
- args: ParsedArgs
+ args: Array<String>,
+ tailLength: Int
) {
- if (target == "config") {
- dumpConfig(pw)
- return
- }
-
for (dumpable in dumpables.values) {
if (dumpable.name.endsWith(target)) {
dumpDumpable(dumpable, fd, pw, args)
@@ -207,21 +94,49 @@
for (buffer in buffers.values) {
if (buffer.name.endsWith(target)) {
- dumpBuffer(buffer, pw, args)
+ dumpBuffer(buffer, pw, tailLength)
return
}
}
}
- private fun dumpDumpablesLocked(fd: FileDescriptor, pw: PrintWriter, args: ParsedArgs) {
+ /**
+ * Dumps all registered dumpables to [pw]
+ */
+ @Synchronized
+ fun dumpDumpables(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
for (module in dumpables.values) {
dumpDumpable(module, fd, pw, args)
}
}
- private fun dumpBuffersLocked(pw: PrintWriter, args: ParsedArgs) {
+ /**
+ * Dumps the names of all registered dumpables (one per line)
+ */
+ @Synchronized
+ fun listDumpables(pw: PrintWriter) {
+ for (module in dumpables.values) {
+ pw.println(module.name)
+ }
+ }
+
+ /**
+ * Dumps all registered [LogBuffer]s to [pw]
+ */
+ @Synchronized
+ fun dumpBuffers(pw: PrintWriter, tailLength: Int) {
for (buffer in buffers.values) {
- dumpBuffer(buffer, pw, args)
+ dumpBuffer(buffer, pw, tailLength)
+ }
+ }
+
+ /**
+ * Dumps the names of all registered buffers (one per line)
+ */
+ @Synchronized
+ fun listBuffers(pw: PrintWriter) {
+ for (buffer in buffers.values) {
+ pw.println(buffer.name)
}
}
@@ -229,139 +144,33 @@
dumpable: RegisteredDumpable<Dumpable>,
fd: FileDescriptor,
pw: PrintWriter,
- args: ParsedArgs
+ args: Array<String>
) {
pw.println()
pw.println("${dumpable.name}:")
pw.println("----------------------------------------------------------------------------")
- dumpable.dumpable.dump(fd, pw, args.rawArgs)
+ dumpable.dumpable.dump(fd, pw, args)
}
private fun dumpBuffer(
buffer: RegisteredDumpable<LogBuffer>,
pw: PrintWriter,
- args: ParsedArgs
+ tailLength: Int
) {
pw.println()
pw.println()
pw.println("BUFFER ${buffer.name}:")
pw.println("============================================================================")
- buffer.dumpable.dump(pw, args.tailLength)
- }
-
- private fun dumpConfig(pw: PrintWriter) {
- pw.println("SystemUiServiceComponents configuration:")
- pw.print("vendor component: ")
- pw.println(context.resources.getString(R.string.config_systemUIVendorServiceComponent))
- dumpServiceList(pw, "global", R.array.config_systemUIServiceComponents)
- dumpServiceList(pw, "per-user", R.array.config_systemUIServiceComponentsPerUser)
- }
-
- private fun dumpServiceList(pw: PrintWriter, type: String, resId: Int) {
- val services: Array<String>? = context.resources.getStringArray(resId)
- pw.print(type)
- pw.print(": ")
- if (services == null) {
- pw.println("N/A")
- return
- }
- pw.print(services.size)
- pw.println(" services")
- for (i in services.indices) {
- pw.print(" ")
- pw.print(i)
- pw.print(": ")
- pw.println(services[i])
- }
- }
-
- private fun parseArgs(args: Array<String>): ParsedArgs {
- val mutArgs = args.toMutableList()
- val pArgs = ParsedArgs(args, mutArgs)
-
- val iterator = mutArgs.iterator()
- while (iterator.hasNext()) {
- val arg = iterator.next()
- if (arg.startsWith("-")) {
- iterator.remove()
- when (arg) {
- PRIORITY_ARG -> {
- pArgs.dumpPriority = readArgument(iterator, PRIORITY_ARG) {
- if (PRIORITY_OPTIONS.contains(it)) {
- it
- } else {
- throw IllegalArgumentException()
- }
- }
- }
- "-t", "--tail" -> {
- pArgs.tailLength = readArgument(iterator, "--tail") {
- it.toInt()
- }
- }
- else -> {
- throw ArgParseException("Unknown flag: $arg")
- }
- }
- }
- }
-
- if (mutArgs.isNotEmpty() && COMMANDS.contains(mutArgs[0])) {
- pArgs.command = mutArgs.removeAt(0)
- }
-
- return pArgs
- }
-
- private fun <T> readArgument(
- iterator: MutableIterator<String>,
- flag: String,
- parser: (arg: String) -> T
- ): T {
- if (!iterator.hasNext()) {
- throw ArgParseException("Missing argument for $flag")
- }
- val value = iterator.next()
-
- return try {
- parser(value).also { iterator.remove() }
- } catch (e: Exception) {
- throw ArgParseException("Invalid argument '$value' for flag $flag")
- }
+ buffer.dumpable.dump(pw, tailLength)
}
private fun canAssignToNameLocked(name: String, newDumpable: Any): Boolean {
val existingDumpable = dumpables[name]?.dumpable ?: buffers[name]?.dumpable
return existingDumpable == null || newDumpable == existingDumpable
}
-
- companion object {
- const val PRIORITY_ARG = "--dump-priority"
- const val PRIORITY_ARG_CRITICAL = "CRITICAL"
- const val PRIORITY_ARG_HIGH = "HIGH"
- const val PRIORITY_ARG_NORMAL = "NORMAL"
- }
}
-private val PRIORITY_OPTIONS =
- arrayOf(PRIORITY_ARG_CRITICAL, PRIORITY_ARG_HIGH, PRIORITY_ARG_NORMAL)
-
-private val COMMANDS = arrayOf("bugreport-critical", "bugreport-normal", "buffers", "dumpables")
-
-private val RESERVED_NAMES = arrayOf("config", *COMMANDS)
-
private data class RegisteredDumpable<T>(
val name: String,
val dumpable: T
)
-
-private class ParsedArgs(
- val rawArgs: Array<String>,
- val nonFlagArgs: List<String>
-) {
- var dumpPriority: String? = null
- var tailLength: Int = 0
- var command: String? = null
-}
-
-class ArgParseException(message: String) : Exception(message)
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt b/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
new file mode 100644
index 0000000..603cb67
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dump
+
+import android.content.Context
+import android.util.Log
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.util.io.Files
+import com.android.systemui.util.time.SystemClock
+import java.io.IOException
+import java.io.PrintWriter
+import java.io.UncheckedIOException
+import java.nio.file.Path
+import java.nio.file.Paths
+import java.nio.file.StandardOpenOption.CREATE
+import java.nio.file.StandardOpenOption.TRUNCATE_EXISTING
+import java.nio.file.attribute.BasicFileAttributes
+import java.text.SimpleDateFormat
+import java.util.Locale
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * Dumps all [LogBuffer]s to a file
+ *
+ * Intended for emergencies, i.e. we're about to crash. This file can then be read at a later date
+ * (usually in a bug report).
+ */
+@Singleton
+class LogBufferEulogizer(
+ private val dumpManager: DumpManager,
+ private val systemClock: SystemClock,
+ private val files: Files,
+ private val logPath: Path,
+ private val minWriteGap: Long,
+ private val maxLogAgeToDump: Long
+) {
+ @Inject constructor(
+ context: Context,
+ dumpManager: DumpManager,
+ systemClock: SystemClock,
+ files: Files
+ ) : this(
+ dumpManager,
+ systemClock,
+ files,
+ Paths.get(context.filesDir.toPath().toString(), "log_buffers.txt"),
+ MIN_WRITE_GAP,
+ MAX_AGE_TO_DUMP
+ )
+
+ /**
+ * Dumps all active log buffers to a file
+ *
+ * The file will be prefaced by the [reason], which will then be returned (presumably so it can
+ * be thrown).
+ */
+ fun <T : Exception> record(reason: T): T {
+ val start = systemClock.uptimeMillis()
+ var duration = 0L
+
+ Log.i(TAG, "Performing emergency dump of log buffers")
+
+ val millisSinceLastWrite = getMillisSinceLastWrite(logPath)
+ if (millisSinceLastWrite < minWriteGap) {
+ Log.w(TAG, "Cannot dump logs, last write was only $millisSinceLastWrite ms ago")
+ return reason
+ }
+
+ try {
+ val writer = files.newBufferedWriter(logPath, CREATE, TRUNCATE_EXISTING)
+ writer.use { out ->
+ val pw = PrintWriter(out)
+
+ pw.println(DATE_FORMAT.format(systemClock.currentTimeMillis()))
+ pw.println()
+ pw.println("Dump triggered by exception:")
+ reason.printStackTrace(pw)
+ dumpManager.dumpBuffers(pw, 0)
+ duration = systemClock.uptimeMillis() - start
+ pw.println()
+ pw.println("Buffer eulogy took ${duration}ms")
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Exception while attempting to dump buffers, bailing", e)
+ }
+
+ Log.i(TAG, "Buffer eulogy took ${duration}ms")
+
+ return reason
+ }
+
+ /**
+ * If a eulogy file is present, writes its contents to [pw].
+ */
+ fun readEulogyIfPresent(pw: PrintWriter) {
+ try {
+ val millisSinceLastWrite = getMillisSinceLastWrite(logPath)
+ if (millisSinceLastWrite > maxLogAgeToDump) {
+ Log.i(TAG, "Not eulogizing buffers; they are " +
+ TimeUnit.HOURS.convert(millisSinceLastWrite, TimeUnit.MILLISECONDS) +
+ " hours old")
+ return
+ }
+
+ files.lines(logPath).use { s ->
+ pw.println()
+ pw.println()
+ pw.println("=============== BUFFERS FROM MOST RECENT CRASH ===============")
+ s.forEach { line ->
+ pw.println(line)
+ }
+ }
+ } catch (e: IOException) {
+ // File doesn't exist, okay
+ } catch (e: UncheckedIOException) {
+ Log.e(TAG, "UncheckedIOException while dumping the core", e)
+ }
+ }
+
+ private fun getMillisSinceLastWrite(path: Path): Long {
+ val stats = try {
+ files.readAttributes(path, BasicFileAttributes::class.java)
+ } catch (e: IOException) {
+ // File doesn't exist
+ null
+ }
+ return systemClock.currentTimeMillis() - (stats?.lastModifiedTime()?.toMillis() ?: 0)
+ }
+}
+
+private const val TAG = "BufferEulogizer"
+private val MIN_WRITE_GAP = TimeUnit.MINUTES.toMillis(5)
+private val MAX_AGE_TO_DUMP = TimeUnit.HOURS.toMillis(48)
+private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/dump/SystemUIAuxiliaryDumpService.java b/packages/SystemUI/src/com/android/systemui/dump/SystemUIAuxiliaryDumpService.java
index 431cd63..da983ab 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/SystemUIAuxiliaryDumpService.java
+++ b/packages/SystemUI/src/com/android/systemui/dump/SystemUIAuxiliaryDumpService.java
@@ -35,11 +35,11 @@
* all other services.
*/
public class SystemUIAuxiliaryDumpService extends Service {
- private final DumpManager mDumpManager;
+ private final DumpHandler mDumpHandler;
@Inject
- public SystemUIAuxiliaryDumpService(DumpManager dumpManager) {
- mDumpManager = dumpManager;
+ public SystemUIAuxiliaryDumpService(DumpHandler dumpHandler) {
+ mDumpHandler = dumpHandler;
}
@Override
@@ -50,9 +50,9 @@
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
// Simulate the NORMAL priority arg being passed to us
- mDumpManager.dump(
+ mDumpHandler.dump(
fd,
pw,
- new String[] { DumpManager.PRIORITY_ARG, DumpManager.PRIORITY_ARG_NORMAL });
+ new String[] { DumpHandler.PRIORITY_ARG, DumpHandler.PRIORITY_ARG_NORMAL });
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index d69c3b0..c66a095 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -129,11 +129,8 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
import com.android.systemui.plugins.GlobalActionsPanelPlugin;
-import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.statusbar.BlurUtils;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
-import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.EmergencyDialerConstants;
@@ -167,19 +164,19 @@
/* Valid settings for global actions keys.
* see config.xml config_globalActionList */
@VisibleForTesting
- protected static final String GLOBAL_ACTION_KEY_POWER = "power";
- protected static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane";
- protected static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport";
- protected static final String GLOBAL_ACTION_KEY_SILENT = "silent";
- protected static final String GLOBAL_ACTION_KEY_USERS = "users";
- protected static final String GLOBAL_ACTION_KEY_SETTINGS = "settings";
- protected static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown";
- protected static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist";
- protected static final String GLOBAL_ACTION_KEY_ASSIST = "assist";
- protected static final String GLOBAL_ACTION_KEY_RESTART = "restart";
- protected static final String GLOBAL_ACTION_KEY_LOGOUT = "logout";
- protected static final String GLOBAL_ACTION_KEY_EMERGENCY = "emergency";
- protected static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot";
+ static final String GLOBAL_ACTION_KEY_POWER = "power";
+ private static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane";
+ private static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport";
+ private static final String GLOBAL_ACTION_KEY_SILENT = "silent";
+ private static final String GLOBAL_ACTION_KEY_USERS = "users";
+ private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings";
+ private static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown";
+ private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist";
+ private static final String GLOBAL_ACTION_KEY_ASSIST = "assist";
+ static final String GLOBAL_ACTION_KEY_RESTART = "restart";
+ private static final String GLOBAL_ACTION_KEY_LOGOUT = "logout";
+ static final String GLOBAL_ACTION_KEY_EMERGENCY = "emergency";
+ static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot";
private static final String PREFS_CONTROLS_SEEDING_COMPLETED = "ControlsSeedingCompleted";
private static final String PREFS_CONTROLS_FILE = "controls_prefs";
@@ -202,7 +199,6 @@
private final MetricsLogger mMetricsLogger;
private final UiEventLogger mUiEventLogger;
private final NotificationShadeDepthController mDepthController;
- private final BlurUtils mBlurUtils;
private final SysUiState mSysUiState;
// Used for RingerModeTracker
@@ -211,7 +207,7 @@
@VisibleForTesting
protected final ArrayList<Action> mItems = new ArrayList<>();
@VisibleForTesting
- protected final ArrayList<Action> mOverflowItems = new ArrayList<>();
+ final ArrayList<Action> mOverflowItems = new ArrayList<>();
@VisibleForTesting
protected ActionsDialog mDialog;
@@ -236,18 +232,18 @@
private final SysuiColorExtractor mSysuiColorExtractor;
private final IStatusBarService mStatusBarService;
private final NotificationShadeWindowController mNotificationShadeWindowController;
- private GlobalActionsPanelPlugin mPanelPlugin;
+ private GlobalActionsPanelPlugin mWalletPlugin;
private ControlsUiController mControlsUiController;
private final IWindowManager mIWindowManager;
private final Executor mBackgroundExecutor;
- private final ControlsListingController mControlsListingController;
private List<ControlsServiceInfo> mControlsServiceInfos = new ArrayList<>();
private ControlsController mControlsController;
private SharedPreferences mControlsPreferences;
private final RingerModeTracker mRingerModeTracker;
private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms
private Handler mMainHandler;
- private boolean mShowLockScreenCardsAndControls = false;
+ @VisibleForTesting
+ boolean mShowLockScreenCardsAndControls = false;
@VisibleForTesting
public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum {
@@ -299,7 +295,7 @@
TrustManager trustManager, IActivityManager iActivityManager,
@Nullable TelecomManager telecomManager, MetricsLogger metricsLogger,
NotificationShadeDepthController depthController, SysuiColorExtractor colorExtractor,
- IStatusBarService statusBarService, BlurUtils blurUtils,
+ IStatusBarService statusBarService,
NotificationShadeWindowController notificationShadeWindowController,
ControlsUiController controlsUiController, IWindowManager iWindowManager,
@Background Executor backgroundExecutor,
@@ -330,8 +326,6 @@
mControlsUiController = controlsUiController;
mIWindowManager = iWindowManager;
mBackgroundExecutor = backgroundExecutor;
- mControlsListingController = controlsListingController;
- mBlurUtils = blurUtils;
mRingerModeTracker = ringerModeTracker;
mControlsController = controlsController;
mSysUiState = sysUiState;
@@ -372,20 +366,21 @@
@Override
public void onUnlockedChanged() {
if (mDialog != null) {
- if (mDialog.mPanelController != null) {
- mDialog.mPanelController.onDeviceLockStateChanged(
- !mKeyguardStateController.isUnlocked());
+ boolean unlocked = mKeyguardStateController.isUnlocked();
+ if (mDialog.mWalletViewController != null) {
+ mDialog.mWalletViewController.onDeviceLockStateChanged(!unlocked);
}
if (!mDialog.isShowingControls() && shouldShowControls()) {
mDialog.showControls(mControlsUiController);
}
+ if (unlocked) {
+ mDialog.hideLockMessage();
+ }
}
}
});
- mControlsListingController.addCallback(list -> {
- mControlsServiceInfos = list;
- });
+ controlsListingController.addCallback(list -> mControlsServiceInfos = list);
// Need to be user-specific with the context to make sure we read the correct prefs
Context userContext = context.createContextAsUser(
@@ -450,10 +445,10 @@
* @param keyguardShowing True if keyguard is showing
*/
public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned,
- GlobalActionsPanelPlugin panelPlugin) {
+ GlobalActionsPanelPlugin walletPlugin) {
mKeyguardShowing = keyguardShowing;
mDeviceProvisioned = isDeviceProvisioned;
- mPanelPlugin = panelPlugin;
+ mWalletPlugin = walletPlugin;
if (mDialog != null && mDialog.isShowing()) {
// In order to force global actions to hide on the same affordance press, we must
// register a call to onGlobalActionsShown() first to prevent the default actions
@@ -520,11 +515,7 @@
*/
@VisibleForTesting
protected int getMaxShownPowerItems() {
- if (shouldUseControlsLayout()) {
- return mResources.getInteger(com.android.systemui.R.integer.power_menu_max_columns);
- } else {
- return Integer.MAX_VALUE;
- }
+ return mResources.getInteger(com.android.systemui.R.integer.power_menu_max_columns);
}
/**
@@ -593,9 +584,10 @@
} else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
addActionItem(getSettingsAction());
} else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
+ int userId = getCurrentUser().id;
if (Settings.Secure.getIntForUser(mContentResolver,
- Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0, getCurrentUser().id) != 0
- && shouldDisplayLockdown()) {
+ Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0, userId) != 0
+ && shouldDisplayLockdown(userId)) {
addActionItem(getLockdownAction());
}
} else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {
@@ -639,12 +631,19 @@
mAdapter = new MyAdapter();
mOverflowAdapter = new MyOverflowAdapter();
- mDepthController.setShowingHomeControls(shouldUseControlsLayout());
+ mDepthController.setShowingHomeControls(true);
+ GlobalActionsPanelPlugin.PanelViewController walletViewController =
+ getWalletViewController();
ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, mOverflowAdapter,
- getWalletPanelViewController(), mDepthController, mSysuiColorExtractor,
+ walletViewController, mDepthController, mSysuiColorExtractor,
mStatusBarService, mNotificationShadeWindowController,
- shouldShowControls() ? mControlsUiController : null, mBlurUtils, mSysUiState,
- shouldUseControlsLayout(), this::onRotate, mKeyguardShowing);
+ controlsAvailable(), shouldShowControls() ? mControlsUiController : null,
+ mSysUiState, this::onRotate, mKeyguardShowing);
+ boolean walletViewAvailable = walletViewController != null
+ && walletViewController.getPanelContent() != null;
+ if (shouldShowLockMessage(walletViewAvailable)) {
+ dialog.showLockMessage();
+ }
dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
dialog.setOnDismissListener(this);
dialog.setOnShowListener(this);
@@ -652,13 +651,12 @@
return dialog;
}
- private boolean shouldDisplayLockdown() {
+ private boolean shouldDisplayLockdown(int userId) {
// Lockdown is meaningless without a place to go.
if (!mKeyguardStateController.isMethodSecure()) {
return false;
}
- int userId = getCurrentUser().id;
// Only show the lockdown button if the device isn't locked down (for whatever reason).
int state = mLockPatternUtils.getStrongAuthForUser(userId);
return (state == STRONG_AUTH_NOT_REQUIRED
@@ -678,11 +676,11 @@
}
@Nullable
- private GlobalActionsPanelPlugin.PanelViewController getWalletPanelViewController() {
- if (mPanelPlugin == null) {
+ private GlobalActionsPanelPlugin.PanelViewController getWalletViewController() {
+ if (mWalletPlugin == null) {
return null;
}
- return mPanelPlugin.onPanelShown(this, !mKeyguardStateController.isUnlocked());
+ return mWalletPlugin.onPanelShown(this, !mKeyguardStateController.isUnlocked());
}
/**
@@ -743,7 +741,7 @@
@Override
public boolean shouldBeSeparated() {
- return !shouldUseControlsLayout();
+ return false;
}
@Override
@@ -751,18 +749,10 @@
Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
View v = super.create(context, convertView, parent, inflater);
int textColor;
- if (shouldUseControlsLayout()) {
- v.setBackgroundTintList(ColorStateList.valueOf(v.getResources().getColor(
- com.android.systemui.R.color.global_actions_emergency_background)));
- textColor = v.getResources().getColor(
- com.android.systemui.R.color.global_actions_emergency_text);
- } else if (shouldBeSeparated()) {
- textColor = v.getResources().getColor(
- com.android.systemui.R.color.global_actions_alert_text);
- } else {
- textColor = v.getResources().getColor(
- com.android.systemui.R.color.global_actions_text);
- }
+ v.setBackgroundTintList(ColorStateList.valueOf(v.getResources().getColor(
+ com.android.systemui.R.color.global_actions_emergency_background)));
+ textColor = v.getResources().getColor(
+ com.android.systemui.R.color.global_actions_emergency_text);
TextView messageView = v.findViewById(R.id.message);
messageView.setTextColor(textColor);
messageView.setSelected(true); // necessary for marquee to work
@@ -1192,13 +1182,6 @@
mUiEventLogger.log(GlobalActionsEvent.GA_POWER_MENU_OPEN);
}
- private int getActionLayoutId() {
- if (shouldUseControlsLayout()) {
- return com.android.systemui.R.layout.global_actions_grid_item_v2;
- }
- return com.android.systemui.R.layout.global_actions_grid_item;
- }
-
/**
* The adapter used for power menu items shown in the global actions dialog.
*/
@@ -1492,7 +1475,8 @@
public View create(
Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
- View v = inflater.inflate(getActionLayoutId(), parent, false /* attach */);
+ View v = inflater.inflate(com.android.systemui.R.layout.global_actions_grid_item_v2,
+ parent, false /* attach */);
ImageView icon = v.findViewById(R.id.icon);
TextView messageView = v.findViewById(R.id.message);
@@ -1598,7 +1582,8 @@
LayoutInflater inflater) {
willCreate();
- View v = inflater.inflate(getActionLayoutId(), parent, false /* attach */);
+ View v = inflater.inflate(com.android.systemui.R.layout.global_actions_grid_item_v2,
+ parent, false /* attach */);
ImageView icon = (ImageView) v.findViewById(R.id.icon);
TextView messageView = (TextView) v.findViewById(R.id.message);
@@ -1905,7 +1890,8 @@
return mLifecycle;
}
- private static final class ActionsDialog extends Dialog implements DialogInterface,
+ @VisibleForTesting
+ static final class ActionsDialog extends Dialog implements DialogInterface,
ColorExtractor.OnColorsChangedListener {
private final Context mContext;
@@ -1916,7 +1902,7 @@
private MultiListLayout mGlobalActionsLayout;
private Drawable mBackgroundDrawable;
private final SysuiColorExtractor mColorExtractor;
- private final GlobalActionsPanelPlugin.PanelViewController mPanelController;
+ private final GlobalActionsPanelPlugin.PanelViewController mWalletViewController;
private boolean mKeyguardShowing;
private boolean mShowing;
private float mScrimAlpha;
@@ -1924,24 +1910,24 @@
private boolean mHadTopUi;
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final NotificationShadeDepthController mDepthController;
- private final BlurUtils mBlurUtils;
private final SysUiState mSysUiState;
- private final boolean mUseControlsLayout;
private ListPopupWindow mOverflowPopup;
private final Runnable mOnRotateCallback;
+ private final boolean mControlsAvailable;
private ControlsUiController mControlsUiController;
private ViewGroup mControlsView;
private ViewGroup mContainer;
+ @VisibleForTesting ViewGroup mLockMessageContainer;
+ private TextView mLockMessage;
ActionsDialog(Context context, MyAdapter adapter, MyOverflowAdapter overflowAdapter,
- GlobalActionsPanelPlugin.PanelViewController plugin,
+ GlobalActionsPanelPlugin.PanelViewController walletViewController,
NotificationShadeDepthController depthController,
SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
NotificationShadeWindowController notificationShadeWindowController,
- ControlsUiController controlsUiController, BlurUtils blurUtils,
- SysUiState sysuiState, boolean useControlsLayout, Runnable onRotateCallback,
- boolean keyguardShowing) {
+ boolean controlsAvailable, @Nullable ControlsUiController controlsUiController,
+ SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing) {
super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
mContext = context;
mAdapter = adapter;
@@ -1950,10 +1936,9 @@
mColorExtractor = sysuiColorExtractor;
mStatusBarService = statusBarService;
mNotificationShadeWindowController = notificationShadeWindowController;
+ mControlsAvailable = controlsAvailable;
mControlsUiController = controlsUiController;
- mBlurUtils = blurUtils;
mSysUiState = sysuiState;
- mUseControlsLayout = useControlsLayout;
mOnRotateCallback = onRotateCallback;
mKeyguardShowing = keyguardShowing;
@@ -1978,7 +1963,7 @@
window.getAttributes().setFitInsetsTypes(0 /* types */);
setTitle(R.string.global_actions);
- mPanelController = plugin;
+ mWalletViewController = walletViewController;
initializeLayout();
}
@@ -1991,11 +1976,11 @@
mControlsUiController.show(mControlsView, this::dismissForControlsActivity);
}
- private boolean shouldUsePanel() {
- return mPanelController != null && mPanelController.getPanelContent() != null;
- }
+ private void initializeWalletView() {
+ if (mWalletViewController == null || mWalletViewController.getPanelContent() == null) {
+ return;
+ }
- private void initializePanel() {
int rotation = RotationUtils.getRotation(mContext);
boolean rotationLocked = RotationPolicy.isRotationLocked(mContext);
if (rotation != RotationUtils.ROTATION_NONE) {
@@ -2032,12 +2017,16 @@
setRotationSuggestionsEnabled(false);
FrameLayout panelContainer =
- findViewById(com.android.systemui.R.id.global_actions_panel_container);
+ findViewById(com.android.systemui.R.id.global_actions_wallet);
FrameLayout.LayoutParams panelParams =
new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT);
- View walletView = mPanelController.getPanelContent();
+ if (!mControlsAvailable) {
+ panelParams.topMargin = mContext.getResources().getDimensionPixelSize(
+ com.android.systemui.R.dimen.global_actions_wallet_top_margin);
+ }
+ View walletView = mWalletViewController.getPanelContent();
panelContainer.addView(walletView, panelParams);
// Smooth transitions when wallet is resized, which can happen when a card is added
ViewGroup root = findViewById(com.android.systemui.R.id.global_actions_grid_root);
@@ -2077,7 +2066,7 @@
}
private void initializeLayout() {
- setContentView(getGlobalActionsLayoutId(mContext));
+ setContentView(com.android.systemui.R.layout.global_actions_grid_v2);
fixNavBarClipping();
mControlsView = findViewById(com.android.systemui.R.id.global_actions_controls);
mGlobalActionsLayout = findViewById(com.android.systemui.R.id.global_actions_view);
@@ -2093,10 +2082,9 @@
mGlobalActionsLayout.setRotationListener(this::onRotate);
mGlobalActionsLayout.setAdapter(mAdapter);
mContainer = findViewById(com.android.systemui.R.id.global_actions_container);
- // Some legacy dialog layouts don't have the outer container
- if (mContainer == null) {
- mContainer = mGlobalActionsLayout;
- }
+ mLockMessageContainer = requireViewById(
+ com.android.systemui.R.id.global_actions_lock_message_container);
+ mLockMessage = requireViewById(com.android.systemui.R.id.global_actions_lock_message);
View overflowButton = findViewById(
com.android.systemui.R.id.global_actions_overflow_button);
@@ -2117,17 +2105,10 @@
}
}
- if (shouldUsePanel()) {
- initializePanel();
- }
+ initializeWalletView();
if (mBackgroundDrawable == null) {
mBackgroundDrawable = new ScrimDrawable();
- if (mUseControlsLayout) {
- mScrimAlpha = 1.0f;
- } else {
- mScrimAlpha = mBlurUtils.supportsBlursOnWindows()
- ? ScrimController.BLUR_SCRIM_ALPHA : ScrimController.BUSY_SCRIM_ALPHA;
- }
+ mScrimAlpha = 1.0f;
}
getWindow().setBackgroundDrawable(mBackgroundDrawable);
}
@@ -2141,29 +2122,6 @@
contentParent.setClipToPadding(false);
}
- private int getGlobalActionsLayoutId(Context context) {
- if (mUseControlsLayout) {
- return com.android.systemui.R.layout.global_actions_grid_v2;
- }
-
- int rotation = RotationUtils.getRotation(context);
- boolean useGridLayout = isForceGridEnabled(context)
- || (shouldUsePanel() && rotation == RotationUtils.ROTATION_NONE);
- if (rotation == RotationUtils.ROTATION_SEASCAPE) {
- if (useGridLayout) {
- return com.android.systemui.R.layout.global_actions_grid_seascape;
- } else {
- return com.android.systemui.R.layout.global_actions_column_seascape;
- }
- } else {
- if (useGridLayout) {
- return com.android.systemui.R.layout.global_actions_grid;
- } else {
- return com.android.systemui.R.layout.global_actions_column;
- }
- }
- }
-
@Override
protected void onStart() {
super.setCanceledOnTouchOutside(true);
@@ -2187,9 +2145,7 @@
if (!(mBackgroundDrawable instanceof ScrimDrawable)) {
return;
}
- ((ScrimDrawable) mBackgroundDrawable).setColor(
- !mUseControlsLayout && colors.supportsDarkText()
- ? Color.WHITE : Color.BLACK, animate);
+ ((ScrimDrawable) mBackgroundDrawable).setColor(Color.BLACK, animate);
View decorView = getWindow().getDecorView();
if (colors.supportsDarkText()) {
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR |
@@ -2216,12 +2172,10 @@
ViewGroup root = (ViewGroup) mGlobalActionsLayout.getRootView();
root.setOnApplyWindowInsetsListener((v, windowInsets) -> {
- if (mUseControlsLayout) {
- root.setPadding(windowInsets.getStableInsetLeft(),
- windowInsets.getStableInsetTop(),
- windowInsets.getStableInsetRight(),
- windowInsets.getStableInsetBottom());
- }
+ root.setPadding(windowInsets.getStableInsetLeft(),
+ windowInsets.getStableInsetTop(),
+ windowInsets.getStableInsetRight(),
+ windowInsets.getStableInsetBottom());
return WindowInsets.CONSUMED;
});
if (mControlsUiController != null) {
@@ -2308,7 +2262,7 @@
private void completeDismiss() {
mShowing = false;
resetOrientation();
- dismissPanel();
+ dismissWallet();
dismissOverflow(true);
if (mControlsUiController != null) mControlsUiController.hide();
mNotificationShadeWindowController.setForceHasTopUi(mHadTopUi);
@@ -2318,9 +2272,9 @@
super.dismiss();
}
- private void dismissPanel() {
- if (mPanelController != null) {
- mPanelController.onDismissed();
+ private void dismissWallet() {
+ if (mWalletViewController != null) {
+ mWalletViewController.onDismissed();
}
}
@@ -2375,7 +2329,7 @@
public void refreshDialog() {
// ensure dropdown menus are dismissed before re-initializing the dialog
- dismissPanel();
+ dismissWallet();
dismissOverflow(true);
if (mControlsUiController != null) {
mControlsUiController.hide();
@@ -2396,6 +2350,25 @@
}
}
+ void hideLockMessage() {
+ if (mLockMessageContainer.getVisibility() == View.VISIBLE) {
+ mLockMessageContainer.animate().alpha(0).setDuration(150).setListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mLockMessageContainer.setVisibility(View.GONE);
+ }
+ }).start();
+ }
+ }
+
+ void showLockMessage() {
+ Drawable lockIcon = mContext.getDrawable(com.android.internal.R.drawable.ic_lock);
+ lockIcon.setTint(mContext.getColor(com.android.systemui.R.color.control_primary_text));
+ mLockMessage.setCompoundDrawablesWithIntrinsicBounds(null, lockIcon, null, null);
+ mLockMessageContainer.setVisibility(View.VISIBLE);
+ }
+
private static class ResetOrientationData {
public boolean locked;
public int rotation;
@@ -2418,17 +2391,21 @@
return isPanelDebugModeEnabled(context);
}
- @VisibleForTesting
- protected boolean shouldShowControls() {
+ private boolean shouldShowControls() {
return (mKeyguardStateController.isUnlocked() || mShowLockScreenCardsAndControls)
- && mControlsUiController.getAvailable()
- && !mControlsServiceInfos.isEmpty()
- && mDeviceProvisioned;
+ && controlsAvailable();
}
- // TODO: Remove legacy layout XML and classes.
- protected boolean shouldUseControlsLayout() {
- // always use new controls layout
- return true;
+
+ private boolean controlsAvailable() {
+ return mDeviceProvisioned
+ && mControlsUiController.getAvailable()
+ && !mControlsServiceInfos.isEmpty();
+ }
+
+ private boolean shouldShowLockMessage(boolean walletViewAvailable) {
+ return !mKeyguardStateController.isUnlocked()
+ && !mShowLockScreenCardsAndControls
+ && (controlsAvailable() || walletViewAvailable);
}
private void onPowerMenuLockScreenSettingsChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index 09757a4..b55b29a 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -53,7 +53,7 @@
private final Lazy<GlobalActionsDialog> mGlobalActionsDialogLazy;
private final KeyguardStateController mKeyguardStateController;
private final DeviceProvisionedController mDeviceProvisionedController;
- private final ExtensionController.Extension<GlobalActionsPanelPlugin> mPanelExtension;
+ private final ExtensionController.Extension<GlobalActionsPanelPlugin> mWalletPluginProvider;
private final BlurUtils mBlurUtils;
private final CommandQueue mCommandQueue;
private GlobalActionsDialog mGlobalActionsDialog;
@@ -69,7 +69,7 @@
mCommandQueue = commandQueue;
mBlurUtils = blurUtils;
mCommandQueue.addCallback(this);
- mPanelExtension = Dependency.get(ExtensionController.class)
+ mWalletPluginProvider = Dependency.get(ExtensionController.class)
.newExtension(GlobalActionsPanelPlugin.class)
.withPlugin(GlobalActionsPanelPlugin.class)
.build();
@@ -90,7 +90,7 @@
mGlobalActionsDialog = mGlobalActionsDialogLazy.get();
mGlobalActionsDialog.showOrHideDialog(mKeyguardStateController.isShowing(),
mDeviceProvisionedController.isDeviceProvisioned(),
- mPanelExtension.get());
+ mWalletPluginProvider.get());
Dependency.get(KeyguardUpdateMonitor.class).requestFaceAuth();
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
index 7defef9..342db34 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
@@ -209,4 +209,4 @@
}
private const val TAG = "LogBuffer"
-private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.S", Locale.US)
+private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
diff --git a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
index 524c695..85e1c6b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
@@ -18,6 +18,7 @@
import android.view.View
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.stack.MediaHeaderView
@@ -33,7 +34,8 @@
class KeyguardMediaController @Inject constructor(
private val mediaHost: MediaHost,
private val bypassController: KeyguardBypassController,
- private val statusBarStateController: SysuiStatusBarStateController
+ private val statusBarStateController: SysuiStatusBarStateController,
+ private val notifLockscreenUserManager: NotificationLockscreenUserManager
) {
init {
@@ -61,11 +63,12 @@
}
private fun updateVisibility() {
- val shouldBeVisible = mediaHost.visible
- && !bypassController.bypassEnabled
- && (statusBarStateController.state == StatusBarState.KEYGUARD ||
- statusBarStateController.state == StatusBarState.FULLSCREEN_USER_SWITCHER)
+ val keyguardOrUserSwitcher = (statusBarStateController.state == StatusBarState.KEYGUARD ||
+ statusBarStateController.state == StatusBarState.FULLSCREEN_USER_SWITCHER)
+ val shouldBeVisible = mediaHost.visible &&
+ !bypassController.bypassEnabled &&
+ keyguardOrUserSwitcher &&
+ notifLockscreenUserManager.shouldShowLockscreenNotifications()
view?.visibility = if (shouldBeVisible) View.VISIBLE else View.GONE
}
-}
-
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/LocalMediaManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/LocalMediaManagerFactory.kt
new file mode 100644
index 0000000..94a0835
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/LocalMediaManagerFactory.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import android.content.Context
+
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.media.InfoMediaManager
+import com.android.settingslib.media.LocalMediaManager
+
+import javax.inject.Inject
+
+/**
+ * Factory to create [LocalMediaManager] objects.
+ */
+class LocalMediaManagerFactory @Inject constructor(
+ private val context: Context,
+ private val localBluetoothManager: LocalBluetoothManager?
+) {
+ /** Creates a [LocalMediaManager] for the given package. */
+ fun create(packageName: String): LocalMediaManager {
+ return InfoMediaManager(context, packageName, null, localBluetoothManager).run {
+ LocalMediaManager(context, localBluetoothManager, this, packageName)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 1691c53..90054d6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -41,7 +41,6 @@
import android.view.View;
import android.widget.ImageButton;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
@@ -56,14 +55,11 @@
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
import com.android.settingslib.Utils;
-import com.android.settingslib.media.LocalMediaManager;
-import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.media.MediaOutputSliceConstants;
import com.android.settingslib.widget.AdaptiveIcon;
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSMediaBrowser;
-import com.android.systemui.util.Assert;
import com.android.systemui.util.concurrency.DelayableExecutor;
import org.jetbrains.annotations.NotNull;
@@ -77,7 +73,6 @@
*/
public class MediaControlPanel {
private static final String TAG = "MediaControlPanel";
- @Nullable private final LocalMediaManager mLocalMediaManager;
// Button IDs for QS controls
static final int[] ACTION_IDS = {
@@ -100,7 +95,6 @@
private MediaSession.Token mToken;
private MediaController mController;
private int mBackgroundColor;
- private MediaDevice mDevice;
protected ComponentName mServiceComponent;
private boolean mIsRegistered = false;
private List<KeyFrames> mKeyFrames;
@@ -113,7 +107,6 @@
public static final String MEDIA_PREFERENCE_KEY = "browser_components";
private SharedPreferences mSharedPrefs;
private boolean mCheckedForResumption = false;
- private boolean mIsRemotePlayback;
private QSMediaBrowser mQSMediaBrowser;
private final MediaController.Callback mSessionCallback = new MediaController.Callback() {
@@ -122,7 +115,6 @@
Log.d(TAG, "session destroyed");
mController.unregisterCallback(mSessionCallback);
clearControls();
- makeInactive();
}
@Override
public void onPlaybackStateChanged(PlaybackState state) {
@@ -130,31 +122,6 @@
if (s == PlaybackState.STATE_NONE) {
Log.d(TAG, "playback state change will trigger resumption, state=" + state);
clearControls();
- makeInactive();
- }
- }
- };
-
- private final LocalMediaManager.DeviceCallback mDeviceCallback =
- new LocalMediaManager.DeviceCallback() {
- @Override
- public void onDeviceListUpdate(List<MediaDevice> devices) {
- if (mLocalMediaManager == null) {
- return;
- }
- MediaDevice currentDevice = mLocalMediaManager.getCurrentConnectedDevice();
- // Check because this can be called several times while changing devices
- if (mDevice == null || !mDevice.equals(currentDevice)) {
- mDevice = currentDevice;
- updateDevice(mDevice);
- }
- }
-
- @Override
- public void onSelectedDeviceStateChanged(MediaDevice device, int state) {
- if (mDevice == null || !mDevice.equals(device)) {
- mDevice = device;
- updateDevice(mDevice);
}
}
};
@@ -162,16 +129,13 @@
/**
* Initialize a new control panel
* @param context
- * @param routeManager Manager used to listen for device change events.
* @param foregroundExecutor foreground executor
* @param backgroundExecutor background executor, used for processing artwork
* @param activityStarter activity starter
*/
- public MediaControlPanel(Context context, @Nullable LocalMediaManager routeManager,
- Executor foregroundExecutor, DelayableExecutor backgroundExecutor,
- ActivityStarter activityStarter) {
+ public MediaControlPanel(Context context, Executor foregroundExecutor,
+ DelayableExecutor backgroundExecutor, ActivityStarter activityStarter) {
mContext = context;
- mLocalMediaManager = routeManager;
mForegroundExecutor = foregroundExecutor;
mBackgroundExecutor = backgroundExecutor;
mActivityStarter = activityStarter;
@@ -183,7 +147,7 @@
if (mSeekBarObserver != null) {
mSeekBarViewModel.getProgress().removeObserver(mSeekBarObserver);
}
- makeInactive();
+ mSeekBarViewModel.onDestroy();
}
private void loadDimens() {
@@ -228,7 +192,7 @@
mLayoutAnimationHelper = new LayoutAnimationHelper(motionView);
GoneChildrenHideHelper.clipGoneChildrenOnLayout(motionView);
mKeyFrames = motionView.getDefinedTransitions().get(0).getKeyFrameList();
- mSeekBarObserver = new SeekBarObserver(motionView);
+ mSeekBarObserver = new SeekBarObserver(vh);
mSeekBarViewModel.getProgress().observeForever(mSeekBarObserver);
SeekBar bar = vh.getSeekBar();
bar.setOnSeekBarChangeListener(mSeekBarViewModel.getSeekBarListener());
@@ -318,30 +282,67 @@
artistText.setText(data.getArtist());
// Transfer chip
- if (mLocalMediaManager != null) {
- mViewHolder.getSeamless().setVisibility(View.VISIBLE);
- setVisibleAndAlpha(collapsedSet, R.id.media_seamless, true /*visible */);
- setVisibleAndAlpha(expandedSet, R.id.media_seamless, true /*visible */);
- updateDevice(mLocalMediaManager.getCurrentConnectedDevice());
- mViewHolder.getSeamless().setOnClickListener(v -> {
- final Intent intent = new Intent()
- .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
- .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
- mController.getPackageName())
- .putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN, mToken);
- mActivityStarter.startActivity(intent, false, true /* dismissShade */,
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- });
- } else {
- Log.d(TAG, "LocalMediaManager is null. Not binding output chip for pkg=" + pkgName);
- }
+ mViewHolder.getSeamless().setVisibility(View.VISIBLE);
+ setVisibleAndAlpha(collapsedSet, R.id.media_seamless, true /*visible */);
+ setVisibleAndAlpha(expandedSet, R.id.media_seamless, true /*visible */);
+ mViewHolder.getSeamless().setOnClickListener(v -> {
+ final Intent intent = new Intent()
+ .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
+ .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
+ mController.getPackageName())
+ .putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN, mToken);
+ mActivityStarter.startActivity(intent, false, true /* dismissShade */,
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ });
+ final boolean isRemotePlayback;
PlaybackInfo playbackInfo = mController.getPlaybackInfo();
if (playbackInfo != null) {
- mIsRemotePlayback = playbackInfo.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
+ isRemotePlayback = playbackInfo.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
} else {
Log.d(TAG, "PlaybackInfo was null. Defaulting to local playback.");
- mIsRemotePlayback = false;
+ isRemotePlayback = false;
}
+
+ ImageView iconView = mViewHolder.getSeamlessIcon();
+ TextView deviceName = mViewHolder.getSeamlessText();
+
+ // Update the outline color
+ RippleDrawable bkgDrawable = (RippleDrawable) mViewHolder.getSeamless().getBackground();
+ GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0);
+ rect.setStroke(2, deviceName.getCurrentTextColor());
+ rect.setColor(Color.TRANSPARENT);
+
+ if (isRemotePlayback) {
+ mViewHolder.getSeamless().setEnabled(false);
+ // TODO(b/156875717): setEnabled should cause the alpha to change.
+ mViewHolder.getSeamless().setAlpha(0.38f);
+ iconView.setImageResource(R.drawable.ic_hardware_speaker);
+ iconView.setVisibility(View.VISIBLE);
+ deviceName.setText(R.string.media_seamless_remote_device);
+ } else if (data.getDevice() != null && data.getDevice().getIcon() != null
+ && data.getDevice().getName() != null) {
+ mViewHolder.getSeamless().setEnabled(true);
+ mViewHolder.getSeamless().setAlpha(1f);
+ Drawable icon = data.getDevice().getIcon();
+ iconView.setVisibility(View.VISIBLE);
+
+ if (icon instanceof AdaptiveIcon) {
+ AdaptiveIcon aIcon = (AdaptiveIcon) icon;
+ aIcon.setBackgroundColor(mBackgroundColor);
+ iconView.setImageDrawable(aIcon);
+ } else {
+ iconView.setImageDrawable(icon);
+ }
+ deviceName.setText(data.getDevice().getName());
+ } else {
+ // Reset to default
+ Log.w(TAG, "device is null. Not binding output chip.");
+ mViewHolder.getSeamless().setEnabled(true);
+ mViewHolder.getSeamless().setAlpha(1f);
+ iconView.setVisibility(View.GONE);
+ deviceName.setText(com.android.internal.R.string.ext_media_seamless_action);
+ }
+
List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact();
// Media controls
int i = 0;
@@ -382,8 +383,6 @@
// Set up long press menu
// TODO: b/156036025 bring back media guts
- makeActive();
-
// Update both constraint sets to regenerate the animation.
mViewHolder.getPlayer().updateState(R.id.collapsed, collapsedSet);
mViewHolder.getPlayer().updateState(R.id.expanded, expandedSet);
@@ -515,60 +514,6 @@
}
/**
- * Update the current device information
- * @param device device information to display
- */
- private void updateDevice(MediaDevice device) {
- mForegroundExecutor.execute(() -> {
- updateChipInternal(device);
- });
- }
-
- private void updateChipInternal(MediaDevice device) {
- if (mViewHolder == null) {
- return;
- }
- ImageView iconView = mViewHolder.getSeamlessIcon();
- TextView deviceName = mViewHolder.getSeamlessText();
-
- // Update the outline color
- LinearLayout viewLayout = (LinearLayout) mViewHolder.getSeamless();
- RippleDrawable bkgDrawable = (RippleDrawable) viewLayout.getBackground();
- GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0);
- rect.setStroke(2, deviceName.getCurrentTextColor());
- rect.setColor(Color.TRANSPARENT);
-
- if (mIsRemotePlayback) {
- mViewHolder.getSeamless().setEnabled(false);
- mViewHolder.getSeamless().setAlpha(0.38f);
- iconView.setImageResource(R.drawable.ic_hardware_speaker);
- iconView.setVisibility(View.VISIBLE);
- deviceName.setText(R.string.media_seamless_remote_device);
- } else if (device != null) {
- mViewHolder.getSeamless().setEnabled(true);
- mViewHolder.getSeamless().setAlpha(1f);
- Drawable icon = device.getIcon();
- iconView.setVisibility(View.VISIBLE);
-
- if (icon instanceof AdaptiveIcon) {
- AdaptiveIcon aIcon = (AdaptiveIcon) icon;
- aIcon.setBackgroundColor(mBackgroundColor);
- iconView.setImageDrawable(aIcon);
- } else {
- iconView.setImageDrawable(icon);
- }
- deviceName.setText(device.getName());
- } else {
- // Reset to default
- Log.d(TAG, "device is null. Not binding output chip.");
- mViewHolder.getSeamless().setEnabled(true);
- mViewHolder.getSeamless().setAlpha(1f);
- iconView.setVisibility(View.GONE);
- deviceName.setText(com.android.internal.R.string.ext_media_seamless_action);
- }
- }
-
- /**
* Puts controls into a resumption state if possible, or calls removePlayer if no component was
* found that could resume playback
*/
@@ -642,27 +587,6 @@
set.setAlpha(actionId, visible ? 1.0f : 0.0f);
}
- private void makeActive() {
- Assert.isMainThread();
- if (!mIsRegistered) {
- if (mLocalMediaManager != null) {
- mLocalMediaManager.registerCallback(mDeviceCallback);
- mLocalMediaManager.startScan();
- }
- mIsRegistered = true;
- }
- }
-
- private void makeInactive() {
- Assert.isMainThread();
- if (mIsRegistered) {
- if (mLocalMediaManager != null) {
- mLocalMediaManager.stopScan();
- mLocalMediaManager.unregisterCallback(mDeviceCallback);
- }
- mIsRegistered = false;
- }
- }
/**
* Verify that we can connect to the given component with a MediaBrowser, and if so, add that
* component to the list of resumption components
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index 85965d0..41d4110 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -34,7 +34,8 @@
val actionsToShowInCompact: List<Int>,
val packageName: String?,
val token: MediaSession.Token?,
- val clickIntent: PendingIntent?
+ val clickIntent: PendingIntent?,
+ val device: MediaDeviceData?
)
/** State of a media action. */
@@ -43,3 +44,9 @@
val intent: PendingIntent?,
val contentDescription: CharSequence?
)
+
+/** State of the media device. */
+data class MediaDeviceData(
+ val icon: Drawable?,
+ val name: String?
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
new file mode 100644
index 0000000..cce9838
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * Combines updates from [MediaDataManager] with [MediaDeviceManager].
+ */
+@Singleton
+class MediaDataCombineLatest @Inject constructor(
+ private val dataSource: MediaDataManager,
+ private val deviceSource: MediaDeviceManager
+) {
+ private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
+ private val entries: MutableMap<String, Pair<MediaData?, MediaDeviceData?>> = mutableMapOf()
+
+ init {
+ dataSource.addListener(object : MediaDataManager.Listener {
+ override fun onMediaDataLoaded(key: String, data: MediaData) {
+ entries[key] = data to entries[key]?.second
+ update(key)
+ }
+ override fun onMediaDataRemoved(key: String) {
+ remove(key)
+ }
+ })
+ deviceSource.addListener(object : MediaDeviceManager.Listener {
+ override fun onMediaDeviceChanged(key: String, data: MediaDeviceData?) {
+ entries[key] = entries[key]?.first to data
+ update(key)
+ }
+ override fun onKeyRemoved(key: String) {
+ remove(key)
+ }
+ })
+ }
+
+ /**
+ * Add a listener for [MediaData] changes that has been combined with latest [MediaDeviceData].
+ */
+ fun addListener(listener: MediaDataManager.Listener) = listeners.add(listener)
+
+ /**
+ * Remove a listener registered with addListener.
+ */
+ fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener)
+
+ private fun update(key: String) {
+ val (entry, device) = entries[key] ?: null to null
+ if (entry != null && device != null) {
+ val data = entry.copy(device = device)
+ listeners.forEach {
+ it.onMediaDataLoaded(key, data)
+ }
+ }
+ }
+
+ private fun remove(key: String) {
+ entries.remove(key)?.let {
+ listeners.forEach {
+ it.onMediaDataRemoved(key)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 90c558a..8cbe3ec 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -55,7 +55,19 @@
private const val SATURATION_MULTIPLIER = 0.8f
private val LOADING = MediaData(false, 0, null, null, null, null, null,
- emptyList(), emptyList(), null, null, null)
+ emptyList(), emptyList(), null, null, null, null)
+
+fun isMediaNotification(sbn: StatusBarNotification): Boolean {
+ if (!sbn.notification.hasMediaSession()) {
+ return false
+ }
+ val notificationStyle = sbn.notification.notificationStyle
+ if (Notification.DecoratedMediaCustomViewStyle::class.java.equals(notificationStyle) ||
+ Notification.MediaStyle::class.java.equals(notificationStyle)) {
+ return true
+ }
+ return false
+}
/**
* A class that facilitates management and loading of Media Data, ready for binding.
@@ -65,14 +77,14 @@
private val context: Context,
private val mediaControllerFactory: MediaControllerFactory,
@Background private val backgroundExecutor: Executor,
- @Main private val foregroundExcecutor: Executor
+ @Main private val foregroundExecutor: Executor
) {
private val listeners: MutableSet<Listener> = mutableSetOf()
private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
- if (isMediaNotification(sbn)) {
+ if (Utils.useQsMediaPlayer(context) && isMediaNotification(sbn)) {
if (!mediaEntries.containsKey(key)) {
mediaEntries.put(key, LOADING)
}
@@ -201,10 +213,10 @@
}
}
- foregroundExcecutor.execute {
+ foregroundExecutor.execute {
onMediaDataLoaded(key, MediaData(true, bgColor, app, smallIconDrawable, artist, song,
artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token,
- notif.contentIntent))
+ notif.contentIntent, null))
}
}
@@ -270,25 +282,10 @@
}
}
- private fun isMediaNotification(sbn: StatusBarNotification): Boolean {
- if (!Utils.useQsMediaPlayer(context)) {
- return false
- }
- if (!sbn.notification.hasMediaSession()) {
- return false
- }
- val notificationStyle = sbn.notification.notificationStyle
- if (Notification.DecoratedMediaCustomViewStyle::class.java.equals(notificationStyle) ||
- Notification.MediaStyle::class.java.equals(notificationStyle)) {
- return true
- }
- return false
- }
-
/**
* Are there any media notifications active?
*/
- fun hasActiveMedia() = mediaEntries.size > 0
+ fun hasActiveMedia() = mediaEntries.isNotEmpty()
fun hasAnyMedia(): Boolean {
// TODO: implement this when we implemented resumption
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
new file mode 100644
index 0000000..2d16e29
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import android.content.Context
+import android.service.notification.StatusBarNotification
+import com.android.settingslib.media.LocalMediaManager
+import com.android.settingslib.media.MediaDevice
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * Provides information about the route (ie. device) where playback is occurring.
+ */
+@Singleton
+class MediaDeviceManager @Inject constructor(
+ private val context: Context,
+ private val localMediaManagerFactory: LocalMediaManagerFactory,
+ private val featureFlag: MediaFeatureFlag,
+ @Main private val fgExecutor: Executor
+) {
+ private val listeners: MutableSet<Listener> = mutableSetOf()
+ private val entries: MutableMap<String, Token> = mutableMapOf()
+
+ /**
+ * Add a listener for changes to the media route (ie. device).
+ */
+ fun addListener(listener: Listener) = listeners.add(listener)
+
+ /**
+ * Remove a listener that has been registered with addListener.
+ */
+ fun removeListener(listener: Listener) = listeners.remove(listener)
+
+ fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
+ if (featureFlag.enabled && isMediaNotification(sbn)) {
+ var tok = entries[key]
+ if (tok == null) {
+ tok = Token(key, localMediaManagerFactory.create(sbn.packageName))
+ entries[key] = tok
+ tok.start()
+ }
+ } else {
+ onNotificationRemoved(key)
+ }
+ }
+
+ fun onNotificationRemoved(key: String) {
+ val token = entries.remove(key)
+ token?.stop()
+ token?.let {
+ listeners.forEach {
+ it.onKeyRemoved(key)
+ }
+ }
+ }
+
+ private fun processDevice(key: String, device: MediaDevice?) {
+ val data = MediaDeviceData(device?.icon, device?.name)
+ listeners.forEach {
+ it.onMediaDeviceChanged(key, data)
+ }
+ }
+
+ interface Listener {
+ /** Called when the route has changed for a given notification. */
+ fun onMediaDeviceChanged(key: String, data: MediaDeviceData?)
+ /** Called when the notification was removed. */
+ fun onKeyRemoved(key: String)
+ }
+
+ private inner class Token(
+ val key: String,
+ val localMediaManager: LocalMediaManager
+ ) : LocalMediaManager.DeviceCallback {
+ private var current: MediaDevice? = null
+ set(value) {
+ if (value != field) {
+ field = value
+ processDevice(key, value)
+ }
+ }
+ fun start() {
+ localMediaManager.registerCallback(this)
+ localMediaManager.startScan()
+ current = localMediaManager.getCurrentConnectedDevice()
+ }
+ fun stop() {
+ localMediaManager.stopScan()
+ localMediaManager.unregisterCallback(this)
+ }
+ override fun onDeviceListUpdate(devices: List<MediaDevice>?) = fgExecutor.execute {
+ current = localMediaManager.getCurrentConnectedDevice()
+ }
+ override fun onSelectedDeviceStateChanged(device: MediaDevice, state: Int) {
+ fgExecutor.execute {
+ current = device
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaFeatureFlag.kt b/packages/SystemUI/src/com/android/systemui/media/MediaFeatureFlag.kt
new file mode 100644
index 0000000..75eb33d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaFeatureFlag.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import android.content.Context
+import com.android.systemui.util.Utils
+import javax.inject.Inject
+
+/**
+ * Provides access to the current value of the feature flag.
+ */
+class MediaFeatureFlag @Inject constructor(private val context: Context) {
+ val enabled
+ get() = Utils.useQsMediaPlayer(context)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index 6b1c520..3e7661a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -26,6 +26,7 @@
import android.view.ViewGroupOverlay
import com.android.systemui.Interpolators
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
@@ -46,7 +47,8 @@
private val keyguardStateController: KeyguardStateController,
private val bypassController: KeyguardBypassController,
private val mediaViewManager: MediaViewManager,
- private val mediaMeasurementProvider: MediaMeasurementManager
+ private val mediaMeasurementProvider: MediaMeasurementManager,
+ private val notifLockscreenUserManager: NotificationLockscreenUserManager
) {
/**
* The root overlay of the hierarchy. This is where the media notification is attached to
@@ -56,7 +58,7 @@
private var rootOverlay: ViewGroupOverlay? = null
private lateinit var currentState: MediaState
private val mediaCarousel
- get() = mediaViewManager.mediaCarousel
+ get() = mediaViewManager.mediaCarousel
private var animationStartState: MediaState? = null
private var statusbarState: Int = statusBarStateController.state
private var animator = ValueAnimator.ofFloat(0.0f, 1.0f).apply {
@@ -136,9 +138,9 @@
*
* @return the hostView associated with this location
*/
- fun register(mediaObject: MediaHost) : ViewGroup {
+ fun register(mediaObject: MediaHost): ViewGroup {
val viewHost = createUniqueObjectHost(mediaObject)
- mediaObject.hostView = viewHost;
+ mediaObject.hostView = viewHost
mediaHosts[mediaObject.location] = mediaObject
if (mediaObject.location == desiredLocation) {
// In case we are overriding a view that is already visible, make sure we attach it
@@ -155,7 +157,7 @@
private fun createUniqueObjectHost(host: MediaHost): UniqueObjectHostView {
val viewHost = UniqueObjectHostView(context)
viewHost.measurementCache = mediaMeasurementProvider.obtainCache(host)
- viewHost.onMeasureListener = { input ->
+ viewHost.onMeasureListener = { input ->
if (host.location == desiredLocation) {
// Measurement of the currently active player is happening, Let's make
// sure the player width is up to date
@@ -215,8 +217,8 @@
applyTargetStateIfNotAnimating()
} else if (animate) {
animator.cancel()
- if (currentAttachmentLocation == IN_OVERLAY
- || !previousHost.hostView.isAttachedToWindow) {
+ if (currentAttachmentLocation == IN_OVERLAY ||
+ !previousHost.hostView.isAttachedToWindow) {
// Let's animate to the new position, starting from the current position
// We also go in here in case the view was detached, since the bounds wouldn't
// be correct anymore
@@ -237,10 +239,10 @@
@MediaLocation currentLocation: Int,
@MediaLocation previousLocation: Int
): Boolean {
- if (currentLocation == LOCATION_QQS
- && previousLocation == LOCATION_LOCKSCREEN
- && (statusBarStateController.leaveOpenOnKeyguardHide()
- || statusbarState == StatusBarState.SHADE_LOCKED)) {
+ if (currentLocation == LOCATION_QQS &&
+ previousLocation == LOCATION_LOCKSCREEN &&
+ (statusBarStateController.leaveOpenOnKeyguardHide() ||
+ statusbarState == StatusBarState.SHADE_LOCKED)) {
// Usually listening to the isShown is enough to determine this, but there is some
// non-trivial reattaching logic happening that will make the view not-shown earlier
return true
@@ -251,10 +253,9 @@
private fun adjustAnimatorForTransition(desiredLocation: Int, previousLocation: Int) {
val (animDuration, delay) = getAnimationParams(previousLocation, desiredLocation)
animator.apply {
- duration = animDuration
+ duration = animDuration
startDelay = delay
}
-
}
private fun getAnimationParams(previousLocation: Int, desiredLocation: Int): Pair<Long, Long> {
@@ -262,8 +263,8 @@
var delay = 0L
if (previousLocation == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QQS) {
// Going to the full shade, let's adjust the animation duration
- if (statusbarState == StatusBarState.SHADE
- && keyguardStateController.isKeyguardFadingAway) {
+ if (statusbarState == StatusBarState.SHADE &&
+ keyguardStateController.isKeyguardFadingAway) {
delay = keyguardStateController.keyguardFadingAwayDelay
}
animDuration = StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE.toLong()
@@ -301,12 +302,12 @@
/**
* @return true if this transformation is guided by an external progress like a finger
*/
- private fun isCurrentlyInGuidedTransformation() : Boolean {
+ private fun isCurrentlyInGuidedTransformation(): Boolean {
return getTransformationProgress() >= 0
}
/**
- * @return the current transformation progress if we're in a guided transformation and -1
+ * @return the current transformation progress if we're in a guided transformation and -1
* otherwise
*/
private fun getTransformationProgress(): Float {
@@ -377,19 +378,20 @@
}
private fun isTransitionRunning(): Boolean {
- return isCurrentlyInGuidedTransformation() && getTransformationProgress() != 1.0f
- || animator.isRunning
+ return isCurrentlyInGuidedTransformation() && getTransformationProgress() != 1.0f ||
+ animator.isRunning
}
@MediaLocation
- private fun calculateLocation() : Int {
- val onLockscreen = (!bypassController.bypassEnabled
- && (statusbarState == StatusBarState.KEYGUARD
- || statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER))
+ private fun calculateLocation(): Int {
+ val onLockscreen = (!bypassController.bypassEnabled &&
+ (statusbarState == StatusBarState.KEYGUARD ||
+ statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER))
+ val allowedOnLockscreen = notifLockscreenUserManager.shouldShowLockscreenNotifications()
return when {
qsExpansion > 0.0f && !onLockscreen -> LOCATION_QS
qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
- onLockscreen -> LOCATION_LOCKSCREEN
+ onLockscreen && allowedOnLockscreen -> LOCATION_LOCKSCREEN
else -> LOCATION_QQS
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
index 6e7b6bc..240e44c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
@@ -26,8 +26,8 @@
/**
* Get the current Media state. This also updates the location on screen
*/
- val currentState : MediaState
- get () {
+ val currentState: MediaState
+ get() {
hostView.getLocationOnScreen(tmpLocationOnScreen)
var left = tmpLocationOnScreen[0] + hostView.paddingLeft
var top = tmpLocationOnScreen[1] + hostView.paddingTop
@@ -37,11 +37,11 @@
// the above could return negative widths, which is wrong
if (right < left) {
left = 0
- right = 0;
+ right = 0
}
if (bottom < top) {
bottom = 0
- top = 0;
+ top = 0
}
state.boundsOnScreen.set(left, top, right, bottom)
return state
@@ -64,7 +64,7 @@
* transitions.
*/
fun init(@MediaLocation location: Int) {
- this.location = location;
+ this.location = location
hostView = mediaHierarchyManager.register(this)
hostView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View?) {
@@ -95,7 +95,7 @@
override var showsOnlyActiveMedia: Boolean = false
override val boundsOnScreen: Rect = Rect()
- override fun copy() : MediaState {
+ override fun copy(): MediaState {
val mediaHostState = MediaHostState()
mediaHostState.expansion = expansion
mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia
@@ -104,7 +104,7 @@
return mediaHostState
}
- override fun interpolate(other: MediaState, amount: Float) : MediaState {
+ override fun interpolate(other: MediaState, amount: Float): MediaState {
val result = MediaHostState()
result.expansion = MathUtils.lerp(expansion, other.expansion, amount)
val left = MathUtils.lerp(boundsOnScreen.left.toFloat(),
@@ -121,10 +121,10 @@
if (other is MediaHostState) {
result.measurementInput = other.measurementInput
}
- } else {
+ } else {
result.measurementInput
}
- return result
+ return result
}
override fun getMeasuringInput(input: MeasurementInput): MediaMeasurementInput {
@@ -138,8 +138,8 @@
var expansion: Float
var showsOnlyActiveMedia: Boolean
val boundsOnScreen: Rect
- fun copy() : MediaState
- fun interpolate(other: MediaState, amount: Float) : MediaState
+ fun copy(): MediaState
+ fun interpolate(other: MediaState, amount: Float): MediaState
fun getMeasuringInput(input: MeasurementInput): MediaMeasurementInput
}
/**
@@ -147,7 +147,8 @@
*/
data class MediaMeasurementInput(
private val viewInput: MeasurementInput,
- val expansion: Float) : MeasurementInput by viewInput {
+ val expansion: Float
+) : MeasurementInput by viewInput {
override fun sameAs(input: MeasurementInput?): Boolean {
if (!(input is MediaMeasurementInput)) {
@@ -155,5 +156,4 @@
}
return width == input.width && expansion == input.expansion
}
-}
-
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
index 8db9dcc..17e8404 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
@@ -6,9 +6,6 @@
import android.view.ViewGroup
import android.widget.HorizontalScrollView
import android.widget.LinearLayout
-import com.android.settingslib.bluetooth.LocalBluetoothManager
-import com.android.settingslib.media.InfoMediaManager
-import com.android.settingslib.media.LocalMediaManager
import com.android.systemui.R
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -30,10 +27,9 @@
private val context: Context,
@Main private val foregroundExecutor: Executor,
@Background private val backgroundExecutor: DelayableExecutor,
- private val localBluetoothManager: LocalBluetoothManager?,
private val visualStabilityManager: VisualStabilityManager,
private val activityStarter: ActivityStarter,
- mediaManager: MediaDataManager
+ mediaManager: MediaDataCombineLatest
) {
private var playerWidth: Int = 0
private var playerWidthPlusPadding: Int = 0
@@ -42,7 +38,7 @@
val mediaCarousel: HorizontalScrollView
private val mediaContent: ViewGroup
private val mediaPlayers: MutableMap<String, MediaControlPanel> = mutableMapOf()
- private val visualStabilityCallback : VisualStabilityManager.Callback
+ private val visualStabilityCallback: VisualStabilityManager.Callback
private var activeMediaIndex: Int = 0
private var needsReordering: Boolean = false
private var scrollIntoCurrentMedia: Int = 0
@@ -151,15 +147,8 @@
private fun updateView(key: String, data: MediaData) {
var existingPlayer = mediaPlayers[key]
if (existingPlayer == null) {
- // Set up listener for device changes
- // TODO: integrate with MediaTransferManager?
- val imm = InfoMediaManager(context, data.packageName,
- null /* notification */, localBluetoothManager)
- val routeManager = LocalMediaManager(context, localBluetoothManager,
- imm, data.packageName)
-
- existingPlayer = MediaControlPanel(context, routeManager, foregroundExecutor,
- backgroundExecutor, activityStarter)
+ existingPlayer = MediaControlPanel(context, foregroundExecutor, backgroundExecutor,
+ activityStarter)
existingPlayer.attach(PlayerViewHolder.create(LayoutInflater.from(context),
mediaContent))
mediaPlayers[key] = existingPlayer
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
index 110b08c..cd8ed26 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
@@ -16,58 +16,41 @@
package com.android.systemui.media
-import android.content.res.ColorStateList
-import android.graphics.Color
import android.text.format.DateUtils
-import android.view.View
-import android.widget.SeekBar
-import android.widget.TextView
import androidx.annotation.UiThread
import androidx.lifecycle.Observer
-import com.android.systemui.R
-
/**
* Observer for changes from SeekBarViewModel.
*
* <p>Updates the seek bar views in response to changes to the model.
*/
-class SeekBarObserver(view: View) : Observer<SeekBarViewModel.Progress> {
-
- private val seekBarView: SeekBar
- private val elapsedTimeView: TextView
- private val totalTimeView: TextView
-
- init {
- seekBarView = view.findViewById(R.id.media_progress_bar)
- elapsedTimeView = view.findViewById(R.id.media_elapsed_time)
- totalTimeView = view.findViewById(R.id.media_total_time)
- }
+class SeekBarObserver(private val holder: PlayerViewHolder) : Observer<SeekBarViewModel.Progress> {
/** Updates seek bar views when the data model changes. */
@UiThread
override fun onChanged(data: SeekBarViewModel.Progress) {
if (!data.enabled) {
- seekBarView.setEnabled(false)
- seekBarView.getThumb().setAlpha(0)
- seekBarView.setProgress(0)
- elapsedTimeView.setText("")
- totalTimeView.setText("")
+ holder.seekBar.setEnabled(false)
+ holder.seekBar.getThumb().setAlpha(0)
+ holder.seekBar.setProgress(0)
+ holder.elapsedTimeView.setText("")
+ holder.totalTimeView.setText("")
return
}
- seekBarView.getThumb().setAlpha(if (data.seekAvailable) 255 else 0)
- seekBarView.setEnabled(data.seekAvailable)
+ holder.seekBar.getThumb().setAlpha(if (data.seekAvailable) 255 else 0)
+ holder.seekBar.setEnabled(data.seekAvailable)
data.elapsedTime?.let {
- seekBarView.setProgress(it)
- elapsedTimeView.setText(DateUtils.formatElapsedTime(
+ holder.seekBar.setProgress(it)
+ holder.elapsedTimeView.setText(DateUtils.formatElapsedTime(
it / DateUtils.SECOND_IN_MILLIS))
}
data.duration?.let {
- seekBarView.setMax(it)
- totalTimeView.setText(DateUtils.formatElapsedTime(
+ holder.seekBar.setMax(it)
+ holder.totalTimeView.setText(DateUtils.formatElapsedTime(
it / DateUtils.SECOND_IN_MILLIS))
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
index b08124b..06821cd6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
@@ -78,7 +78,22 @@
val progress: LiveData<Progress>
get() = _progress
private var controller: MediaController? = null
+ set(value) {
+ if (field?.sessionToken != value?.sessionToken) {
+ field?.unregisterCallback(callback)
+ value?.registerCallback(callback)
+ field = value
+ }
+ }
private var playbackState: PlaybackState? = null
+ private var callback = object : MediaController.Callback() {
+ override fun onPlaybackStateChanged(state: PlaybackState) {
+ playbackState = state
+ if (shouldPollPlaybackPosition()) {
+ checkPlaybackPosition()
+ }
+ }
+ }
/** Listening state (QS open or closed) is used to control polling of progress. */
var listening = true
@@ -95,6 +110,9 @@
@WorkerThread
fun onSeek(position: Long) {
controller?.transportControls?.seekTo(position)
+ // Invalidate the cached playbackState to avoid the thumb jumping back to the previous
+ // position.
+ playbackState = null
}
/**
@@ -125,12 +143,23 @@
*/
@AnyThread
fun clearController() = bgExecutor.execute {
+ controller = null
+ playbackState = null
_data = _data.copy(enabled = false)
}
+ /**
+ * Call to clean up any resources.
+ */
+ @AnyThread
+ fun onDestroy() {
+ controller = null
+ playbackState = null
+ }
+
@AnyThread
private fun checkPlaybackPosition(): Runnable = bgExecutor.executeDelayed({
- val duration = _data?.duration ?: -1
+ val duration = _data.duration ?: -1
val currentPosition = playbackState?.computePosition(duration.toLong())?.toInt()
if (currentPosition != null && _data.elapsedTime != currentPosition) {
_data = _data.copy(elapsedTime = currentPosition)
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index ae61006..72d76b9 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -58,6 +58,7 @@
import com.android.systemui.R;
import com.android.systemui.pip.phone.PipUpdateThread;
import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.wm.DisplayController;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -82,8 +83,10 @@
* see also {@link com.android.systemui.pip.phone.PipMotionHelper}.
*/
@Singleton
-public class PipTaskOrganizer extends TaskOrganizer {
+public class PipTaskOrganizer extends TaskOrganizer implements
+ DisplayController.OnDisplaysChangedListener {
private static final String TAG = PipTaskOrganizer.class.getSimpleName();
+ private static final boolean DEBUG = false;
private static final int MSG_RESIZE_IMMEDIATE = 1;
private static final int MSG_RESIZE_ANIMATE = 2;
@@ -206,10 +209,17 @@
mSurfaceControlTransactionFactory;
private PictureInPictureParams mPictureInPictureParams;
+ /**
+ * If set to {@code true}, the entering animation will be skipped and we will wait for
+ * {@link #onFixedRotationFinished(int)} callback to actually enter PiP.
+ */
+ private boolean mShouldDeferEnteringPip;
+
@Inject
public PipTaskOrganizer(Context context, @NonNull PipBoundsHandler boundsHandler,
@NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
- @Nullable Divider divider) {
+ @Nullable Divider divider,
+ @NonNull DisplayController displayController) {
mMainHandler = new Handler(Looper.getMainLooper());
mUpdateHandler = new Handler(PipUpdateThread.get().getLooper(), mUpdateCallbacks);
mPipBoundsHandler = boundsHandler;
@@ -219,6 +229,7 @@
mPipAnimationController = new PipAnimationController(context, surfaceTransactionHelper);
mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
mSplitDivider = divider;
+ displayController.addDisplayWindowListener(this);
}
public Handler getUpdateHandler() {
@@ -281,7 +292,8 @@
final int direction = syncWithSplitScreenBounds(destinationBounds)
? TRANSITION_DIRECTION_TO_SPLIT_SCREEN
: TRANSITION_DIRECTION_TO_FULLSCREEN;
- final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+ final SurfaceControl.Transaction tx =
+ mSurfaceControlTransactionFactory.getTransaction();
mSurfaceTransactionHelper.scale(tx, mLeash, destinationBounds,
mLastReportedBounds);
tx.setWindowCrop(mLeash, destinationBounds.width(), destinationBounds.height());
@@ -325,53 +337,67 @@
@Override
public void onTaskAppeared(ActivityManager.RunningTaskInfo info, SurfaceControl leash) {
Objects.requireNonNull(info, "Requires RunningTaskInfo");
- mPictureInPictureParams = info.pictureInPictureParams;
- final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
- info.topActivity, getAspectRatioOrDefault(mPictureInPictureParams),
- null /* bounds */, getMinimalSize(info.topActivityInfo));
- Objects.requireNonNull(destinationBounds, "Missing destination bounds");
mTaskInfo = info;
mToken = mTaskInfo.token;
mInPip = true;
mLeash = leash;
-
- // TODO: Skip enter animation when entering pip from another orientation
- final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
mInitialState.put(mToken.asBinder(), new Configuration(mTaskInfo.configuration));
+ mPictureInPictureParams = mTaskInfo.pictureInPictureParams;
+
+ if (mShouldDeferEnteringPip) {
+ if (DEBUG) Log.d(TAG, "Defer entering PiP animation, fixed rotation is ongoing");
+ // if deferred, hide the surface till fixed rotation is completed
+ final SurfaceControl.Transaction tx =
+ mSurfaceControlTransactionFactory.getTransaction();
+ tx.setAlpha(mLeash, 0f);
+ tx.apply();
+ return;
+ }
+
+ final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
+ mTaskInfo.topActivity, getAspectRatioOrDefault(mPictureInPictureParams),
+ null /* bounds */, getMinimalSize(mTaskInfo.topActivityInfo));
+ Objects.requireNonNull(destinationBounds, "Missing destination bounds");
+ final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
scheduleAnimateResizePip(currentBounds, destinationBounds,
TRANSITION_DIRECTION_TO_PIP, mEnterExitAnimationDuration,
null /* updateBoundsCallback */);
} else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
- // If we are fading the PIP in, then we should move the pip to the final location as
- // soon as possible, but set the alpha immediately since the transaction can take a
- // while to process
- final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
- tx.setAlpha(mLeash, 0f);
- tx.apply();
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
- wct.setBounds(mToken, destinationBounds);
- wct.scheduleFinishEnterPip(mToken, destinationBounds);
- applySyncTransaction(wct, new WindowContainerTransactionCallback() {
- @Override
- public void onTransactionReady(int id, SurfaceControl.Transaction t) {
- t.apply();
- mUpdateHandler.post(() -> mPipAnimationController
- .getAnimator(mLeash, destinationBounds, 0f, 1f)
- .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
- .setPipAnimationCallback(mPipAnimationCallback)
- .setDuration(mEnterExitAnimationDuration)
- .start());
- }
- });
+ enterPipWithAlphaAnimation(destinationBounds, mEnterExitAnimationDuration);
mOneShotAnimationType = ANIM_TYPE_BOUNDS;
} else {
throw new RuntimeException("Unrecognized animation type: " + mOneShotAnimationType);
}
}
+ private void enterPipWithAlphaAnimation(Rect destinationBounds, long durationMs) {
+ // If we are fading the PIP in, then we should move the pip to the final location as
+ // soon as possible, but set the alpha immediately since the transaction can take a
+ // while to process
+ final SurfaceControl.Transaction tx =
+ mSurfaceControlTransactionFactory.getTransaction();
+ tx.setAlpha(mLeash, 0f);
+ tx.apply();
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
+ wct.setBounds(mToken, destinationBounds);
+ wct.scheduleFinishEnterPip(mToken, destinationBounds);
+ applySyncTransaction(wct, new WindowContainerTransactionCallback() {
+ @Override
+ public void onTransactionReady(int id, SurfaceControl.Transaction t) {
+ t.apply();
+ mUpdateHandler.post(() -> mPipAnimationController
+ .getAnimator(mLeash, destinationBounds, 0f, 1f)
+ .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
+ .setPipAnimationCallback(mPipAnimationCallback)
+ .setDuration(durationMs)
+ .start());
+ }
+ });
+ }
+
/**
* Note that dismissing PiP is now originated from SystemUI, see {@link #exitPip(int)}.
* Meanwhile this callback is invoked whenever the task is removed. For instance:
@@ -391,6 +417,7 @@
Log.wtf(TAG, "Unrecognized token: " + token);
return;
}
+ mShouldDeferEnteringPip = false;
mPictureInPictureParams = null;
mInPip = false;
}
@@ -416,6 +443,23 @@
// Do nothing
}
+ @Override
+ public void onFixedRotationStarted(int displayId, int newRotation) {
+ mShouldDeferEnteringPip = true;
+ }
+
+ @Override
+ public void onFixedRotationFinished(int displayId) {
+ if (mShouldDeferEnteringPip && mInPip) {
+ final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
+ mTaskInfo.topActivity, getAspectRatioOrDefault(mPictureInPictureParams),
+ null /* bounds */, getMinimalSize(mTaskInfo.topActivityInfo));
+ // schedule a regular animation to ensure all the callbacks are still being sent
+ enterPipWithAlphaAnimation(destinationBounds, 0 /* durationMs */);
+ }
+ mShouldDeferEnteringPip = false;
+ }
+
/**
* TODO(b/152809058): consolidate the display info handling logic in SysUI
*
@@ -476,6 +520,10 @@
*/
public void scheduleAnimateResizePip(Rect toBounds, int duration,
Consumer<Rect> updateBoundsCallback) {
+ if (mShouldDeferEnteringPip) {
+ Log.d(TAG, "skip scheduleAnimateResizePip, entering pip deferred");
+ return;
+ }
scheduleAnimateResizePip(mLastReportedBounds, toBounds,
TRANSITION_DIRECTION_NONE, duration, updateBoundsCallback);
}
@@ -567,6 +615,10 @@
// can be initiated in other component, ignore if we are no longer in PIP
return;
}
+ if (mShouldDeferEnteringPip) {
+ Log.d(TAG, "skip scheduleOffsetPip, entering pip deferred");
+ return;
+ }
SomeArgs args = SomeArgs.obtain();
args.arg1 = updateBoundsCallback;
args.arg2 = originalBounds;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 1982227..69bad80 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -21,6 +21,7 @@
import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;
import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ACTIONS;
import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ALLOW_TIMEOUT;
@@ -68,6 +69,7 @@
import android.view.ViewGroup;
import android.view.WindowManager.LayoutParams;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.LinearLayout;
@@ -220,6 +222,29 @@
// Hide without an animation.
getWindow().setExitTransition(null);
+
+ initAccessibility();
+ }
+
+ private void initAccessibility() {
+ getWindow().getDecorView().setAccessibilityDelegate(new View.AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ String label = getResources().getString(R.string.pip_menu_title);
+ info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, label));
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (action == ACTION_CLICK && mMenuState == MENU_STATE_CLOSE) {
+ Message m = Message.obtain();
+ m.what = PipMenuActivityController.MESSAGE_SHOW_MENU;
+ sendMessage(m, "Could not notify controller to show PIP menu");
+ }
+ return super.performAccessibilityAction(host, action, args);
+ }
+ });
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/power/InattentiveSleepWarningView.java b/packages/SystemUI/src/com/android/systemui/power/InattentiveSleepWarningView.java
index cf1bc7d..1ed98c0 100644
--- a/packages/SystemUI/src/com/android/systemui/power/InattentiveSleepWarningView.java
+++ b/packages/SystemUI/src/com/android/systemui/power/InattentiveSleepWarningView.java
@@ -119,7 +119,7 @@
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
- WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
+ WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
PixelFormat.TRANSLUCENT);
lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 865fd07..b877e87 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -157,6 +157,7 @@
if (mListening) {
setListening(false);
}
+ mQSCustomizer.setQs(null);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 5e3e438..cdde06b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -361,9 +361,13 @@
if (mHost != null) {
mHost.removeCallback(this);
}
+ if (mTileLayout != null) {
+ mTileLayout.setListening(false);
+ }
for (TileRecord record : mRecords) {
record.tile.removeCallbacks();
}
+ mRecords.clear();
if (mBrightnessMirrorController != null) {
mBrightnessMirrorController.removeCallback(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 34a9e28..b272b60 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -842,7 +842,26 @@
Log.e(TAG_OPS, "Failed to get overview proxy for assistant visibility.");
}
} catch (RemoteException e) {
- Log.e(TAG_OPS, "Failed to call onAssistantVisibilityChanged()", e);
+ Log.e(TAG_OPS, "Failed to call notifyAssistantVisibilityChanged()", e);
+ }
+ }
+
+ /**
+ * Notifies the Launcher of split screen size changes
+ * @param secondaryWindowBounds Bounds of the secondary window including the insets
+ * @param secondaryWindowInsets stable insets received by the secondary window
+ */
+ public void notifySplitScreenBoundsChanged(
+ Rect secondaryWindowBounds, Rect secondaryWindowInsets) {
+ try {
+ if (mOverviewProxy != null) {
+ mOverviewProxy.onSplitScreenSecondaryBoundsChanged(
+ secondaryWindowBounds, secondaryWindowInsets);
+ } else {
+ Log.e(TAG_OPS, "Failed to get overview proxy for split screen bounds.");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG_OPS, "Failed to call onSplitScreenSecondaryBoundsChanged()", e);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 6f68ee8..f2d2eb3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -703,9 +703,7 @@
mScreenshotPreview.buildLayer();
mScreenshotAnimation.start();
});
-
});
-
}
private AnimatorSet createScreenshotDropInAnimation(int width, int height, Rect bounds) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index bc3c33d..e88ce5a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -331,7 +331,7 @@
int requestCode = mContext.getUserId();
// Create a edit action
- PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode,
+ PendingIntent editAction = PendingIntent.getBroadcast(context, requestCode,
new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
.putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, editIntent)
.putExtra(GlobalScreenshot.EXTRA_CANCEL_NOTIFICATION,
@@ -341,7 +341,7 @@
mSmartActionsEnabled)
.setAction(Intent.ACTION_EDIT)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
- PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
+ PendingIntent.FLAG_CANCEL_CURRENT);
Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
Icon.createWithResource(r, R.drawable.ic_screenshot_edit),
r.getString(com.android.internal.R.string.screenshot_edit), editAction);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
index 44b20e5..b5209bb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
@@ -22,8 +22,8 @@
import android.graphics.drawable.Icon;
import android.util.AttributeSet;
import android.util.Log;
+import android.widget.FrameLayout;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.systemui.R;
@@ -31,7 +31,7 @@
/**
* View for a chip with an icon and text.
*/
-public class ScreenshotActionChip extends LinearLayout {
+public class ScreenshotActionChip extends FrameLayout {
private static final String TAG = "ScreenshotActionChip";
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index cdd1280..95aceed 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -18,15 +18,10 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
-import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.content.Context;
@@ -36,15 +31,11 @@
import android.provider.Settings;
import android.util.Slog;
import android.view.LayoutInflater;
-import android.view.SurfaceControl;
import android.view.View;
-import android.window.TaskOrganizer;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import android.window.WindowOrganizer;
-import androidx.annotation.Nullable;
-
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
@@ -81,7 +72,6 @@
static final boolean DEBUG = false;
static final int DEFAULT_APP_TRANSITION_DURATION = 336;
- static final float ADJUSTED_NONFOCUS_DIM = 0.3f;
private final Optional<Lazy<Recents>> mRecentsOptionalLazy;
@@ -139,303 +129,7 @@
}
};
- private class DividerImeController implements DisplayImeController.ImePositionProcessor {
- /**
- * These are the y positions of the top of the IME surface when it is hidden and when it is
- * shown respectively. These are NOT necessarily the top of the visible IME itself.
- */
- private int mHiddenTop = 0;
- private int mShownTop = 0;
-
- // The following are target states (what we are curretly animating towards).
- /**
- * {@code true} if, at the end of the animation, the split task positions should be
- * adjusted by height of the IME. This happens when the secondary split is the IME target.
- */
- private boolean mTargetAdjusted = false;
- /**
- * {@code true} if, at the end of the animation, the IME should be shown/visible
- * regardless of what has focus.
- */
- private boolean mTargetShown = false;
- private float mTargetPrimaryDim = 0.f;
- private float mTargetSecondaryDim = 0.f;
-
- // The following are the current (most recent) states set during animation
- /** {@code true} if the secondary split has IME focus. */
- private boolean mSecondaryHasFocus = false;
- /** The dimming currently applied to the primary/secondary splits. */
- private float mLastPrimaryDim = 0.f;
- private float mLastSecondaryDim = 0.f;
- /** The most recent y position of the top of the IME surface */
- private int mLastAdjustTop = -1;
-
- // The following are states reached last time an animation fully completed.
- /** {@code true} if the IME was shown/visible by the last-completed animation. */
- private boolean mImeWasShown = false;
- /** {@code true} if the split positions were adjusted by the last-completed animation. */
- private boolean mAdjusted = false;
-
- /**
- * When some aspect of split-screen needs to animate independent from the IME,
- * this will be non-null and control split animation.
- */
- @Nullable
- private ValueAnimator mAnimation = null;
-
- private boolean mPaused = true;
- private boolean mPausedTargetAdjusted = false;
-
- private boolean getSecondaryHasFocus(int displayId) {
- WindowContainerToken imeSplit = TaskOrganizer.getImeTarget(displayId);
- return imeSplit != null
- && (imeSplit.asBinder() == mSplits.mSecondary.token.asBinder());
- }
-
- private void updateDimTargets() {
- final boolean splitIsVisible = !mView.isHidden();
- mTargetPrimaryDim = (mSecondaryHasFocus && mTargetShown && splitIsVisible)
- ? ADJUSTED_NONFOCUS_DIM : 0.f;
- mTargetSecondaryDim = (!mSecondaryHasFocus && mTargetShown && splitIsVisible)
- ? ADJUSTED_NONFOCUS_DIM : 0.f;
- }
-
- @Override
- public void onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
- boolean imeShouldShow, SurfaceControl.Transaction t) {
- if (!isDividerVisible()) {
- return;
- }
- final boolean splitIsVisible = !mView.isHidden();
- mSecondaryHasFocus = getSecondaryHasFocus(displayId);
- final boolean targetAdjusted = splitIsVisible && imeShouldShow && mSecondaryHasFocus
- && !mSplitLayout.mDisplayLayout.isLandscape();
- mHiddenTop = hiddenTop;
- mShownTop = shownTop;
- mTargetShown = imeShouldShow;
- if (mLastAdjustTop < 0) {
- mLastAdjustTop = imeShouldShow ? hiddenTop : shownTop;
- } else if (mLastAdjustTop != (imeShouldShow ? mShownTop : mHiddenTop)) {
- if (mTargetAdjusted != targetAdjusted && targetAdjusted == mAdjusted) {
- // Check for an "interruption" of an existing animation. In this case, we
- // need to fake-flip the last-known state direction so that the animation
- // completes in the other direction.
- mAdjusted = mTargetAdjusted;
- } else if (targetAdjusted && mTargetAdjusted && mAdjusted) {
- // Already fully adjusted for IME, but IME height has changed; so, force-start
- // an async animation to the new IME height.
- mAdjusted = false;
- }
- }
- if (mPaused) {
- mPausedTargetAdjusted = targetAdjusted;
- if (DEBUG) Slog.d(TAG, " ime starting but paused " + dumpState());
- return;
- }
- mTargetAdjusted = targetAdjusted;
- updateDimTargets();
- if (DEBUG) Slog.d(TAG, " ime starting. vis:" + splitIsVisible + " " + dumpState());
- if (mAnimation != null || (mImeWasShown && imeShouldShow
- && mTargetAdjusted != mAdjusted)) {
- // We need to animate adjustment independently of the IME position, so
- // start our own animation to drive adjustment. This happens when a
- // different split's editor has gained focus while the IME is still visible.
- startAsyncAnimation();
- }
- if (splitIsVisible) {
- // If split is hidden, we don't want to trigger any relayouts that would cause the
- // divider to show again.
- updateImeAdjustState();
- }
- }
-
- private void updateImeAdjustState() {
- // Reposition the server's secondary split position so that it evaluates
- // insets properly.
- WindowContainerTransaction wct = new WindowContainerTransaction();
- if (mTargetAdjusted) {
- mSplitLayout.updateAdjustedBounds(mShownTop, mHiddenTop, mShownTop);
- wct.setBounds(mSplits.mSecondary.token, mSplitLayout.mAdjustedSecondary);
- // "Freeze" the configuration size so that the app doesn't get a config
- // or relaunch. This is required because normally nav-bar contributes
- // to configuration bounds (via nondecorframe).
- Rect adjustAppBounds = new Rect(mSplits.mSecondary.configuration
- .windowConfiguration.getAppBounds());
- adjustAppBounds.offset(0, mSplitLayout.mAdjustedSecondary.top
- - mSplitLayout.mSecondary.top);
- wct.setAppBounds(mSplits.mSecondary.token, adjustAppBounds);
- wct.setScreenSizeDp(mSplits.mSecondary.token,
- mSplits.mSecondary.configuration.screenWidthDp,
- mSplits.mSecondary.configuration.screenHeightDp);
-
- wct.setBounds(mSplits.mPrimary.token, mSplitLayout.mAdjustedPrimary);
- adjustAppBounds = new Rect(mSplits.mPrimary.configuration
- .windowConfiguration.getAppBounds());
- adjustAppBounds.offset(0, mSplitLayout.mAdjustedPrimary.top
- - mSplitLayout.mPrimary.top);
- wct.setAppBounds(mSplits.mPrimary.token, adjustAppBounds);
- wct.setScreenSizeDp(mSplits.mPrimary.token,
- mSplits.mPrimary.configuration.screenWidthDp,
- mSplits.mPrimary.configuration.screenHeightDp);
- } else {
- wct.setBounds(mSplits.mSecondary.token, mSplitLayout.mSecondary);
- wct.setAppBounds(mSplits.mSecondary.token, null);
- wct.setScreenSizeDp(mSplits.mSecondary.token,
- SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
- wct.setBounds(mSplits.mPrimary.token, mSplitLayout.mPrimary);
- wct.setAppBounds(mSplits.mPrimary.token, null);
- wct.setScreenSizeDp(mSplits.mPrimary.token,
- SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
- }
-
- WindowOrganizer.applyTransaction(wct);
-
- // Update all the adjusted-for-ime states
- if (!mPaused) {
- mView.setAdjustedForIme(mTargetShown, mTargetShown
- ? DisplayImeController.ANIMATION_DURATION_SHOW_MS
- : DisplayImeController.ANIMATION_DURATION_HIDE_MS);
- }
- setAdjustedForIme(mTargetShown && !mPaused);
- }
-
- @Override
- public void onImePositionChanged(int displayId, int imeTop,
- SurfaceControl.Transaction t) {
- if (mAnimation != null || !isDividerVisible() || mPaused) {
- // Not synchronized with IME anymore, so return.
- return;
- }
- final float fraction = ((float) imeTop - mHiddenTop) / (mShownTop - mHiddenTop);
- final float progress = mTargetShown ? fraction : 1.f - fraction;
- onProgress(progress, t);
- }
-
- @Override
- public void onImeEndPositioning(int displayId, boolean cancelled,
- SurfaceControl.Transaction t) {
- if (mAnimation != null || !isDividerVisible() || mPaused) {
- // Not synchronized with IME anymore, so return.
- return;
- }
- onEnd(cancelled, t);
- }
-
- private void onProgress(float progress, SurfaceControl.Transaction t) {
- if (mTargetAdjusted != mAdjusted && !mPaused) {
- final float fraction = mTargetAdjusted ? progress : 1.f - progress;
- mLastAdjustTop = (int) (fraction * mShownTop + (1.f - fraction) * mHiddenTop);
- mSplitLayout.updateAdjustedBounds(mLastAdjustTop, mHiddenTop, mShownTop);
- mView.resizeSplitSurfaces(t, mSplitLayout.mAdjustedPrimary,
- mSplitLayout.mAdjustedSecondary);
- }
- final float invProg = 1.f - progress;
- mView.setResizeDimLayer(t, true /* primary */,
- mLastPrimaryDim * invProg + progress * mTargetPrimaryDim);
- mView.setResizeDimLayer(t, false /* primary */,
- mLastSecondaryDim * invProg + progress * mTargetSecondaryDim);
- }
-
- private void onEnd(boolean cancelled, SurfaceControl.Transaction t) {
- if (!cancelled) {
- onProgress(1.f, t);
- mAdjusted = mTargetAdjusted;
- mImeWasShown = mTargetShown;
- mLastAdjustTop = mAdjusted ? mShownTop : mHiddenTop;
- mLastPrimaryDim = mTargetPrimaryDim;
- mLastSecondaryDim = mTargetSecondaryDim;
- }
- }
-
- private void startAsyncAnimation() {
- if (mAnimation != null) {
- mAnimation.cancel();
- }
- mAnimation = ValueAnimator.ofFloat(0.f, 1.f);
- mAnimation.setDuration(DisplayImeController.ANIMATION_DURATION_SHOW_MS);
- if (mTargetAdjusted != mAdjusted) {
- final float fraction =
- ((float) mLastAdjustTop - mHiddenTop) / (mShownTop - mHiddenTop);
- final float progress = mTargetAdjusted ? fraction : 1.f - fraction;
- mAnimation.setCurrentFraction(progress);
- }
-
- mAnimation.addUpdateListener(animation -> {
- SurfaceControl.Transaction t = mTransactionPool.acquire();
- float value = (float) animation.getAnimatedValue();
- onProgress(value, t);
- t.apply();
- mTransactionPool.release(t);
- });
- mAnimation.setInterpolator(DisplayImeController.INTERPOLATOR);
- mAnimation.addListener(new AnimatorListenerAdapter() {
- private boolean mCancel = false;
- @Override
- public void onAnimationCancel(Animator animation) {
- mCancel = true;
- }
- @Override
- public void onAnimationEnd(Animator animation) {
- SurfaceControl.Transaction t = mTransactionPool.acquire();
- onEnd(mCancel, t);
- t.apply();
- mTransactionPool.release(t);
- mAnimation = null;
- }
- });
- mAnimation.start();
- }
-
- private String dumpState() {
- return "top:" + mHiddenTop + "->" + mShownTop
- + " adj:" + mAdjusted + "->" + mTargetAdjusted + "(" + mLastAdjustTop + ")"
- + " shw:" + mImeWasShown + "->" + mTargetShown
- + " dims:" + mLastPrimaryDim + "," + mLastSecondaryDim
- + "->" + mTargetPrimaryDim + "," + mTargetSecondaryDim
- + " shf:" + mSecondaryHasFocus + " desync:" + (mAnimation != null)
- + " paus:" + mPaused + "[" + mPausedTargetAdjusted + "]";
- }
-
- /** Completely aborts/resets adjustment state */
- public void pause(int displayId) {
- if (DEBUG) Slog.d(TAG, "ime pause posting " + dumpState());
- mHandler.post(() -> {
- if (DEBUG) Slog.d(TAG, "ime pause run posted " + dumpState());
- if (mPaused) {
- return;
- }
- mPaused = true;
- mPausedTargetAdjusted = mTargetAdjusted;
- mTargetAdjusted = false;
- mTargetPrimaryDim = mTargetSecondaryDim = 0.f;
- updateImeAdjustState();
- startAsyncAnimation();
- if (mAnimation != null) {
- mAnimation.end();
- }
- });
- }
-
- public void resume(int displayId) {
- if (DEBUG) Slog.d(TAG, "ime resume posting " + dumpState());
- mHandler.post(() -> {
- if (DEBUG) Slog.d(TAG, "ime resume run posted " + dumpState());
- if (!mPaused) {
- return;
- }
- mPaused = false;
- mTargetAdjusted = mPausedTargetAdjusted;
- updateDimTargets();
- if ((mTargetAdjusted != mAdjusted) && !mMinimized && mView != null) {
- // End unminimize animations since they conflict with adjustment animations.
- mView.finishAnimations();
- }
- updateImeAdjustState();
- startAsyncAnimation();
- });
- }
- }
- private final DividerImeController mImePositionProcessor = new DividerImeController();
+ private final DividerImeController mImePositionProcessor;
private TaskStackChangeListener mActivityRestartListener = new TaskStackChangeListener() {
@Override
@@ -465,6 +159,7 @@
mRecentsOptionalLazy = recentsOptionalLazy;
mForcedResizableController = new ForcedResizableInfoActivityController(context, this);
mTransactionPool = transactionPool;
+ mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mHandler);
}
@Override
@@ -582,7 +277,8 @@
mView = (DividerView)
LayoutInflater.from(dctx).inflate(R.layout.docked_stack_divider, null);
DisplayLayout displayLayout = mDisplayController.getDisplayLayout(mContext.getDisplayId());
- mView.injectDependencies(mWindowManager, mDividerState, this, mSplits, mSplitLayout);
+ mView.injectDependencies(mWindowManager, mDividerState, this, mSplits, mSplitLayout,
+ mImePositionProcessor);
mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE);
mView.setMinimizedDockStack(mMinimized, mHomeStackResizable);
final int size = dctx.getResources().getDimensionPixelSize(
@@ -830,6 +526,10 @@
}
}
+ SplitDisplayLayout getSplitLayout() {
+ return mSplitLayout;
+ }
+
/** @return the container token for the secondary split root task. */
public WindowContainerToken getSecondaryRoot() {
if (mSplits == null || mSplits.mSecondary == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java
new file mode 100644
index 0000000..1e0c07b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.stackdivider;
+
+import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
+import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.util.Slog;
+import android.view.SurfaceControl;
+import android.window.TaskOrganizer;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+import android.window.WindowOrganizer;
+
+import androidx.annotation.Nullable;
+
+import com.android.systemui.TransactionPool;
+import com.android.systemui.wm.DisplayImeController;
+
+class DividerImeController implements DisplayImeController.ImePositionProcessor {
+ private static final String TAG = "DividerImeController";
+ private static final boolean DEBUG = Divider.DEBUG;
+
+ private static final float ADJUSTED_NONFOCUS_DIM = 0.3f;
+
+ private final SplitScreenTaskOrganizer mSplits;
+ private final TransactionPool mTransactionPool;
+ private final Handler mHandler;
+
+ /**
+ * These are the y positions of the top of the IME surface when it is hidden and when it is
+ * shown respectively. These are NOT necessarily the top of the visible IME itself.
+ */
+ private int mHiddenTop = 0;
+ private int mShownTop = 0;
+
+ // The following are target states (what we are curretly animating towards).
+ /**
+ * {@code true} if, at the end of the animation, the split task positions should be
+ * adjusted by height of the IME. This happens when the secondary split is the IME target.
+ */
+ private boolean mTargetAdjusted = false;
+ /**
+ * {@code true} if, at the end of the animation, the IME should be shown/visible
+ * regardless of what has focus.
+ */
+ private boolean mTargetShown = false;
+ private float mTargetPrimaryDim = 0.f;
+ private float mTargetSecondaryDim = 0.f;
+
+ // The following are the current (most recent) states set during animation
+ /** {@code true} if the secondary split has IME focus. */
+ private boolean mSecondaryHasFocus = false;
+ /** The dimming currently applied to the primary/secondary splits. */
+ private float mLastPrimaryDim = 0.f;
+ private float mLastSecondaryDim = 0.f;
+ /** The most recent y position of the top of the IME surface */
+ private int mLastAdjustTop = -1;
+
+ // The following are states reached last time an animation fully completed.
+ /** {@code true} if the IME was shown/visible by the last-completed animation. */
+ private boolean mImeWasShown = false;
+ /** {@code true} if the split positions were adjusted by the last-completed animation. */
+ private boolean mAdjusted = false;
+
+ /**
+ * When some aspect of split-screen needs to animate independent from the IME,
+ * this will be non-null and control split animation.
+ */
+ @Nullable
+ private ValueAnimator mAnimation = null;
+
+ private boolean mPaused = true;
+ private boolean mPausedTargetAdjusted = false;
+
+ DividerImeController(SplitScreenTaskOrganizer splits, TransactionPool pool, Handler handler) {
+ mSplits = splits;
+ mTransactionPool = pool;
+ mHandler = handler;
+ }
+
+ private DividerView getView() {
+ return mSplits.mDivider.getView();
+ }
+
+ private SplitDisplayLayout getLayout() {
+ return mSplits.mDivider.getSplitLayout();
+ }
+
+ private boolean isDividerVisible() {
+ return mSplits.mDivider.isDividerVisible();
+ }
+
+ private boolean getSecondaryHasFocus(int displayId) {
+ WindowContainerToken imeSplit = TaskOrganizer.getImeTarget(displayId);
+ return imeSplit != null
+ && (imeSplit.asBinder() == mSplits.mSecondary.token.asBinder());
+ }
+
+ private void updateDimTargets() {
+ final boolean splitIsVisible = !getView().isHidden();
+ mTargetPrimaryDim = (mSecondaryHasFocus && mTargetShown && splitIsVisible)
+ ? ADJUSTED_NONFOCUS_DIM : 0.f;
+ mTargetSecondaryDim = (!mSecondaryHasFocus && mTargetShown && splitIsVisible)
+ ? ADJUSTED_NONFOCUS_DIM : 0.f;
+ }
+
+ @Override
+ public void onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
+ boolean imeShouldShow, SurfaceControl.Transaction t) {
+ if (!isDividerVisible()) {
+ return;
+ }
+ final boolean splitIsVisible = !getView().isHidden();
+ mSecondaryHasFocus = getSecondaryHasFocus(displayId);
+ final boolean targetAdjusted = splitIsVisible && imeShouldShow && mSecondaryHasFocus
+ && !getLayout().mDisplayLayout.isLandscape();
+ mHiddenTop = hiddenTop;
+ mShownTop = shownTop;
+ mTargetShown = imeShouldShow;
+ if (mLastAdjustTop < 0) {
+ mLastAdjustTop = imeShouldShow ? hiddenTop : shownTop;
+ } else if (mLastAdjustTop != (imeShouldShow ? mShownTop : mHiddenTop)) {
+ if (mTargetAdjusted != targetAdjusted && targetAdjusted == mAdjusted) {
+ // Check for an "interruption" of an existing animation. In this case, we
+ // need to fake-flip the last-known state direction so that the animation
+ // completes in the other direction.
+ mAdjusted = mTargetAdjusted;
+ } else if (targetAdjusted && mTargetAdjusted && mAdjusted) {
+ // Already fully adjusted for IME, but IME height has changed; so, force-start
+ // an async animation to the new IME height.
+ mAdjusted = false;
+ }
+ }
+ if (mPaused) {
+ mPausedTargetAdjusted = targetAdjusted;
+ if (DEBUG) Slog.d(TAG, " ime starting but paused " + dumpState());
+ return;
+ }
+ mTargetAdjusted = targetAdjusted;
+ updateDimTargets();
+ if (DEBUG) Slog.d(TAG, " ime starting. vis:" + splitIsVisible + " " + dumpState());
+ if (mAnimation != null || (mImeWasShown && imeShouldShow
+ && mTargetAdjusted != mAdjusted)) {
+ // We need to animate adjustment independently of the IME position, so
+ // start our own animation to drive adjustment. This happens when a
+ // different split's editor has gained focus while the IME is still visible.
+ startAsyncAnimation();
+ }
+ if (splitIsVisible) {
+ // If split is hidden, we don't want to trigger any relayouts that would cause the
+ // divider to show again.
+ updateImeAdjustState();
+ }
+ }
+
+ private void updateImeAdjustState() {
+ // Reposition the server's secondary split position so that it evaluates
+ // insets properly.
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ final SplitDisplayLayout splitLayout = getLayout();
+ if (mTargetAdjusted) {
+ splitLayout.updateAdjustedBounds(mShownTop, mHiddenTop, mShownTop);
+ wct.setBounds(mSplits.mSecondary.token, splitLayout.mAdjustedSecondary);
+ // "Freeze" the configuration size so that the app doesn't get a config
+ // or relaunch. This is required because normally nav-bar contributes
+ // to configuration bounds (via nondecorframe).
+ Rect adjustAppBounds = new Rect(mSplits.mSecondary.configuration
+ .windowConfiguration.getAppBounds());
+ adjustAppBounds.offset(0, splitLayout.mAdjustedSecondary.top
+ - splitLayout.mSecondary.top);
+ wct.setAppBounds(mSplits.mSecondary.token, adjustAppBounds);
+ wct.setScreenSizeDp(mSplits.mSecondary.token,
+ mSplits.mSecondary.configuration.screenWidthDp,
+ mSplits.mSecondary.configuration.screenHeightDp);
+
+ wct.setBounds(mSplits.mPrimary.token, splitLayout.mAdjustedPrimary);
+ adjustAppBounds = new Rect(mSplits.mPrimary.configuration
+ .windowConfiguration.getAppBounds());
+ adjustAppBounds.offset(0, splitLayout.mAdjustedPrimary.top
+ - splitLayout.mPrimary.top);
+ wct.setAppBounds(mSplits.mPrimary.token, adjustAppBounds);
+ wct.setScreenSizeDp(mSplits.mPrimary.token,
+ mSplits.mPrimary.configuration.screenWidthDp,
+ mSplits.mPrimary.configuration.screenHeightDp);
+ } else {
+ wct.setBounds(mSplits.mSecondary.token, splitLayout.mSecondary);
+ wct.setAppBounds(mSplits.mSecondary.token, null);
+ wct.setScreenSizeDp(mSplits.mSecondary.token,
+ SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
+ wct.setBounds(mSplits.mPrimary.token, splitLayout.mPrimary);
+ wct.setAppBounds(mSplits.mPrimary.token, null);
+ wct.setScreenSizeDp(mSplits.mPrimary.token,
+ SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
+ }
+
+ WindowOrganizer.applyTransaction(wct);
+
+ // Update all the adjusted-for-ime states
+ if (!mPaused) {
+ final DividerView view = getView();
+ if (view != null) {
+ view.setAdjustedForIme(mTargetShown, mTargetShown
+ ? DisplayImeController.ANIMATION_DURATION_SHOW_MS
+ : DisplayImeController.ANIMATION_DURATION_HIDE_MS);
+ }
+ }
+ mSplits.mDivider.setAdjustedForIme(mTargetShown && !mPaused);
+ }
+
+ @Override
+ public void onImePositionChanged(int displayId, int imeTop,
+ SurfaceControl.Transaction t) {
+ if (mAnimation != null || !isDividerVisible() || mPaused) {
+ // Not synchronized with IME anymore, so return.
+ return;
+ }
+ final float fraction = ((float) imeTop - mHiddenTop) / (mShownTop - mHiddenTop);
+ final float progress = mTargetShown ? fraction : 1.f - fraction;
+ onProgress(progress, t);
+ }
+
+ @Override
+ public void onImeEndPositioning(int displayId, boolean cancelled,
+ SurfaceControl.Transaction t) {
+ if (mAnimation != null || !isDividerVisible() || mPaused) {
+ // Not synchronized with IME anymore, so return.
+ return;
+ }
+ onEnd(cancelled, t);
+ }
+
+ private void onProgress(float progress, SurfaceControl.Transaction t) {
+ final DividerView view = getView();
+ if (mTargetAdjusted != mAdjusted && !mPaused) {
+ final SplitDisplayLayout splitLayout = getLayout();
+ final float fraction = mTargetAdjusted ? progress : 1.f - progress;
+ mLastAdjustTop = (int) (fraction * mShownTop + (1.f - fraction) * mHiddenTop);
+ splitLayout.updateAdjustedBounds(mLastAdjustTop, mHiddenTop, mShownTop);
+ view.resizeSplitSurfaces(t, splitLayout.mAdjustedPrimary,
+ splitLayout.mAdjustedSecondary);
+ }
+ final float invProg = 1.f - progress;
+ view.setResizeDimLayer(t, true /* primary */,
+ mLastPrimaryDim * invProg + progress * mTargetPrimaryDim);
+ view.setResizeDimLayer(t, false /* primary */,
+ mLastSecondaryDim * invProg + progress * mTargetSecondaryDim);
+ }
+
+ void setDimsHidden(SurfaceControl.Transaction t, boolean hidden) {
+ final DividerView view = getView();
+ if (hidden) {
+ view.setResizeDimLayer(t, true /* primary */, 0.f /* alpha */);
+ view.setResizeDimLayer(t, false /* primary */, 0.f /* alpha */);
+ } else {
+ updateDimTargets();
+ view.setResizeDimLayer(t, true /* primary */, mTargetPrimaryDim);
+ view.setResizeDimLayer(t, false /* primary */, mTargetSecondaryDim);
+ }
+ }
+
+ private void onEnd(boolean cancelled, SurfaceControl.Transaction t) {
+ if (!cancelled) {
+ onProgress(1.f, t);
+ mAdjusted = mTargetAdjusted;
+ mImeWasShown = mTargetShown;
+ mLastAdjustTop = mAdjusted ? mShownTop : mHiddenTop;
+ mLastPrimaryDim = mTargetPrimaryDim;
+ mLastSecondaryDim = mTargetSecondaryDim;
+ }
+ }
+
+ private void startAsyncAnimation() {
+ if (mAnimation != null) {
+ mAnimation.cancel();
+ }
+ mAnimation = ValueAnimator.ofFloat(0.f, 1.f);
+ mAnimation.setDuration(DisplayImeController.ANIMATION_DURATION_SHOW_MS);
+ if (mTargetAdjusted != mAdjusted) {
+ final float fraction =
+ ((float) mLastAdjustTop - mHiddenTop) / (mShownTop - mHiddenTop);
+ final float progress = mTargetAdjusted ? fraction : 1.f - fraction;
+ mAnimation.setCurrentFraction(progress);
+ }
+
+ mAnimation.addUpdateListener(animation -> {
+ SurfaceControl.Transaction t = mTransactionPool.acquire();
+ float value = (float) animation.getAnimatedValue();
+ onProgress(value, t);
+ t.apply();
+ mTransactionPool.release(t);
+ });
+ mAnimation.setInterpolator(DisplayImeController.INTERPOLATOR);
+ mAnimation.addListener(new AnimatorListenerAdapter() {
+ private boolean mCancel = false;
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancel = true;
+ }
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ SurfaceControl.Transaction t = mTransactionPool.acquire();
+ onEnd(mCancel, t);
+ t.apply();
+ mTransactionPool.release(t);
+ mAnimation = null;
+ }
+ });
+ mAnimation.start();
+ }
+
+ private String dumpState() {
+ return "top:" + mHiddenTop + "->" + mShownTop
+ + " adj:" + mAdjusted + "->" + mTargetAdjusted + "(" + mLastAdjustTop + ")"
+ + " shw:" + mImeWasShown + "->" + mTargetShown
+ + " dims:" + mLastPrimaryDim + "," + mLastSecondaryDim
+ + "->" + mTargetPrimaryDim + "," + mTargetSecondaryDim
+ + " shf:" + mSecondaryHasFocus + " desync:" + (mAnimation != null)
+ + " paus:" + mPaused + "[" + mPausedTargetAdjusted + "]";
+ }
+
+ /** Completely aborts/resets adjustment state */
+ public void pause(int displayId) {
+ if (DEBUG) Slog.d(TAG, "ime pause posting " + dumpState());
+ mHandler.post(() -> {
+ if (DEBUG) Slog.d(TAG, "ime pause run posted " + dumpState());
+ if (mPaused) {
+ return;
+ }
+ mPaused = true;
+ mPausedTargetAdjusted = mTargetAdjusted;
+ mTargetAdjusted = false;
+ mTargetPrimaryDim = mTargetSecondaryDim = 0.f;
+ updateImeAdjustState();
+ startAsyncAnimation();
+ if (mAnimation != null) {
+ mAnimation.end();
+ }
+ });
+ }
+
+ public void resume(int displayId) {
+ if (DEBUG) Slog.d(TAG, "ime resume posting " + dumpState());
+ mHandler.post(() -> {
+ if (DEBUG) Slog.d(TAG, "ime resume run posted " + dumpState());
+ if (!mPaused) {
+ return;
+ }
+ mPaused = false;
+ mTargetAdjusted = mPausedTargetAdjusted;
+ updateDimTargets();
+ final DividerView view = getView();
+ if ((mTargetAdjusted != mAdjusted) && !mSplits.mDivider.isMinimized() && view != null) {
+ // End unminimize animations since they conflict with adjustment animations.
+ view.finishAnimations();
+ }
+ updateImeAdjustState();
+ startAsyncAnimation();
+ });
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index db89cea..e349b5af 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -19,7 +19,6 @@
import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
import static android.view.WindowManager.DOCKED_RIGHT;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
import android.animation.AnimationHandler;
import android.animation.Animator;
@@ -39,7 +38,6 @@
import android.util.AttributeSet;
import android.util.Slog;
import android.view.Display;
-import android.view.InsetsState;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.SurfaceControl;
@@ -48,10 +46,8 @@
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewConfiguration;
-import android.view.ViewRootImpl;
import android.view.ViewTreeObserver.InternalInsetsInfo;
import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
-import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -65,9 +61,10 @@
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
import com.android.internal.policy.DockedDividerUtils;
+import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.shared.system.WindowManagerWrapper;
+import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.statusbar.FlingAnimationUtils;
import java.util.function.Consumer;
@@ -120,7 +117,6 @@
private int mStartY;
private int mStartPosition;
private int mDockSide;
- private final int[] mTempInt2 = new int[2];
private boolean mMoving;
private int mTouchSlop;
private boolean mBackgroundLifted;
@@ -147,8 +143,8 @@
private VelocityTracker mVelocityTracker;
private FlingAnimationUtils mFlingAnimationUtils;
private SplitDisplayLayout mSplitLayout;
+ private DividerImeController mImeController;
private DividerCallbacks mCallback;
- private final Rect mStableInsets = new Rect();
private final AnimationHandler mAnimationHandler = new AnimationHandler();
private boolean mGrowRecents;
@@ -336,29 +332,6 @@
}
@Override
- public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- if (isAttachedToWindow()
- && ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL) {
- // Our window doesn't cover entire display, so we use the display frame to re-calculate
- // the insets.
- final InsetsState state = getWindowInsetsController().getState();
- insets = state.calculateInsets(state.getDisplayFrame(),
- null /* ignoringVisibilityState */, insets.isRound(),
- insets.shouldAlwaysConsumeSystemBars(), insets.getDisplayCutout(),
- 0 /* legacySystemUiFlags */,
- SOFT_INPUT_ADJUST_NOTHING, null /* typeSideMap */);
- }
- if (mStableInsets.left != insets.getStableInsetLeft()
- || mStableInsets.top != insets.getStableInsetTop()
- || mStableInsets.right != insets.getStableInsetRight()
- || mStableInsets.bottom != insets.getStableInsetBottom()) {
- mStableInsets.set(insets.getStableInsetLeft(), insets.getStableInsetTop(),
- insets.getStableInsetRight(), insets.getStableInsetBottom());
- }
- return super.onApplyWindowInsets(insets);
- }
-
- @Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mFirstLayout) {
@@ -381,16 +354,19 @@
if (changed) {
mWindowManagerProxy.setTouchRegion(new Rect(mHandle.getLeft(), mHandle.getTop(),
mHandle.getRight(), mHandle.getBottom()));
+ notifySplitScreenBoundsChanged();
}
}
public void injectDependencies(DividerWindowManager windowManager, DividerState dividerState,
- DividerCallbacks callback, SplitScreenTaskOrganizer tiles, SplitDisplayLayout sdl) {
+ DividerCallbacks callback, SplitScreenTaskOrganizer tiles, SplitDisplayLayout sdl,
+ DividerImeController imeController) {
mWindowManager = windowManager;
mState = dividerState;
mCallback = callback;
mTiles = tiles;
mSplitLayout = sdl;
+ mImeController = imeController;
if (mState.mRatioPositionBeforeMinimized == 0) {
// Set the middle target as the initial state
@@ -405,19 +381,7 @@
}
public Rect getNonMinimizedSplitScreenSecondaryBounds() {
- calculateBoundsForPosition(mSnapTargetBeforeMinimized.position,
- DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect);
- mOtherTaskRect.bottom -= mStableInsets.bottom;
- switch (mDockSide) {
- case WindowManager.DOCKED_LEFT:
- mOtherTaskRect.top += mStableInsets.top;
- mOtherTaskRect.right -= mStableInsets.right;
- break;
- case WindowManager.DOCKED_RIGHT:
- mOtherTaskRect.top += mStableInsets.top;
- mOtherTaskRect.left += mStableInsets.left;
- break;
- }
+ mOtherTaskRect.set(mSplitLayout.mSecondary);
return mOtherTaskRect;
}
@@ -442,6 +406,7 @@
} else {
t.show(sc);
}
+ mImeController.setDimsHidden(t, hidden);
t.apply();
mTiles.releaseTransaction(t);
});
@@ -681,6 +646,7 @@
saveSnapTargetBeforeMinimized(saveTarget);
}
}
+ notifySplitScreenBoundsChanged();
};
anim.addListener(new AnimatorListenerAdapter() {
@@ -713,6 +679,25 @@
return anim;
}
+ private void notifySplitScreenBoundsChanged() {
+ mOtherTaskRect.set(mSplitLayout.mSecondary);
+
+ mTmpRect.set(mSplitLayout.mDisplayLayout.stableInsets());
+ switch (mSplitLayout.getPrimarySplitSide()) {
+ case WindowManager.DOCKED_LEFT:
+ mTmpRect.left = 0;
+ break;
+ case WindowManager.DOCKED_RIGHT:
+ mTmpRect.right = 0;
+ break;
+ case WindowManager.DOCKED_TOP:
+ mTmpRect.top = 0;
+ break;
+ }
+ Dependency.get(OverviewProxyService.class)
+ .notifySplitScreenBoundsChanged(mOtherTaskRect, mTmpRect);
+ }
+
private void cancelFlingAnimation() {
if (mCurrentAnimator != null) {
mCurrentAnimator.cancel();
@@ -846,8 +831,7 @@
mDockedStackMinimized = minimized;
if (mSplitLayout.mDisplayLayout.rotation() != mDefaultDisplay.getRotation()) {
// Splitscreen to minimize is about to starts after rotating landscape to seascape,
- // update insets, display info and snap algorithm targets
- WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
+ // update display info and snap algorithm targets
repositionSnapTargetBeforeMinimized();
}
if (mIsInMinimizeInteraction != minimized || mCurrentAnimator != null) {
@@ -1149,7 +1133,7 @@
// Move a right-docked-app to line up with the divider while dragging it
if (mDockSide == DOCKED_RIGHT) {
- mDockedTaskRect.offset(Math.max(position, mStableInsets.left - mDividerSize)
+ mDockedTaskRect.offset(Math.max(position, -mDividerSize)
- mDockedTaskRect.left + mDividerSize, 0);
}
resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect,
@@ -1164,7 +1148,7 @@
// Move a docked app if from the right in position with the divider up to insets
if (mDockSide == DOCKED_RIGHT) {
- mDockedTaskRect.offset(Math.max(position, mStableInsets.left - mDividerSize)
+ mDockedTaskRect.offset(Math.max(position, -mDividerSize)
- mDockedTaskRect.left + mDividerSize, 0);
}
calculateBoundsForPosition(taskPosition, DockedDividerUtils.invertDockSide(mDockSide),
@@ -1180,7 +1164,7 @@
// Move a right-docked-app to line up with the divider while dragging it
if (mDockSide == DOCKED_RIGHT) {
- mDockedTaskRect.offset(position - mStableInsets.left + mDividerSize, 0);
+ mDockedTaskRect.offset(position + mDividerSize, 0);
}
resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect);
} else if (taskPosition != TASK_POSITION_SAME) {
@@ -1238,34 +1222,10 @@
float fraction = getSnapAlgorithm().calculateDismissingFraction(position);
fraction = Math.max(0, Math.min(fraction, 1f));
fraction = DIM_INTERPOLATOR.getInterpolation(fraction);
- if (hasInsetsAtDismissTarget(dismissTarget)) {
-
- // Less darkening with system insets.
- fraction *= 0.8f;
- }
return fraction;
}
/**
- * @return true if and only if there are system insets at the location of the dismiss target
- */
- private boolean hasInsetsAtDismissTarget(SnapTarget dismissTarget) {
- if (isHorizontalDivision()) {
- if (dismissTarget == getSnapAlgorithm().getDismissStartTarget()) {
- return mStableInsets.top != 0;
- } else {
- return mStableInsets.bottom != 0;
- }
- } else {
- if (dismissTarget == getSnapAlgorithm().getDismissStartTarget()) {
- return mStableInsets.left != 0;
- } else {
- return mStableInsets.right != 0;
- }
- }
- }
-
- /**
* When the snap target is dismissing one side, make sure that the dismissing side doesn't get
* 0 size.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt
new file mode 100644
index 0000000..8248fc9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt
@@ -0,0 +1,87 @@
+package com.android.systemui.statusbar
+
+import android.app.Notification
+import android.os.RemoteException
+import com.android.internal.statusbar.IStatusBarService
+import com.android.internal.statusbar.NotificationVisibility
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.Assert
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * Class to shim calls to IStatusBarManager#onNotificationClick/#onNotificationActionClick that
+ * allow an in-process notification to go out (e.g., for tracking interactions) as well as
+ * sending the messages along to system server.
+ *
+ * NOTE: this class eats exceptions from system server, as no current client of these APIs cares
+ * about errors
+ */
+@Singleton
+public class NotificationClickNotifier @Inject constructor(
+ val barService: IStatusBarService,
+ @Main val mainExecutor: Executor
+) {
+ val listeners = mutableListOf<NotificationInteractionListener>()
+
+ fun addNotificationInteractionListener(listener: NotificationInteractionListener) {
+ Assert.isMainThread()
+ listeners.add(listener)
+ }
+
+ fun removeNotificationInteractionListener(listener: NotificationInteractionListener) {
+ Assert.isMainThread()
+ listeners.remove(listener)
+ }
+
+ private fun notifyListenersAboutInteraction(key: String) {
+ for (l in listeners) {
+ l.onNotificationInteraction(key)
+ }
+ }
+
+ fun onNotificationActionClick(
+ key: String,
+ actionIndex: Int,
+ action: Notification.Action,
+ visibility: NotificationVisibility,
+ generatedByAssistant: Boolean
+ ) {
+ try {
+ barService.onNotificationActionClick(
+ key, actionIndex, action, visibility, generatedByAssistant)
+ } catch (e: RemoteException) {
+ // nothing
+ }
+
+ mainExecutor.execute {
+ notifyListenersAboutInteraction(key)
+ }
+ }
+
+ fun onNotificationClick(
+ key: String,
+ visibility: NotificationVisibility
+ ) {
+ try {
+ barService.onNotificationClick(key, visibility)
+ } catch (e: RemoteException) {
+ // nothing
+ }
+
+ mainExecutor.execute {
+ notifyListenersAboutInteraction(key)
+ }
+ }
+}
+
+/**
+ * Interface for listeners to get notified when a notification is interacted with via a click or
+ * interaction with remote input or actions
+ */
+interface NotificationInteractionListener {
+ fun onNotificationInteraction(key: String)
+}
+
+private const val TAG = "NotificationClickNotifier"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt
new file mode 100644
index 0000000..2ed04eb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt
@@ -0,0 +1,40 @@
+package com.android.systemui.statusbar
+
+import com.android.systemui.statusbar.notification.NotificationEntryManager
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * Class to track user interaction with notifications. It's a glorified map of key : bool that can
+ * merge multiple "user interacted with notification" signals into a single place.
+ */
+@Singleton
+class NotificationInteractionTracker @Inject constructor(
+ private val clicker: NotificationClickNotifier,
+ private val entryManager: NotificationEntryManager
+) : NotifCollectionListener, NotificationInteractionListener {
+ private val interactions = mutableMapOf<String, Boolean>()
+
+ init {
+ clicker.addNotificationInteractionListener(this)
+ entryManager.addCollectionListener(this)
+ }
+
+ fun hasUserInteractedWith(key: String): Boolean = key in interactions
+
+ override fun onEntryAdded(entry: NotificationEntry) {
+ interactions[entry.key] = false
+ }
+
+ override fun onEntryCleanUp(entry: NotificationEntry) {
+ interactions.remove(entry.key)
+ }
+
+ override fun onNotificationInteraction(key: String) {
+ interactions[key] = true
+ }
+}
+
+private const val TAG = "NotificationInteractionTracker"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 2baab61..03424c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -35,7 +35,6 @@
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.os.Handler;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -43,7 +42,6 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -92,9 +90,9 @@
private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray();
private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray();
private final UserManager mUserManager;
- private final IStatusBarService mBarService;
private final List<UserChangedListener> mListeners = new ArrayList<>();
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final NotificationClickNotifier mClickNotifier;
private boolean mShowLockscreenNotifications;
private boolean mAllowLockscreenRemoteInput;
@@ -170,11 +168,7 @@
final NotificationVisibility nv = NotificationVisibility.obtain(
notificationKey,
rank, count, true, location);
- try {
- mBarService.onNotificationClick(notificationKey, nv);
- } catch (RemoteException exception) {
- /* ignore */
- }
+ mClickNotifier.onNotificationClick(notificationKey, nv);
}
break;
}
@@ -203,7 +197,7 @@
BroadcastDispatcher broadcastDispatcher,
DevicePolicyManager devicePolicyManager,
UserManager userManager,
- IStatusBarService iStatusBarService,
+ NotificationClickNotifier clickNotifier,
KeyguardManager keyguardManager,
StatusBarStateController statusBarStateController,
@Main Handler mainHandler,
@@ -214,7 +208,7 @@
mDevicePolicyManager = devicePolicyManager;
mUserManager = userManager;
mCurrentUserId = ActivityManager.getCurrentUser();
- mBarService = iStatusBarService;
+ mClickNotifier = clickNotifier;
statusBarStateController.addCallback(this);
mLockPatternUtils = new LockPatternUtils(context);
mKeyguardManager = keyguardManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 9f4932e..8ed69d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_MEDIA_FAKE_ARTWORK;
import static com.android.systemui.statusbar.phone.StatusBar.ENABLE_LOCKSCREEN_WALLPAPER;
import static com.android.systemui.statusbar.phone.StatusBar.SHOW_LOCKSCREEN_MEDIA_ARTWORK;
@@ -44,8 +45,6 @@
import android.view.View;
import android.widget.ImageView;
-import androidx.annotation.NonNull;
-
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dependency;
@@ -53,8 +52,8 @@
import com.android.systemui.Interpolators;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.media.MediaData;
import com.android.systemui.media.MediaDataManager;
+import com.android.systemui.media.MediaDeviceManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.dagger.StatusBarModule;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
@@ -70,6 +69,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.Utils;
+import com.android.systemui.util.concurrency.DelayableExecutor;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -79,7 +79,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
import dagger.Lazy;
@@ -90,6 +90,7 @@
public class NotificationMediaManager implements Dumpable {
private static final String TAG = "NotificationMediaManager";
public static final boolean DEBUG_MEDIA = false;
+ private static final long PAUSED_MEDIA_TIMEOUT = TimeUnit.MINUTES.toMillis(10);
private final StatusBarStateController mStatusBarStateController
= Dependency.get(StatusBarStateController.class);
@@ -106,6 +107,7 @@
}
private final NotificationEntryManager mEntryManager;
+ private final MediaDataManager mMediaDataManager;
@Nullable
private Lazy<NotificationShadeWindowController> mNotificationShadeWindowController;
@@ -117,7 +119,7 @@
@Nullable
private LockscreenWallpaper mLockscreenWallpaper;
- private final Executor mMainExecutor;
+ private final DelayableExecutor mMainExecutor;
private final Context mContext;
private final MediaSessionManager mMediaSessionManager;
@@ -130,6 +132,7 @@
private MediaController mMediaController;
private String mMediaNotificationKey;
private MediaMetadata mMediaMetadata;
+ private Runnable mMediaTimeoutCancellation;
private BackDropView mBackdrop;
private ImageView mBackdropFront;
@@ -159,11 +162,36 @@
if (DEBUG_MEDIA) {
Log.v(TAG, "DEBUG_MEDIA: onPlaybackStateChanged: " + state);
}
+ if (mMediaTimeoutCancellation != null) {
+ mMediaTimeoutCancellation.run();
+ mMediaTimeoutCancellation = null;
+ }
if (state != null) {
if (!isPlaybackActive(state.getState())) {
clearCurrentMediaNotification();
}
findAndUpdateMediaNotifications();
+ scheduleMediaTimeout(state);
+ }
+ }
+
+ private void scheduleMediaTimeout(PlaybackState state) {
+ final NotificationEntry entry;
+ synchronized (mEntryManager) {
+ entry = mEntryManager.getActiveNotificationUnfiltered(mMediaNotificationKey);
+ }
+ if (entry != null) {
+ if (!isPlayingState(state.getState())) {
+ mMediaTimeoutCancellation = mMainExecutor.executeDelayed(() -> {
+ synchronized (mEntryManager) {
+ if (mMediaNotificationKey == null) {
+ return;
+ }
+ mEntryManager.removeNotification(mMediaNotificationKey, null,
+ UNDEFINED_DISMISS_REASON);
+ }
+ }, PAUSED_MEDIA_TIMEOUT);
+ }
}
}
@@ -189,9 +217,10 @@
NotificationEntryManager notificationEntryManager,
MediaArtworkProcessor mediaArtworkProcessor,
KeyguardBypassController keyguardBypassController,
- @Main Executor mainExecutor,
+ @Main DelayableExecutor mainExecutor,
DeviceConfigProxy deviceConfig,
- MediaDataManager mediaDataManager) {
+ MediaDataManager mediaDataManager,
+ MediaDeviceManager mediaDeviceManager) {
mContext = context;
mMediaArtworkProcessor = mediaArtworkProcessor;
mKeyguardBypassController = keyguardBypassController;
@@ -205,17 +234,20 @@
mNotificationShadeWindowController = notificationShadeWindowController;
mEntryManager = notificationEntryManager;
mMainExecutor = mainExecutor;
+ mMediaDataManager = mediaDataManager;
notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
public void onPendingEntryAdded(NotificationEntry entry) {
mediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
+ mediaDeviceManager.onNotificationAdded(entry.getKey(), entry.getSbn());
}
@Override
public void onPreEntryUpdated(NotificationEntry entry) {
mediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
+ mediaDeviceManager.onNotificationAdded(entry.getKey(), entry.getSbn());
}
@Override
@@ -236,6 +268,7 @@
int reason) {
onNotificationRemoved(entry.getKey());
mediaDataManager.onNotificationRemoved(entry.getKey());
+ mediaDeviceManager.onNotificationRemoved(entry.getKey());
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 9181c69..710ac9e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -28,6 +28,7 @@
import android.app.RemoteInputHistoryItem;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.UserInfo;
import android.net.Uri;
import android.os.Handler;
import android.os.RemoteException;
@@ -123,6 +124,7 @@
private final KeyguardManager mKeyguardManager;
private final StatusBarStateController mStatusBarStateController;
private final RemoteInputUriController mRemoteInputUriController;
+ private final NotificationClickNotifier mClickNotifier;
protected RemoteInputController mRemoteInputController;
protected NotificationLifetimeExtender.NotificationSafeToRemoveCallback
@@ -214,11 +216,7 @@
mEntryManager.getActiveNotificationUnfiltered(key));
final NotificationVisibility nv =
NotificationVisibility.obtain(key, rank, count, true, location);
- try {
- mBarService.onNotificationActionClick(key, buttonIndex, action, nv, false);
- } catch (RemoteException e) {
- // Ignore
- }
+ mClickNotifier.onNotificationActionClick(key, buttonIndex, action, nv, false);
}
private NotificationEntry getNotificationForParent(ViewParent parent) {
@@ -275,6 +273,7 @@
StatusBarStateController statusBarStateController,
@Main Handler mainHandler,
RemoteInputUriController remoteInputUriController,
+ NotificationClickNotifier clickNotifier,
ActionClickLogger logger) {
mContext = context;
mLockscreenUserManager = lockscreenUserManager;
@@ -290,6 +289,7 @@
mKeyguardManager = context.getSystemService(KeyguardManager.class);
mStatusBarStateController = statusBarStateController;
mRemoteInputUriController = remoteInputUriController;
+ mClickNotifier = clickNotifier;
notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
@@ -401,15 +401,35 @@
if (!mLockscreenUserManager.shouldAllowLockscreenRemoteInput()) {
final int userId = pendingIntent.getCreatorUserHandle().getIdentifier();
+
+ final boolean isLockedManagedProfile =
+ mUserManager.getUserInfo(userId).isManagedProfile()
+ && mKeyguardManager.isDeviceLocked(userId);
+
+ final boolean isParentUserLocked;
+ if (isLockedManagedProfile) {
+ final UserInfo profileParent = mUserManager.getProfileParent(userId);
+ isParentUserLocked = (profileParent != null)
+ && mKeyguardManager.isDeviceLocked(profileParent.id);
+ } else {
+ isParentUserLocked = false;
+ }
+
if (mLockscreenUserManager.isLockscreenPublicMode(userId)
|| mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
- // Even if we don't have security we should go through this flow, otherwise we won't
- // go to the shade
- mCallback.onLockedRemoteInput(row, view);
+ // If the parent user is no longer locked, and the user to which the remote input
+ // is destined is a locked, managed profile, then onLockedWorkRemoteInput should be
+ // called to unlock it.
+ if (isLockedManagedProfile && !isParentUserLocked) {
+ mCallback.onLockedWorkRemoteInput(userId, row, view);
+ } else {
+ // Even if we don't have security we should go through this flow, otherwise
+ // we won't go to the shade.
+ mCallback.onLockedRemoteInput(row, view);
+ }
return true;
}
- if (mUserManager.getUserInfo(userId).isManagedProfile()
- && mKeyguardManager.isDeviceLocked(userId)) {
+ if (isLockedManagedProfile) {
mCallback.onLockedWorkRemoteInput(userId, row, view);
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 7aae724..d3819e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -37,6 +37,7 @@
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
+import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.NotificationShadeWindowController
import com.android.systemui.statusbar.phone.PanelExpansionListener
import com.android.systemui.statusbar.phone.ScrimController
@@ -60,6 +61,7 @@
private val choreographer: Choreographer,
private val wallpaperManager: WallpaperManager,
private val notificationShadeWindowController: NotificationShadeWindowController,
+ private val dozeParameters: DozeParameters,
dumpManager: DumpManager
) : PanelExpansionListener, Dumpable {
companion object {
@@ -206,9 +208,12 @@
keyguardAnimator?.cancel()
keyguardAnimator = ValueAnimator.ofFloat(1f, 0f).apply {
- duration = keyguardStateController.keyguardFadingAwayDuration
+ // keyguardStateController.keyguardFadingAwayDuration might be zero when unlock by
+ // fingerprint due to there is no window container, see AppTransition#goodToGo.
+ // We use DozeParameters.wallpaperFadeOutDuration as an alternative.
+ duration = dozeParameters.wallpaperFadeOutDuration
startDelay = keyguardStateController.keyguardFadingAwayDelay
- interpolator = Interpolators.DECELERATE_QUINT
+ interpolator = Interpolators.FAST_OUT_SLOW_IN
addUpdateListener { animation: ValueAnimator ->
wakeAndUnlockBlurRadius =
blurUtils.blurRadiusOfRatio(animation.animatedValue as Float)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index 88f148b..6d2cc6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -121,14 +121,14 @@
}
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
- return maybeStartExpansion(event)
+ return canHandleMotionEvent() && startExpansion(event)
}
- private fun maybeStartExpansion(event: MotionEvent): Boolean {
- if (!wakeUpCoordinator.canShowPulsingHuns || qsExpanded ||
- bouncerShowing) {
- return false
- }
+ private fun canHandleMotionEvent(): Boolean {
+ return wakeUpCoordinator.canShowPulsingHuns && !qsExpanded && !bouncerShowing
+ }
+
+ private fun startExpansion(event: MotionEvent): Boolean {
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain()
}
@@ -177,8 +177,12 @@
}
override fun onTouchEvent(event: MotionEvent): Boolean {
- if (!isExpanding) {
- return maybeStartExpansion(event)
+ if (!canHandleMotionEvent()) {
+ return false
+ }
+
+ if (!isExpanding || event.actionMasked == MotionEvent.ACTION_DOWN) {
+ return startExpansion(event)
}
velocityTracker!!.addMovement(event)
val y = event.y
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
index bb96f42..7fc18b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
@@ -35,6 +35,7 @@
public class SmartReplyController {
private final IStatusBarService mBarService;
private final NotificationEntryManager mEntryManager;
+ private final NotificationClickNotifier mClickNotifier;
private Set<String> mSendingKeys = new ArraySet<>();
private Callback mCallback;
@@ -42,9 +43,11 @@
* Injected constructor. See {@link StatusBarModule}.
*/
public SmartReplyController(NotificationEntryManager entryManager,
- IStatusBarService statusBarService) {
+ IStatusBarService statusBarService,
+ NotificationClickNotifier clickNotifier) {
mBarService = statusBarService;
mEntryManager = entryManager;
+ mClickNotifier = clickNotifier;
}
public void setCallback(Callback callback) {
@@ -78,12 +81,8 @@
NotificationLogger.getNotificationLocation(entry);
final NotificationVisibility nv = NotificationVisibility.obtain(
entry.getKey(), rank, count, true, location);
- try {
- mBarService.onNotificationActionClick(
- entry.getKey(), actionIndex, action, nv, generatedByAssistant);
- } catch (RemoteException e) {
- // Nothing to do, system going down
- }
+ mClickNotifier.onNotificationActionClick(
+ entry.getKey(), actionIndex, action, nv, generatedByAssistant);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index b08eb9f..c988e12 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -24,10 +24,12 @@
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.media.MediaDataManager;
+import com.android.systemui.media.MediaDeviceManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.ActionClickLogger;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.MediaArtworkProcessor;
+import com.android.systemui.statusbar.NotificationClickNotifier;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -47,6 +49,7 @@
import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.concurrency.DelayableExecutor;
import java.util.concurrent.Executor;
@@ -75,6 +78,7 @@
StatusBarStateController statusBarStateController,
Handler mainHandler,
RemoteInputUriController remoteInputUriController,
+ NotificationClickNotifier clickNotifier,
ActionClickLogger actionClickLogger) {
return new NotificationRemoteInputManager(
context,
@@ -85,6 +89,7 @@
statusBarStateController,
mainHandler,
remoteInputUriController,
+ clickNotifier,
actionClickLogger);
}
@@ -98,9 +103,10 @@
NotificationEntryManager notificationEntryManager,
MediaArtworkProcessor mediaArtworkProcessor,
KeyguardBypassController keyguardBypassController,
- @Main Executor mainExecutor,
+ @Main DelayableExecutor mainExecutor,
DeviceConfigProxy deviceConfigProxy,
- MediaDataManager mediaDataManager) {
+ MediaDataManager mediaDataManager,
+ MediaDeviceManager mediaDeviceManager) {
return new NotificationMediaManager(
context,
statusBarLazy,
@@ -110,7 +116,8 @@
keyguardBypassController,
mainExecutor,
deviceConfigProxy,
- mediaDataManager);
+ mediaDataManager,
+ mediaDeviceManager);
}
/** */
@@ -128,8 +135,10 @@
@Singleton
@Provides
static SmartReplyController provideSmartReplyController(
- NotificationEntryManager entryManager, IStatusBarService statusBarService) {
- return new SmartReplyController(entryManager, statusBarService);
+ NotificationEntryManager entryManager,
+ IStatusBarService statusBarService,
+ NotificationClickNotifier clickNotifier) {
+ return new SmartReplyController(entryManager, statusBarService, clickNotifier);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
index 2beceb2..97ae83e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
@@ -90,7 +90,7 @@
* @param removedByUser true if the notification was removed by a user action
*/
default void onEntryRemoved(
- NotificationEntry entry,
+ @NonNull NotificationEntry entry,
@Nullable NotificationVisibility visibility,
boolean removedByUser,
int reason) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index d647124..adb51a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -151,6 +151,16 @@
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("NotificationEntryManager state:");
+ pw.println(" mAllNotifications=");
+ if (mAllNotifications.size() == 0) {
+ pw.println("null");
+ } else {
+ int i = 0;
+ for (NotificationEntry entry : mAllNotifications) {
+ dumpEntry(pw, " ", i, entry);
+ i++;
+ }
+ }
pw.print(" mPendingNotifications=");
if (mPendingNotifications.size() == 0) {
pw.println("null");
@@ -350,8 +360,8 @@
private final NotificationHandler mNotifListener = new NotificationHandler() {
@Override
public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
- final boolean isUpdate = mActiveNotifications.containsKey(sbn.getKey());
- if (isUpdate) {
+ final boolean isUpdateToInflatedNotif = mActiveNotifications.containsKey(sbn.getKey());
+ if (isUpdateToInflatedNotif) {
updateNotification(sbn, rankingMap);
} else {
addNotification(sbn, rankingMap);
@@ -442,16 +452,12 @@
}
if (!lifetimeExtended) {
// At this point, we are guaranteed the notification will be removed
+ abortExistingInflation(key, "removeNotification");
mAllNotifications.remove(pendingEntry);
+ mLeakDetector.trackGarbage(pendingEntry);
}
}
- }
-
- if (!lifetimeExtended) {
- abortExistingInflation(key, "removeNotification");
- }
-
- if (entry != null) {
+ } else {
// If a manager needs to keep the notification around for whatever reason, we
// keep the notification
boolean entryDismissed = entry.isRowDismissed();
@@ -469,6 +475,8 @@
if (!lifetimeExtended) {
// At this point, we are guaranteed the notification will be removed
+ abortExistingInflation(key, "removeNotification");
+ mAllNotifications.remove(entry);
// Ensure any managers keeping the lifetime extended stop managing the entry
cancelLifetimeExtension(entry);
@@ -477,13 +485,10 @@
entry.removeRow();
}
- mAllNotifications.remove(entry);
-
// Let's remove the children if this was a summary
handleGroupSummaryRemoved(key);
removeVisibleNotification(key);
updateNotifications("removeNotificationInternal");
- mLeakDetector.trackGarbage(entry);
removedByUser |= entryDismissed;
mLogger.logNotifRemoved(entry.getKey(), removedByUser);
@@ -497,6 +502,7 @@
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onEntryCleanUp(entry);
}
+ mLeakDetector.trackGarbage(entry);
}
}
}
@@ -556,21 +562,26 @@
Ranking ranking = new Ranking();
rankingMap.getRanking(key, ranking);
- NotificationEntry entry = new NotificationEntry(
- notification,
- ranking,
- mFgsFeatureController.isForegroundServiceDismissalEnabled(),
- SystemClock.uptimeMillis());
+ NotificationEntry entry = mPendingNotifications.get(key);
+ if (entry != null) {
+ entry.setSbn(notification);
+ } else {
+ entry = new NotificationEntry(
+ notification,
+ ranking,
+ mFgsFeatureController.isForegroundServiceDismissalEnabled(),
+ SystemClock.uptimeMillis());
+ mAllNotifications.add(entry);
+ mLeakDetector.trackInstance(entry);
+
+ for (NotifCollectionListener listener : mNotifCollectionListeners) {
+ listener.onEntryInit(entry);
+ }
+ }
+
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onEntryBind(entry, notification);
}
- mAllNotifications.add(entry);
-
- mLeakDetector.trackInstance(entry);
-
- for (NotifCollectionListener listener : mNotifCollectionListeners) {
- listener.onEntryInit(entry);
- }
// Construct the expanded view.
if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
@@ -581,7 +592,6 @@
mInflationCallback);
}
- abortExistingInflation(key, "addNotification");
mPendingNotifications.put(key, entry);
mLogger.logNotifAdded(entry.getKey());
for (NotificationEntryListener listener : mNotificationEntryListeners) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
index 0caf0dc..97c1523 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
@@ -19,6 +19,8 @@
import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED;
import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.NOT_DISMISSED;
+import com.android.systemui.statusbar.NotificationInteractionTracker;
+
import java.util.Arrays;
import java.util.List;
@@ -35,6 +37,7 @@
*/
public static String dumpTree(
List<ListEntry> entries,
+ NotificationInteractionTracker interactionTracker,
boolean includeRecordKeeping,
String indent) {
StringBuilder sb = new StringBuilder();
@@ -46,7 +49,8 @@
indent,
sb,
true,
- includeRecordKeeping);
+ includeRecordKeeping,
+ interactionTracker.hasUserInteractedWith(entry.getKey()));
if (entry instanceof GroupEntry) {
GroupEntry ge = (GroupEntry) entry;
List<NotificationEntry> children = ge.getChildren();
@@ -56,7 +60,8 @@
childEntryIndent,
sb,
true,
- includeRecordKeeping);
+ includeRecordKeeping,
+ interactionTracker.hasUserInteractedWith(entry.getKey()));
}
}
}
@@ -80,7 +85,8 @@
indent,
sb,
false,
- includeRecordKeeping);
+ includeRecordKeeping,
+ false);
}
return sb.toString();
}
@@ -91,7 +97,9 @@
String indent,
StringBuilder sb,
boolean includeParent,
- boolean includeRecordKeeping) {
+ boolean includeRecordKeeping,
+ boolean hasBeenInteractedWith
+ ) {
sb.append(indent)
.append("[").append(index).append("] ")
.append(entry.getKey());
@@ -157,6 +165,8 @@
.append(" ");
}
+ rksb.append("interacted=").append(hasBeenInteractedWith ? "yes" : "no").append(" ");
+
String rkString = rksb.toString();
if (!rkString.isEmpty()) {
sb.append("\n\t")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index a3621b6..d7365e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -61,6 +61,7 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.Dumpable;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.dump.LogBufferEulogizer;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.notification.collection.coalescer.CoalescedEvent;
import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
@@ -126,6 +127,7 @@
private final IStatusBarService mStatusBarService;
private final FeatureFlags mFeatureFlags;
private final NotifCollectionLogger mLogger;
+ private final LogBufferEulogizer mEulogizer;
private final Map<String, NotificationEntry> mNotificationSet = new ArrayMap<>();
private final Collection<NotificationEntry> mReadOnlyNotificationSet =
@@ -146,10 +148,12 @@
IStatusBarService statusBarService,
DumpManager dumpManager,
FeatureFlags featureFlags,
- NotifCollectionLogger logger) {
+ NotifCollectionLogger logger,
+ LogBufferEulogizer logBufferEulogizer) {
Assert.isMainThread();
mStatusBarService = statusBarService;
mLogger = logger;
+ mEulogizer = logBufferEulogizer;
dumpManager.registerDumpable(TAG, this);
mFeatureFlags = featureFlags;
}
@@ -223,7 +227,8 @@
requireNonNull(stats);
if (entry != mNotificationSet.get(entry.getKey())) {
- throw new IllegalStateException("Invalid entry: " + entry.getKey());
+ throw mEulogizer.record(
+ new IllegalStateException("Invalid entry: " + entry.getKey()));
}
if (entry.getDismissState() == DISMISSED) {
@@ -367,8 +372,11 @@
final NotificationEntry entry = mNotificationSet.get(sbn.getKey());
if (entry == null) {
- throw new IllegalStateException("No notification to remove with key " + sbn.getKey());
+ throw mEulogizer.record(
+ new IllegalStateException("No notification to remove with key "
+ + sbn.getKey()));
}
+
entry.mCancellationReason = reason;
tryRemoveNotification(entry);
applyRanking(rankingMap);
@@ -390,11 +398,11 @@
if (entry == null) {
// A new notification!
entry = new NotificationEntry(sbn, ranking, SystemClock.uptimeMillis());
+ mEventQueue.add(new InitEntryEvent(entry));
mEventQueue.add(new BindEntryEvent(entry, sbn));
mNotificationSet.put(sbn.getKey(), entry);
mLogger.logNotifPosted(sbn.getKey());
- mEventQueue.add(new InitEntryEvent(entry));
mEventQueue.add(new EntryAddedEvent(entry));
} else {
@@ -426,12 +434,15 @@
*/
private boolean tryRemoveNotification(NotificationEntry entry) {
if (mNotificationSet.get(entry.getKey()) != entry) {
- throw new IllegalStateException("No notification to remove with key " + entry.getKey());
+ throw mEulogizer.record(
+ new IllegalStateException("No notification to remove with key "
+ + entry.getKey()));
}
if (!isCanceled(entry)) {
- throw new IllegalStateException("Cannot remove notification " + entry.getKey()
- + ": has not been marked for removal");
+ throw mEulogizer.record(
+ new IllegalStateException("Cannot remove notification " + entry.getKey()
+ + ": has not been marked for removal"));
}
if (isDismissedByUser(entry)) {
@@ -501,11 +512,11 @@
checkForReentrantCall();
if (!entry.mLifetimeExtenders.remove(extender)) {
- throw new IllegalStateException(
+ throw mEulogizer.record(new IllegalStateException(
String.format(
"Cannot end lifetime extension for extender \"%s\" (%s)",
extender.getName(),
- extender));
+ extender)));
}
mLogger.logLifetimeExtensionEnded(
@@ -581,11 +592,11 @@
checkForReentrantCall();
if (!entry.mDismissInterceptors.remove(interceptor)) {
- throw new IllegalStateException(
+ throw mEulogizer.record(new IllegalStateException(
String.format(
"Cannot end dismiss interceptor for interceptor \"%s\" (%s)",
interceptor.getName(),
- interceptor));
+ interceptor)));
}
if (!isDismissIntercepted(entry)) {
@@ -608,7 +619,7 @@
private void checkForReentrantCall() {
if (mAmDispatchingToOtherCode) {
- throw new IllegalStateException("Reentrant call detected");
+ throw mEulogizer.record(new IllegalStateException("Reentrant call detected"));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
index dc0b802..bab2686 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
@@ -74,11 +74,14 @@
val aRank = a.ranking.rank
val bRank = b.ranking.rank
+ val aIsFsn = a.isColorizedForegroundService()
+ val bIsFsn = b.isColorizedForegroundService()
+
val aPersonType = a.getPeopleNotificationType()
val bPersonType = b.getPeopleNotificationType()
- val aMedia = isImportantMedia(a)
- val bMedia = isImportantMedia(b)
+ val aMedia = a.isImportantMedia()
+ val bMedia = b.isImportantMedia()
val aSystemMax = a.isSystemMax()
val bSystemMax = b.isSystemMax()
@@ -92,7 +95,7 @@
aHeadsUp != bHeadsUp -> if (aHeadsUp) -1 else 1
// Provide consistent ranking with headsUpManager
aHeadsUp -> headsUpManager.compare(a, b)
-
+ aIsFsn != bIsFsn -> if (aIsFsn) -1 else 1
usePeopleFiltering && aPersonType != bPersonType ->
peopleNotificationIdentifier.compareTo(aPersonType, bPersonType)
// Upsort current media notification.
@@ -106,11 +109,6 @@
}
}
- private fun isImportantMedia(entry: NotificationEntry): Boolean {
- val importance = entry.ranking.importance
- return entry.key == mediaManager.mediaNotificationKey && importance > IMPORTANCE_MIN
- }
-
fun updateRanking(
newRankingMap: RankingMap?,
entries: Collection<NotificationEntry>,
@@ -153,15 +151,12 @@
@PriorityBucket
private fun getBucketForEntry(entry: NotificationEntry): Int {
val isHeadsUp = entry.isRowHeadsUp
- val isMedia = isImportantMedia(entry)
+ val isMedia = entry.isImportantMedia()
val isSystemMax = entry.isSystemMax()
return when {
- entry.sbn.notification.isForegroundService && entry.sbn.notification.isColorized ->
- BUCKET_FOREGROUND_SERVICE
- usePeopleFiltering && entry.getPeopleNotificationType() != TYPE_NON_PERSON ->
- BUCKET_PEOPLE
- isHeadsUp || isMedia || isSystemMax || entry.isHighPriority() ->
- BUCKET_ALERTING
+ entry.isColorizedForegroundService() -> BUCKET_FOREGROUND_SERVICE
+ usePeopleFiltering && entry.isConversation() -> BUCKET_PEOPLE
+ isHeadsUp || isMedia || isSystemMax || entry.isHighPriority() -> BUCKET_ALERTING
else -> BUCKET_SILENT
}
}
@@ -190,6 +185,11 @@
}
}
+ private fun NotificationEntry.isImportantMedia() =
+ key == mediaManager.mediaNotificationKey && ranking.importance > IMPORTANCE_MIN
+
+ private fun NotificationEntry.isConversation() = getPeopleNotificationType() != TYPE_NON_PERSON
+
private fun NotificationEntry.getPeopleNotificationType() =
peopleNotificationIdentifier.getPeopleNotificationType(sbn, ranking)
@@ -198,10 +198,12 @@
}
// Convenience functions
-private fun NotificationEntry.isSystemMax(): Boolean {
- return importance >= IMPORTANCE_HIGH && sbn.isSystemNotification()
-}
+private fun NotificationEntry.isSystemMax() =
+ importance >= IMPORTANCE_HIGH && sbn.isSystemNotification()
-private fun StatusBarNotification.isSystemNotification(): Boolean {
- return "android" == packageName || "com.android.systemui" == packageName
+private fun StatusBarNotification.isSystemNotification() =
+ "android" == packageName || "com.android.systemui" == packageName
+
+private fun NotificationEntry.isColorizedForegroundService() = sbn.notification.run {
+ isForegroundService && isColorized && importance > IMPORTANCE_MIN
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 4cec383..9d81d35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -36,6 +36,7 @@
import com.android.systemui.Dumpable;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.NotificationInteractionTracker;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
@@ -73,6 +74,7 @@
public class ShadeListBuilder implements Dumpable {
private final SystemClock mSystemClock;
private final ShadeListBuilderLogger mLogger;
+ private final NotificationInteractionTracker mInteractionTracker;
private List<ListEntry> mNotifList = new ArrayList<>();
private List<ListEntry> mNewNotifList = new ArrayList<>();
@@ -105,10 +107,12 @@
public ShadeListBuilder(
SystemClock systemClock,
ShadeListBuilderLogger logger,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ NotificationInteractionTracker interactionTracker) {
Assert.isMainThread();
mSystemClock = systemClock;
mLogger = logger;
+ mInteractionTracker = interactionTracker;
dumpManager.registerDumpable(TAG, this);
}
@@ -821,6 +825,7 @@
pw.println(ListDumper.dumpTree(
getShadeList(),
+ mInteractionTracker,
true,
"\t\t"));
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
index 3fab6f7..75d772e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
@@ -77,7 +77,7 @@
* the entry during this call. Instead, use {@link #onEntryRemoved} which will be called before
* deletion.
*/
- default void onEntryCleanUp(NotificationEntry entry) {
+ default void onEntryCleanUp(@NonNull NotificationEntry entry) {
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
index 3ee2673..90d30dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
@@ -154,12 +154,14 @@
* the real work once rather than repeatedly start and cancel it.
*/
private void requestPipelineRun(NotificationEntry entry) {
+ mLogger.logRequestPipelineRun(entry.getKey());
+
final BindEntry bindEntry = getBindEntry(entry);
if (bindEntry.row == null) {
// Row is not managed yet but may be soon. Stop for now.
+ mLogger.logRequestPipelineRowNotSet(entry.getKey());
return;
}
- mLogger.logRequestPipelineRun(entry.getKey());
// Abort any existing pipeline run
mStage.abortStage(entry, bindEntry.row);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
index 1997304..f26598d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
@@ -18,6 +18,7 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogLevel.WARNING
import com.android.systemui.log.dagger.NotificationLog
import javax.inject.Inject
@@ -48,6 +49,14 @@
})
}
+ fun logRequestPipelineRowNotSet(notifKey: String) {
+ buffer.log(TAG, WARNING, {
+ str1 = notifKey
+ }, {
+ "Row is not set so pipeline will not run. notif = $str1"
+ })
+ }
+
fun logStartPipeline(notifKey: String) {
buffer.log(TAG, INFO, {
str1 = notifKey
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index e9849ec..9925909 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -64,6 +64,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
/**
* A frame layout containing the actual payload of the notification, including the contracted,
@@ -518,9 +519,12 @@
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
updateVisibility();
- if (visibility != VISIBLE) {
+ if (visibility != VISIBLE && !mOnContentViewInactiveListeners.isEmpty()) {
// View is no longer visible so all content views are inactive.
- for (Runnable r : mOnContentViewInactiveListeners.values()) {
+ // Clone list as runnables may modify the list of listeners
+ ArrayList<Runnable> listeners = new ArrayList<>(
+ mOnContentViewInactiveListeners.values());
+ for (Runnable r : listeners) {
r.run();
}
mOnContentViewInactiveListeners.clear();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
index 66c07bd..1dc828b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
@@ -192,9 +192,9 @@
private void bindName() {
TextView name = findViewById(R.id.name);
Bundle extras = mSbn.getNotification().extras;
- String nameString = extras.getString(Notification.EXTRA_CONVERSATION_TITLE);
+ CharSequence nameString = extras.getCharSequence(Notification.EXTRA_CONVERSATION_TITLE, "");
if (TextUtils.isEmpty(nameString)) {
- nameString = extras.getString(Notification.EXTRA_TITLE);
+ nameString = extras.getCharSequence(Notification.EXTRA_TITLE, "");
}
name.setText(nameString);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 3db4b6f..e33cc60 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -5983,6 +5983,7 @@
// ANIMATION_TYPE_ADD
new AnimationFilter()
+ .animateAlpha()
.animateHeight()
.animateTopInset()
.animateY()
@@ -5991,6 +5992,7 @@
// ANIMATION_TYPE_REMOVE
new AnimationFilter()
+ .animateAlpha()
.animateHeight()
.animateTopInset()
.animateY()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 35c33ae..999e636 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -38,6 +38,7 @@
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.hardware.biometrics.BiometricSourceType;
+import android.os.Bundle;
import android.os.PowerManager;
import android.os.SystemClock;
import android.util.Log;
@@ -51,6 +52,7 @@
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.TextView;
@@ -244,6 +246,7 @@
private final KeyguardUpdateMonitor mUpdateMonitor;
private final ConversationNotificationManager mConversationNotificationManager;
private final MediaHierarchyManager mMediaHierarchyManager;
+ private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private KeyguardAffordanceHelper mAffordanceHelper;
private KeyguardUserSwitcher mKeyguardUserSwitcher;
@@ -439,6 +442,26 @@
private int mOldLayoutDirection;
+ private View.AccessibilityDelegate mAccessibilityDelegate = new View.AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
+ info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP);
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (action == AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId()
+ || action
+ == AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP.getId()) {
+ mStatusBarKeyguardViewManager.showBouncer(true);
+ return true;
+ }
+ return super.performAccessibilityAction(host, action, args);
+ }
+ };
+
@Inject
public NotificationPanelViewController(NotificationPanelView view,
InjectionInflationController injectionInflationController,
@@ -459,7 +482,8 @@
FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
ConversationNotificationManager conversationNotificationManager,
- MediaHierarchyManager mediaHierarchyManager) {
+ MediaHierarchyManager mediaHierarchyManager,
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
super(view, falsingManager, dozeLog, keyguardStateController,
(SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
latencyTracker, flingAnimationUtilsBuilder, statusBarTouchableRegionManager);
@@ -470,6 +494,7 @@
mConfigurationController = configurationController;
mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
mMediaHierarchyManager = mediaHierarchyManager;
+ mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mView.setWillNotDraw(!DEBUG);
mInjectionInflationController = injectionInflationController;
mFalsingManager = falsingManager;
@@ -583,6 +608,8 @@
mOldLayoutDirection = layoutDirection;
}
});
+
+ mView.setAccessibilityDelegate(mAccessibilityDelegate);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index a065b74..2a4475b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -46,6 +46,8 @@
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.media.MediaData;
+import com.android.systemui.media.MediaDataManager;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.RotationLockTile;
import com.android.systemui.screenrecord.RecordingController;
@@ -80,14 +82,14 @@
*/
public class PhoneStatusBarPolicy
implements BluetoothController.Callback,
- CommandQueue.Callbacks,
- RotationLockControllerCallback,
- Listener,
- ZenModeController.Callback,
- DeviceProvisionedListener,
- KeyguardStateController.Callback,
- LocationController.LocationChangeCallback,
- RecordingController.RecordingStateChangeCallback {
+ CommandQueue.Callbacks,
+ RotationLockControllerCallback,
+ Listener,
+ ZenModeController.Callback,
+ DeviceProvisionedListener,
+ KeyguardStateController.Callback,
+ LocationController.LocationChangeCallback,
+ RecordingController.RecordingStateChangeCallback, MediaDataManager.Listener {
private static final String TAG = "PhoneStatusBarPolicy";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -108,6 +110,7 @@
private final String mSlotLocation;
private final String mSlotSensorsOff;
private final String mSlotScreenRecord;
+ private final String mSlotMedia;
private final int mDisplayId;
private final SharedPreferences mSharedPreferences;
private final DateFormatUtil mDateFormatUtil;
@@ -135,6 +138,7 @@
private final SensorPrivacyController mSensorPrivacyController;
private final RecordingController mRecordingController;
private final RingerModeTracker mRingerModeTracker;
+ private final MediaDataManager mMediaDataManager;
private boolean mZenVisible;
private boolean mVolumeVisible;
@@ -159,6 +163,7 @@
SensorPrivacyController sensorPrivacyController, IActivityManager iActivityManager,
AlarmManager alarmManager, UserManager userManager,
RecordingController recordingController,
+ MediaDataManager mediaDataManager,
@Nullable TelecomManager telecomManager, @DisplayId int displayId,
@Main SharedPreferences sharedPreferences, DateFormatUtil dateFormatUtil,
RingerModeTracker ringerModeTracker) {
@@ -185,6 +190,7 @@
mUiBgExecutor = uiBgExecutor;
mTelecomManager = telecomManager;
mRingerModeTracker = ringerModeTracker;
+ mMediaDataManager = mediaDataManager;
mSlotCast = resources.getString(com.android.internal.R.string.status_bar_cast);
mSlotHotspot = resources.getString(com.android.internal.R.string.status_bar_hotspot);
@@ -202,6 +208,7 @@
mSlotSensorsOff = resources.getString(com.android.internal.R.string.status_bar_sensors_off);
mSlotScreenRecord = resources.getString(
com.android.internal.R.string.status_bar_screen_record);
+ mSlotMedia = resources.getString(com.android.internal.R.string.status_bar_media);
mDisplayId = displayId;
mSharedPreferences = sharedPreferences;
@@ -280,6 +287,11 @@
mIconController.setIconVisibility(mSlotSensorsOff,
mSensorPrivacyController.isSensorPrivacyEnabled());
+ // play/pause icon when media is active
+ mIconController.setIcon(mSlotMedia, R.drawable.stat_sys_media,
+ mResources.getString(R.string.accessibility_media_active));
+ mIconController.setIconVisibility(mSlotMedia, mMediaDataManager.hasActiveMedia());
+
// screen record
mIconController.setIcon(mSlotScreenRecord, R.drawable.stat_sys_screen_record, null);
mIconController.setIconVisibility(mSlotScreenRecord, false);
@@ -296,6 +308,7 @@
mSensorPrivacyController.addCallback(mSensorPrivacyListener);
mLocationController.addCallback(this);
mRecordingController.addCallback(this);
+ mMediaDataManager.addListener(this);
mCommandQueue.addCallback(this);
}
@@ -700,4 +713,18 @@
if (DEBUG) Log.d(TAG, "screenrecord: hiding icon");
mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, false));
}
+
+ @Override
+ public void onMediaDataLoaded(String key, MediaData data) {
+ updateMediaIcon();
+ }
+
+ @Override
+ public void onMediaDataRemoved(String key) {
+ updateMediaIcon();
+ }
+
+ private void updateMediaIcon() {
+ mIconController.setIconVisibility(mSlotMedia, mMediaDataManager.hasActiveMedia());
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 7924348..64e5f0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -45,7 +45,6 @@
import android.view.View;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.ActivityIntentHelper;
@@ -59,6 +58,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.NotificationClickNotifier;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -103,7 +103,7 @@
private final NotifCollection mNotifCollection;
private final HeadsUpManagerPhone mHeadsUpManager;
private final ActivityStarter mActivityStarter;
- private final IStatusBarService mBarService;
+ private final NotificationClickNotifier mClickNotifier;
private final StatusBarStateController mStatusBarStateController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final KeyguardManager mKeyguardManager;
@@ -142,7 +142,7 @@
NotifCollection notifCollection,
HeadsUpManagerPhone headsUpManager,
ActivityStarter activityStarter,
- IStatusBarService statusBarService,
+ NotificationClickNotifier clickNotifier,
StatusBarStateController statusBarStateController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
KeyguardManager keyguardManager,
@@ -177,7 +177,7 @@
mNotifCollection = notifCollection;
mHeadsUpManager = headsUpManager;
mActivityStarter = activityStarter;
- mBarService = statusBarService;
+ mClickNotifier = clickNotifier;
mStatusBarStateController = statusBarStateController;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mKeyguardManager = keyguardManager;
@@ -379,11 +379,7 @@
NotificationLogger.getNotificationLocation(entry);
final NotificationVisibility nv = NotificationVisibility.obtain(notificationKey,
rank, count, true, location);
- try {
- mBarService.onNotificationClick(notificationKey, nv);
- } catch (RemoteException ex) {
- // system process is dead if we're here.
- }
+ mClickNotifier.onNotificationClick(notificationKey, nv);
if (!isBubble) {
if (parentToCancelFinal != null) {
@@ -651,7 +647,7 @@
private final NotifCollection mNotifCollection;
private final HeadsUpManagerPhone mHeadsUpManager;
private final ActivityStarter mActivityStarter;
- private final IStatusBarService mStatusBarService;
+ private final NotificationClickNotifier mClickNotifier;
private final StatusBarStateController mStatusBarStateController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final KeyguardManager mKeyguardManager;
@@ -689,7 +685,7 @@
NotifCollection notifCollection,
HeadsUpManagerPhone headsUpManager,
ActivityStarter activityStarter,
- IStatusBarService statusBarService,
+ NotificationClickNotifier clickNotifier,
StatusBarStateController statusBarStateController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
KeyguardManager keyguardManager,
@@ -720,7 +716,7 @@
mNotifCollection = notifCollection;
mHeadsUpManager = headsUpManager;
mActivityStarter = activityStarter;
- mStatusBarService = statusBarService;
+ mClickNotifier = clickNotifier;
mStatusBarStateController = statusBarStateController;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mKeyguardManager = keyguardManager;
@@ -777,7 +773,7 @@
mNotifCollection,
mHeadsUpManager,
mActivityStarter,
- mStatusBarService,
+ mClickNotifier,
mStatusBarStateController,
mStatusBarKeyguardViewManager,
mKeyguardManager,
diff --git a/packages/SystemUI/src/com/android/systemui/util/io/Files.java b/packages/SystemUI/src/com/android/systemui/util/io/Files.java
new file mode 100644
index 0000000..7d633a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/io/Files.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.io;
+
+import androidx.annotation.NonNull;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.LinkOption;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.stream.Stream;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Wrapper around {@link java.nio.file.Files} that can be mocked in tests.
+ */
+@Singleton
+public class Files {
+ @Inject
+ public Files() { }
+
+ /** See {@link java.nio.file.Files#newBufferedWriter} */
+ public BufferedWriter newBufferedWriter(Path path, OpenOption... options) throws IOException {
+ return java.nio.file.Files.newBufferedWriter(path, StandardCharsets.UTF_8, options);
+ }
+
+ /** See {@link java.nio.file.Files#lines} */
+ public Stream<String> lines(Path path) throws IOException {
+ return java.nio.file.Files.lines(path);
+ }
+
+ /** See {@link java.nio.file.Files#readAttributes} */
+ public <A extends BasicFileAttributes> A readAttributes(
+ @NonNull Path path,
+ @NonNull Class<A> type,
+ @NonNull LinkOption... options) throws IOException {
+ return java.nio.file.Files.readAttributes(path, type, options);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
index 378dde2..708b5a7 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
@@ -21,16 +21,17 @@
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
-import android.os.Handler;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.util.concurrency.DelayableExecutor;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -49,8 +50,9 @@
private String mTag = null;
@VisibleForTesting ProximityEvent mLastEvent;
private int mSensorDelay = SensorManager.SENSOR_DELAY_NORMAL;
- private boolean mPaused;
+ @VisibleForTesting protected boolean mPaused;
private boolean mRegistered;
+ private final AtomicBoolean mAlerting = new AtomicBoolean();
private SensorEventListener mSensorEventListener = new SensorEventListener() {
@Override
@@ -217,8 +219,12 @@
/** Update all listeners with the last value this class received from the sensor. */
public void alertListeners() {
+ if (mAlerting.getAndSet(true)) {
+ return;
+ }
mListeners.forEach(proximitySensorListener ->
proximitySensorListener.onSensorEvent(mLastEvent));
+ mAlerting.set(false);
}
private void onSensorEvent(SensorEvent event) {
@@ -239,14 +245,14 @@
public static class ProximityCheck implements Runnable {
private final ProximitySensor mSensor;
- private final Handler mHandler;
+ private final DelayableExecutor mDelayableExecutor;
private List<Consumer<Boolean>> mCallbacks = new ArrayList<>();
@Inject
- public ProximityCheck(ProximitySensor sensor, Handler handler) {
+ public ProximityCheck(ProximitySensor sensor, DelayableExecutor delayableExecutor) {
mSensor = sensor;
mSensor.setTag("prox_check");
- mHandler = handler;
+ mDelayableExecutor = delayableExecutor;
mSensor.pause();
ProximitySensorListener listener = proximityEvent -> {
mCallbacks.forEach(
@@ -280,7 +286,7 @@
mCallbacks.add(callback);
if (!mSensor.isRegistered()) {
mSensor.resume();
- mHandler.postDelayed(this, timeoutMs);
+ mDelayableExecutor.executeDelayed(this, timeoutMs);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/time/SystemClock.java b/packages/SystemUI/src/com/android/systemui/util/time/SystemClock.java
index 6fef59f..6f32cc1 100644
--- a/packages/SystemUI/src/com/android/systemui/util/time/SystemClock.java
+++ b/packages/SystemUI/src/com/android/systemui/util/time/SystemClock.java
@@ -37,4 +37,7 @@
/** @see android.os.SystemClock#currentThreadTimeMillis() */
long currentThreadTimeMillis();
+
+ /** @see System#currentTimeMillis() */
+ long currentTimeMillis();
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/time/SystemClockImpl.java b/packages/SystemUI/src/com/android/systemui/util/time/SystemClockImpl.java
index f0c7014..4e508cfa 100644
--- a/packages/SystemUI/src/com/android/systemui/util/time/SystemClockImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/time/SystemClockImpl.java
@@ -42,4 +42,9 @@
public long currentThreadTimeMillis() {
return android.os.SystemClock.currentThreadTimeMillis();
}
+
+ @Override
+ public long currentTimeMillis() {
+ return System.currentTimeMillis();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayController.java
index c66f07dd..083c243 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/DisplayController.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayController.java
@@ -134,6 +134,39 @@
}
});
}
+
+ @Override
+ public void onFixedRotationStarted(int displayId, int newRotation) {
+ mHandler.post(() -> {
+ synchronized (mDisplays) {
+ if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) {
+ Slog.w(TAG, "Skipping onFixedRotationStarted on unknown"
+ + " display, displayId=" + displayId);
+ return;
+ }
+ for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
+ mDisplayChangedListeners.get(i).onFixedRotationStarted(
+ displayId, newRotation);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onFixedRotationFinished(int displayId) {
+ mHandler.post(() -> {
+ synchronized (mDisplays) {
+ if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) {
+ Slog.w(TAG, "Skipping onFixedRotationFinished on unknown"
+ + " display, displayId=" + displayId);
+ return;
+ }
+ for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
+ mDisplayChangedListeners.get(i).onFixedRotationFinished(displayId);
+ }
+ }
+ });
+ }
};
@Inject
@@ -232,5 +265,15 @@
* Called when a display is removed.
*/
default void onDisplayRemoved(int displayId) {}
+
+ /**
+ * Called when fixed rotation on a display is started.
+ */
+ default void onFixedRotationStarted(int displayId, int newRotation) {}
+
+ /**
+ * Called when fixed rotation on a display is finished.
+ */
+ default void onFixedRotationFinished(int displayId) {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
index c7e9acc..2968b92 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
@@ -178,6 +178,7 @@
ValueAnimator mAnimation = null;
int mRotation = Surface.ROTATION_0;
boolean mImeShowing = false;
+ final Rect mImeFrame = new Rect();
PerDisplay(int displayId, int initialRotation) {
mDisplayId = displayId;
@@ -254,8 +255,8 @@
}
}
- private int imeTop(InsetsSource imeSource, float surfaceOffset) {
- return imeSource.getFrame().top + (int) surfaceOffset;
+ private int imeTop(float surfaceOffset) {
+ return mImeFrame.top + (int) surfaceOffset;
}
private void startAnimation(final boolean show, final boolean forceRestart) {
@@ -263,6 +264,11 @@
if (imeSource == null || mImeSourceControl == null) {
return;
}
+ // Set frame, but only if the new frame isn't empty -- this maintains continuity
+ final Rect newFrame = imeSource.getFrame();
+ if (newFrame.height() != 0) {
+ mImeFrame.set(newFrame);
+ }
mHandler.post(() -> {
if (DEBUG) {
Slog.d(TAG, "Run startAnim show:" + show + " was:"
@@ -284,7 +290,7 @@
}
final float defaultY = mImeSourceControl.getSurfacePosition().y;
final float x = mImeSourceControl.getSurfacePosition().x;
- final float hiddenY = defaultY + imeSource.getFrame().height();
+ final float hiddenY = defaultY + mImeFrame.height();
final float shownY = defaultY;
final float startY = show ? hiddenY : shownY;
final float endY = show ? shownY : hiddenY;
@@ -306,7 +312,7 @@
SurfaceControl.Transaction t = mTransactionPool.acquire();
float value = (float) animation.getAnimatedValue();
t.setPosition(mImeSourceControl.getLeash(), x, value);
- dispatchPositionChanged(mDisplayId, imeTop(imeSource, value), t);
+ dispatchPositionChanged(mDisplayId, imeTop(value), t);
t.apply();
mTransactionPool.release(t);
});
@@ -319,11 +325,11 @@
t.setPosition(mImeSourceControl.getLeash(), x, startY);
if (DEBUG) {
Slog.d(TAG, "onAnimationStart d:" + mDisplayId + " top:"
- + imeTop(imeSource, hiddenY) + "->" + imeTop(imeSource, shownY)
+ + imeTop(hiddenY) + "->" + imeTop(shownY)
+ " showing:" + (mAnimationDirection == DIRECTION_SHOW));
}
- dispatchStartPositioning(mDisplayId, imeTop(imeSource, hiddenY),
- imeTop(imeSource, shownY), mAnimationDirection == DIRECTION_SHOW,
+ dispatchStartPositioning(mDisplayId, imeTop(hiddenY),
+ imeTop(shownY), mAnimationDirection == DIRECTION_SHOW,
t);
if (mAnimationDirection == DIRECTION_SHOW) {
t.show(mImeSourceControl.getLeash());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
index 45f9437..805254c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
@@ -85,7 +85,8 @@
mFsc = new ForegroundServiceController(
mEntryManager, mAppOpsController, mMainHandler);
mListener = new ForegroundServiceNotificationListener(
- mContext, mFsc, mEntryManager, mNotifPipeline, mClock);
+ mContext, mFsc, mEntryManager, mNotifPipeline,
+ mock(ForegroundServiceLifetimeExtender.class), mClock);
ArgumentCaptor<NotificationEntryListener> entryListenerCaptor =
ArgumentCaptor.forClass(NotificationEntryListener.class);
verify(mEntryManager).addNotificationEntryListener(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java
index bca8dee..050b553 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java
@@ -26,6 +26,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.systemui.statusbar.NotificationInteractionTracker;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.util.time.FakeSystemClock;
@@ -33,6 +34,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -42,9 +45,13 @@
private Notification mNotif;
private final FakeSystemClock mClock = new FakeSystemClock();
+ @Mock
+ private NotificationInteractionTracker mInteractionTracker;
+
@Before
public void setup() {
- mExtender = new ForegroundServiceLifetimeExtender(mClock);
+ MockitoAnnotations.initMocks(this);
+ mExtender = new ForegroundServiceLifetimeExtender(mInteractionTracker, mClock);
mNotif = new Notification.Builder(mContext, "")
.setSmallIcon(R.drawable.ic_person)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index 50b7af2..e0049d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -19,6 +19,7 @@
import static junit.framework.TestCase.assertFalse;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -29,9 +30,8 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static java.lang.Thread.sleep;
-
import android.app.AppOpsManager;
+import android.os.Looper;
import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -229,12 +229,7 @@
@Test
public void testActiveOpNotRemovedAfterNoted() throws InterruptedException {
// Replaces the timeout delay with 5 ms
- AppOpsControllerImpl.H testHandler = mController.new H(mTestableLooper.getLooper()) {
- @Override
- public void scheduleRemoval(AppOpItem item, long timeToRemoval) {
- super.scheduleRemoval(item, 5L);
- }
- };
+ TestHandler testHandler = new TestHandler(mTestableLooper.getLooper());
mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
mController.setBGHandler(testHandler);
@@ -245,6 +240,10 @@
mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
AppOpsManager.MODE_ALLOWED);
+ // Check that we "scheduled" the removal. Don't actually schedule until we are ready to
+ // process messages at a later time.
+ assertNotNull(testHandler.mDelayScheduled);
+
mTestableLooper.processAllMessages();
List<AppOpItem> list = mController.getActiveAppOps();
verify(mCallback).onActiveStateChanged(
@@ -253,8 +252,8 @@
// Duplicates are not removed between active and noted
assertEquals(2, list.size());
- sleep(10L);
-
+ // Now is later, so we can schedule delayed messages.
+ testHandler.scheduleDelayed();
mTestableLooper.processAllMessages();
verify(mCallback, never()).onActiveStateChanged(
@@ -321,4 +320,24 @@
verify(mCallback).onActiveStateChanged(
AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
}
+
+ private class TestHandler extends AppOpsControllerImpl.H {
+ TestHandler(Looper looper) {
+ mController.super(looper);
+ }
+
+ Runnable mDelayScheduled;
+
+ void scheduleDelayed() {
+ if (mDelayScheduled != null) {
+ mDelayScheduled.run();
+ mDelayScheduled = null;
+ }
+ }
+
+ @Override
+ public void scheduleRemoval(AppOpItem item, long timeToRemoval) {
+ mDelayScheduled = () -> super.scheduleRemoval(item, 0L);
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
index ec6d3e9..6a14863 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -49,11 +49,13 @@
private int mOrientation = Configuration.ORIENTATION_PORTRAIT;
private float mLauncherGridDiff = 30f;
+ private Runnable mOnBubbleAnimatedOutAction = Mockito.mock(Runnable.class);
+
@Spy
private ExpandedAnimationController mExpandedController =
new ExpandedAnimationController(
new Point(mDisplayWidth, mDisplayHeight) /* displaySize */,
- mExpandedViewPadding, mOrientation);
+ mExpandedViewPadding, mOrientation, mOnBubbleAnimatedOutAction);
private int mStackOffset;
private float mBubblePaddingTop;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
index b1ac022..cc62a2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
@@ -40,6 +40,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.Mockito;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -66,7 +67,7 @@
public int getAsInt() {
return mLayout.getChildCount();
}
- }));
+ }, Mockito.mock(Runnable.class)));
mLayout.setActiveController(mStackController);
addOneMoreThanBubbleLimitBubbles();
mStackOffset = mLayout.getResources().getDimensionPixelSize(R.dimen.bubble_stack_offset);
@@ -303,8 +304,9 @@
private class TestableStackController extends StackAnimationController {
TestableStackController(
FloatingContentCoordinator floatingContentCoordinator,
- IntSupplier bubbleCountSupplier) {
- super(floatingContentCoordinator, bubbleCountSupplier);
+ IntSupplier bubbleCountSupplier,
+ Runnable onBubbleAnimatedOutAction) {
+ super(floatingContentCoordinator, bubbleCountSupplier, onBubbleAnimatedOutAction);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index 317500c..a567536 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -18,6 +18,7 @@
import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_LOCK_SCREEN;
+import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -46,6 +47,7 @@
import com.android.systemui.plugins.SensorManagerPlugin;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.util.sensors.AsyncSensorManager;
+import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.wakelock.WakeLock;
import org.junit.Before;
@@ -82,7 +84,7 @@
@Mock
private DozeLog mDozeLog;
@Mock
- private Sensor mProximitySensor;
+ private ProximitySensor mProximitySensor;
private SensorManagerPlugin.SensorEventListener mWakeLockScreenListener;
private TestableLooper mTestableLooper;
private DozeSensors mDozeSensors;
@@ -93,7 +95,6 @@
mTestableLooper = TestableLooper.get(this);
when(mAmbientDisplayConfiguration.getWakeLockScreenDebounce()).thenReturn(5000L);
when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
- when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(mProximitySensor);
doAnswer(invocation -> {
((Runnable) invocation.getArgument(0)).run();
return null;
@@ -103,10 +104,9 @@
@Test
public void testRegisterProx() {
- // We should not register with the sensor manager initially.
- verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt());
+ assertFalse(mProximitySensor.isRegistered());
mDozeSensors.setProxListening(true);
- verify(mSensorManager).registerListener(any(), any(Sensor.class), anyInt());
+ verify(mProximitySensor).resume();
}
@Test
@@ -169,7 +169,8 @@
TestableDozeSensors() {
super(getContext(), mAlarmManager, mSensorManager, mDozeParameters,
- mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog);
+ mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
+ mProximitySensor);
for (TriggerSensor sensor : mSensors) {
if (sensor instanceof PluginSensor
&& ((PluginSensor) sensor).mPluginSensor.getType()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index debc9d6..73aaeff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -31,7 +31,6 @@
import android.hardware.Sensor;
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Handler;
-import android.os.Looper;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -42,10 +41,12 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dock.DockManager;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.sensors.AsyncSensorManager;
import com.android.systemui.util.sensors.FakeProximitySensor;
import com.android.systemui.util.sensors.FakeSensorManager;
import com.android.systemui.util.sensors.ProximitySensor;
+import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.wakelock.WakeLock;
import com.android.systemui.util.wakelock.WakeLockFake;
@@ -75,6 +76,7 @@
private FakeSensorManager mSensors;
private Sensor mTapSensor;
private FakeProximitySensor mProximitySensor;
+ private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
@Before
public void setUp() throws Exception {
@@ -89,7 +91,7 @@
mProximitySensor = new FakeProximitySensor(getContext().getResources(), asyncSensorManager);
mTriggers = new DozeTriggers(mContext, mMachine, mHost, mAlarmManager, config, parameters,
- asyncSensorManager, Handler.createAsync(Looper.myLooper()), wakeLock, true,
+ asyncSensorManager, mFakeExecutor, wakeLock, true,
mDockManager, mProximitySensor, mock(DozeLog.class), mBroadcastDispatcher);
waitForSensorManager();
}
@@ -111,9 +113,8 @@
verify(mMachine, never()).requestState(any());
verify(mMachine, never()).requestPulse(anyInt());
- captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */);
- waitForSensorManager();
mProximitySensor.setLastEvent(new ProximitySensor.ProximityEvent(false, 2));
+ captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */);
mProximitySensor.alertListeners();
verify(mMachine).requestPulse(anyInt());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
similarity index 90%
rename from packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
index 8d530ec..9e67eda 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
@@ -32,9 +32,12 @@
import java.io.PrintWriter
@SmallTest
-class DumpManagerTest : SysuiTestCase() {
+class DumpHandlerTest : SysuiTestCase() {
- private lateinit var dumpManager: DumpManager
+ private lateinit var dumpHandler: DumpHandler
+
+ @Mock
+ private lateinit var logBufferEulogizer: LogBufferEulogizer
@Mock
private lateinit var fd: FileDescriptor
@@ -53,11 +56,13 @@
@Mock
private lateinit var buffer2: LogBuffer
+ private val dumpManager = DumpManager()
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- dumpManager = DumpManager(mContext)
+ dumpHandler = DumpHandler(mContext, dumpManager, logBufferEulogizer)
}
@Test
@@ -71,7 +76,7 @@
// WHEN some of them are dumped explicitly
val args = arrayOf("dumpable1", "dumpable3", "buffer2")
- dumpManager.dump(fd, pw, args)
+ dumpHandler.dump(fd, pw, args)
// THEN only the requested ones have their dump() method called
verify(dumpable1).dump(fd, pw, args)
@@ -91,7 +96,7 @@
// WHEN that module is dumped
val args = arrayOf("dumpable1")
- dumpManager.dump(fd, pw, args)
+ dumpHandler.dump(fd, pw, args)
// THEN its dump() method is called
verify(dumpable1).dump(fd, pw, args)
@@ -108,7 +113,7 @@
// WHEN a critical dump is requested
val args = arrayOf("--dump-priority", "CRITICAL")
- dumpManager.dump(fd, pw, args)
+ dumpHandler.dump(fd, pw, args)
// THEN all modules are dumped (but no buffers)
verify(dumpable1).dump(fd, pw, args)
@@ -127,9 +132,9 @@
dumpManager.registerBuffer("buffer1", buffer1)
dumpManager.registerBuffer("buffer2", buffer2)
- // WHEN a critical dump is requested
+ // WHEN a normal dump is requested
val args = arrayOf("--dump-priority", "NORMAL")
- dumpManager.dump(fd, pw, args)
+ dumpHandler.dump(fd, pw, args)
// THEN all buffers are dumped (but no modules)
verify(dumpable1, never()).dump(
@@ -147,4 +152,4 @@
verify(buffer1).dump(pw, 0)
verify(buffer2).dump(pw, 0)
}
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt
new file mode 100644
index 0000000..cb38846
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dump
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.io.FakeBasicFileAttributes
+import com.android.systemui.util.io.Files
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.io.BufferedWriter
+import java.io.ByteArrayOutputStream
+import java.io.IOException
+import java.io.OutputStreamWriter
+import java.io.PrintWriter
+import java.nio.file.LinkOption
+import java.nio.file.OpenOption
+import java.nio.file.Paths
+import java.nio.file.attribute.BasicFileAttributes
+import java.util.Arrays
+
+@SmallTest
+class LogEulogizerTest : SysuiTestCase() {
+
+ lateinit var eulogizer: LogBufferEulogizer
+
+ @Mock
+ lateinit var dumpManager: DumpManager
+
+ @Mock
+ lateinit var files: Files
+
+ private val clock = FakeSystemClock()
+
+ private val path = Paths.get("/foo/bar/baz.txt")
+ private val fileAttrs = FakeBasicFileAttributes()
+ private val fileStream = ByteArrayOutputStream()
+ private val fileWriter = BufferedWriter(OutputStreamWriter(fileStream))
+
+ private val dumpStream = ByteArrayOutputStream()
+ private val dumpWriter = PrintWriter(OutputStreamWriter(dumpStream))
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ eulogizer =
+ LogBufferEulogizer(dumpManager, clock, files, path, MIN_WRITE_GAP, MAX_READ_AGE)
+
+ Mockito.`when`(files.newBufferedWriter(eq(path), any(OpenOption::class.java)))
+ .thenReturn(fileWriter)
+
+ Mockito.`when`(
+ files.readAttributes(eq(path),
+ eq(BasicFileAttributes::class.java),
+ any(LinkOption::class.java))
+ ).thenReturn(fileAttrs)
+
+ Mockito.`when`(files.lines(eq(path))).thenReturn(Arrays.stream(FAKE_LINES))
+ }
+
+ @Test
+ fun testFileIsCreated() {
+ // GIVEN that the log file doesn't already exist
+ Mockito.`when`(
+ files.readAttributes(eq(path),
+ eq(BasicFileAttributes::class.java),
+ any(LinkOption::class.java))
+ ).thenThrow(IOException("File not found"))
+
+ // WHEN .record() is called
+ val exception = RuntimeException("Something bad happened")
+ assertEquals(exception, eulogizer.record(exception))
+
+ // THEN the buffers are dumped to the file
+ verify(dumpManager).dumpBuffers(any(PrintWriter::class.java), Mockito.anyInt())
+ assertTrue(fileStream.toString().isNotEmpty())
+ }
+
+ @Test
+ fun testExistingFileIsOverwritten() {
+ // GIVEN that the log file already exists but hasn't been modified in a while
+ fileAttrs.setLastModifiedTime(clock.currentTimeMillis() - MIN_WRITE_GAP - 20)
+
+ // WHEN .record() is called
+ val exception = RuntimeException("Something bad happened")
+ assertEquals(exception, eulogizer.record(exception))
+
+ // THEN the buffers are dumped to the file
+ verify(dumpManager).dumpBuffers(any(PrintWriter::class.java), Mockito.anyInt())
+ assertTrue(fileStream.toString().isNotEmpty())
+ }
+
+ @Test
+ fun testYoungFileIsNotOverwritten() {
+ // GIVEN that the log file has been modified recently
+ fileAttrs.setLastModifiedTime(clock.currentTimeMillis() - MIN_WRITE_GAP + 7)
+
+ // WHEN .record() is called
+ val exception = RuntimeException("Something bad happened")
+ assertEquals(exception, eulogizer.record(exception))
+
+ // THEN the file isn't written to
+ verify(dumpManager, never()).dumpBuffers(any(PrintWriter::class.java), Mockito.anyInt())
+ assertTrue(fileStream.toString().isEmpty())
+ }
+
+ @Test
+ fun testRecentFileIsDumped() {
+ // GIVEN that the log file was written to "recently"
+ fileAttrs.setLastModifiedTime(clock.currentTimeMillis() - MAX_READ_AGE + 7)
+
+ // WHEN we're asked to eulogize the log
+ eulogizer.readEulogyIfPresent(dumpWriter)
+ dumpWriter.close()
+
+ // THEN the log file is written to the output stream
+ verify(files).lines(eq(path))
+ assertTrue(dumpStream.toString().isNotBlank())
+ }
+
+ @Test
+ fun testOldFileIsNotDumped() {
+ // GIVEN that the log file was written to a long time ago
+ fileAttrs.setLastModifiedTime(clock.currentTimeMillis() - MAX_READ_AGE - 7)
+
+ // WHEN we're asked to eulogize the log
+ eulogizer.readEulogyIfPresent(dumpWriter)
+ dumpWriter.close()
+
+ // THEN the log file is NOT written to the output stream
+ verify(files, never()).lines(eq(path))
+ assertTrue(dumpStream.toString().isEmpty())
+ }
+}
+
+private const val MIN_WRITE_GAP = 10L
+private const val MAX_READ_AGE = 100L
+
+private val FAKE_LINES =
+ arrayOf(
+ "First line",
+ "Second line",
+ "Third line"
+ )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
index 4a85980..8db57cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
@@ -16,9 +16,13 @@
package com.android.systemui.globalactions;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -30,6 +34,7 @@
import android.app.trust.TrustManager;
import android.content.ContentResolver;
import android.content.res.Resources;
+import android.graphics.Color;
import android.media.AudioManager;
import android.net.ConnectivityManager;
import android.os.Handler;
@@ -40,9 +45,12 @@
import android.testing.TestableLooper;
import android.util.FeatureFlagUtils;
import android.view.IWindowManager;
+import android.view.View;
+import android.widget.FrameLayout;
import androidx.test.filters.SmallTest;
+import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
@@ -56,7 +64,7 @@
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.GlobalActions;
-import com.android.systemui.statusbar.BlurUtils;
+import com.android.systemui.plugins.GlobalActionsPanelPlugin;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -98,7 +106,6 @@
@Mock private NotificationShadeDepthController mDepthController;
@Mock private SysuiColorExtractor mColorExtractor;
@Mock private IStatusBarService mStatusBarService;
- @Mock private BlurUtils mBlurUtils;
@Mock private NotificationShadeWindowController mNotificationShadeWindowController;
@Mock private ControlsUiController mControlsUiController;
@Mock private IWindowManager mWindowManager;
@@ -109,6 +116,8 @@
@Mock private RingerModeTracker mRingerModeTracker;
@Mock private RingerModeLiveData mRingerModeLiveData;
@Mock private SysUiState mSysUiState;
+ @Mock GlobalActionsPanelPlugin mWalletPlugin;
+ @Mock GlobalActionsPanelPlugin.PanelViewController mWalletController;
@Mock private Handler mHandler;
private TestableLooper mTestableLooper;
@@ -143,7 +152,6 @@
mDepthController,
mColorExtractor,
mStatusBarService,
- mBlurUtils,
mNotificationShadeWindowController,
mControlsUiController,
mWindowManager,
@@ -156,6 +164,11 @@
mHandler
);
mGlobalActionsDialog.setZeroDialogPressDelayForTesting();
+
+ ColorExtractor.GradientColors backdropColors = new ColorExtractor.GradientColors();
+ backdropColors.setMainColor(Color.BLACK);
+ when(mColorExtractor.getNeutralColors()).thenReturn(backdropColors);
+ when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
}
@Test
@@ -277,4 +290,65 @@
assertEquals(3, mGlobalActionsDialog.mItems.size());
assertEquals(0, mGlobalActionsDialog.mOverflowItems.size());
}
+
+ @Test
+ public void testShouldShowLockScreenMessage() {
+ mGlobalActionsDialog = spy(mGlobalActionsDialog);
+ mGlobalActionsDialog.mDialog = null;
+ when(mKeyguardStateController.isUnlocked()).thenReturn(false);
+ mGlobalActionsDialog.mShowLockScreenCardsAndControls = false;
+ setupDefaultActions();
+ when(mWalletPlugin.onPanelShown(any(), anyBoolean())).thenReturn(mWalletController);
+ when(mWalletController.getPanelContent()).thenReturn(new FrameLayout(mContext));
+
+ mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin);
+
+ GlobalActionsDialog.ActionsDialog dialog = mGlobalActionsDialog.mDialog;
+ assertThat(dialog).isNotNull();
+ assertThat(dialog.mLockMessageContainer.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void testShouldNotShowLockScreenMessage_whenWalletOrControlsShownOnLockScreen() {
+ mGlobalActionsDialog = spy(mGlobalActionsDialog);
+ mGlobalActionsDialog.mDialog = null;
+ when(mKeyguardStateController.isUnlocked()).thenReturn(false);
+ mGlobalActionsDialog.mShowLockScreenCardsAndControls = true;
+ setupDefaultActions();
+ when(mWalletPlugin.onPanelShown(any(), anyBoolean())).thenReturn(mWalletController);
+ when(mWalletController.getPanelContent()).thenReturn(new FrameLayout(mContext));
+
+ mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin);
+
+ GlobalActionsDialog.ActionsDialog dialog = mGlobalActionsDialog.mDialog;
+ assertThat(dialog).isNotNull();
+ assertThat(dialog.mLockMessageContainer.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void testShouldNotShowLockScreenMessage_whenControlsAndWalletBothDisabled() {
+ mGlobalActionsDialog = spy(mGlobalActionsDialog);
+ mGlobalActionsDialog.mDialog = null;
+ when(mKeyguardStateController.isUnlocked()).thenReturn(false);
+ mGlobalActionsDialog.mShowLockScreenCardsAndControls = true;
+ setupDefaultActions();
+ when(mWalletPlugin.onPanelShown(any(), anyBoolean())).thenReturn(mWalletController);
+ when(mWalletController.getPanelContent()).thenReturn(null);
+ when(mControlsUiController.getAvailable()).thenReturn(false);
+
+ mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin);
+
+ GlobalActionsDialog.ActionsDialog dialog = mGlobalActionsDialog.mDialog;
+ assertThat(dialog).isNotNull();
+ assertThat(dialog.mLockMessageContainer.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ private void setupDefaultActions() {
+ String[] actions = {
+ GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
+ GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
+ GlobalActionsDialog.GLOBAL_ACTION_KEY_SCREENSHOT,
+ };
+ doReturn(actions).when(mGlobalActionsDialog).getDefaultActions();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
new file mode 100644
index 0000000..9aee11e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import android.testing.AndroidTestingRunner
+import android.view.View.GONE
+import android.view.View.VISIBLE
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.stack.MediaHeaderView
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class KeyguardMediaControllerTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var mediaHost: MediaHost
+ @Mock
+ private lateinit var bypassController: KeyguardBypassController
+ @Mock
+ private lateinit var statusBarStateController: SysuiStatusBarStateController
+ @Mock
+ private lateinit var notificationLockscreenUserManager: NotificationLockscreenUserManager
+ @Mock
+ private lateinit var mediaHeaderView: MediaHeaderView
+ @Captor
+ private lateinit var visibilityListener: ArgumentCaptor<((Boolean) -> Unit)>
+ @JvmField @Rule
+ val mockito = MockitoJUnit.rule()
+ private lateinit var keyguardMediaController: KeyguardMediaController
+
+ @Before
+ fun setup() {
+ keyguardMediaController = KeyguardMediaController(mediaHost, bypassController,
+ statusBarStateController, notificationLockscreenUserManager)
+ }
+
+ @Test
+ fun testAttach_hiddenWhenHostIsHidden() {
+ `when`(mediaHost.visible).thenReturn(false)
+ triggerVisibilityListener()
+
+ verify(mediaHeaderView).visibility = eq(GONE)
+ }
+ @Test
+ fun testAttach_visibleOnKeyguard() {
+ `when`(mediaHost.visible).thenReturn(true)
+ `when`(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ `when`(notificationLockscreenUserManager.shouldShowLockscreenNotifications())
+ .thenReturn(true)
+ triggerVisibilityListener()
+
+ verify(mediaHeaderView).visibility = eq(VISIBLE)
+ }
+ @Test
+ fun testAttach_hiddenOnKeyguard_whenNotificationsAreHidden() {
+ `when`(mediaHost.visible).thenReturn(true)
+ `when`(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ `when`(notificationLockscreenUserManager.shouldShowLockscreenNotifications())
+ .thenReturn(false)
+ triggerVisibilityListener()
+
+ verify(mediaHeaderView).visibility = eq(GONE)
+ }
+
+ private fun triggerVisibilityListener() {
+ keyguardMediaController.attach(mediaHeaderView)
+ verify(mediaHost).visibleChangedListener = visibilityListener.capture()
+ visibilityListener.value.invoke(true)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
new file mode 100644
index 0000000..e8fb41a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import android.content.res.ColorStateList
+import android.graphics.Color
+import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.RippleDrawable
+import android.media.MediaMetadata
+import android.media.session.MediaSession
+import android.media.session.PlaybackState
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.ImageButton
+import android.widget.ImageView
+import android.widget.SeekBar
+import android.widget.TextView
+
+import androidx.constraintlayout.motion.widget.MotionLayout
+import androidx.constraintlayout.motion.widget.MotionScene
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.filters.SmallTest
+
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+
+import com.google.common.truth.Truth.assertThat
+
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when` as whenever
+
+import java.util.ArrayList
+
+private const val KEY = "TEST_KEY"
+private const val APP = "APP"
+private const val BG_COLOR = Color.RED
+private const val PACKAGE = "PKG"
+private const val ARTIST = "ARTIST"
+private const val TITLE = "TITLE"
+private const val DEVICE_NAME = "DEVICE_NAME"
+private const val SESSION_KEY = "SESSION_KEY"
+private const val SESSION_ARTIST = "SESSION_ARTIST"
+private const val SESSION_TITLE = "SESSION_TITLE"
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class MediaControlPanelTest : SysuiTestCase() {
+
+ private lateinit var player: MediaControlPanel
+
+ private lateinit var fgExecutor: FakeExecutor
+ private lateinit var bgExecutor: FakeExecutor
+ @Mock private lateinit var activityStarter: ActivityStarter
+
+ @Mock private lateinit var holder: PlayerViewHolder
+ @Mock private lateinit var motion: MotionLayout
+ private lateinit var background: TextView
+ private lateinit var appIcon: ImageView
+ private lateinit var appName: TextView
+ private lateinit var albumView: ImageView
+ private lateinit var titleText: TextView
+ private lateinit var artistText: TextView
+ private lateinit var seamless: ViewGroup
+ private lateinit var seamlessIcon: ImageView
+ private lateinit var seamlessText: TextView
+ private lateinit var seekBar: SeekBar
+ private lateinit var elapsedTimeView: TextView
+ private lateinit var totalTimeView: TextView
+ private lateinit var action0: ImageButton
+ private lateinit var action1: ImageButton
+ private lateinit var action2: ImageButton
+ private lateinit var action3: ImageButton
+ private lateinit var action4: ImageButton
+
+ private lateinit var session: MediaSession
+
+ @Before
+ fun setUp() {
+ fgExecutor = FakeExecutor(FakeSystemClock())
+ bgExecutor = FakeExecutor(FakeSystemClock())
+
+ activityStarter = mock(ActivityStarter::class.java)
+
+ player = MediaControlPanel(context, fgExecutor, bgExecutor, activityStarter)
+
+ // Mock out a view holder for the player to attach to.
+ holder = mock(PlayerViewHolder::class.java)
+ motion = mock(MotionLayout::class.java)
+ val trans: ArrayList<MotionScene.Transition> = ArrayList()
+ trans.add(mock(MotionScene.Transition::class.java))
+ whenever(motion.definedTransitions).thenReturn(trans)
+ val constraintSet = mock(ConstraintSet::class.java)
+ whenever(motion.getConstraintSet(R.id.expanded)).thenReturn(constraintSet)
+ whenever(motion.getConstraintSet(R.id.collapsed)).thenReturn(constraintSet)
+ whenever(holder.player).thenReturn(motion)
+ background = TextView(context)
+ whenever(holder.background).thenReturn(background)
+ appIcon = ImageView(context)
+ whenever(holder.appIcon).thenReturn(appIcon)
+ appName = TextView(context)
+ whenever(holder.appName).thenReturn(appName)
+ albumView = ImageView(context)
+ whenever(holder.albumView).thenReturn(albumView)
+ titleText = TextView(context)
+ whenever(holder.titleText).thenReturn(titleText)
+ artistText = TextView(context)
+ whenever(holder.artistText).thenReturn(artistText)
+ seamless = FrameLayout(context)
+ val seamlessBackground = mock(RippleDrawable::class.java)
+ seamless.setBackground(seamlessBackground)
+ whenever(seamlessBackground.getDrawable(0)).thenReturn(mock(GradientDrawable::class.java))
+ whenever(holder.seamless).thenReturn(seamless)
+ seamlessIcon = ImageView(context)
+ whenever(holder.seamlessIcon).thenReturn(seamlessIcon)
+ seamlessText = TextView(context)
+ whenever(holder.seamlessText).thenReturn(seamlessText)
+ seekBar = SeekBar(context)
+ whenever(holder.seekBar).thenReturn(seekBar)
+ elapsedTimeView = TextView(context)
+ whenever(holder.elapsedTimeView).thenReturn(elapsedTimeView)
+ totalTimeView = TextView(context)
+ whenever(holder.totalTimeView).thenReturn(totalTimeView)
+ action0 = ImageButton(context)
+ whenever(holder.action0).thenReturn(action0)
+ action1 = ImageButton(context)
+ whenever(holder.action1).thenReturn(action1)
+ action2 = ImageButton(context)
+ whenever(holder.action2).thenReturn(action2)
+ action3 = ImageButton(context)
+ whenever(holder.action3).thenReturn(action3)
+ action4 = ImageButton(context)
+ whenever(holder.action4).thenReturn(action4)
+
+ // Create media session
+ val metadataBuilder = MediaMetadata.Builder().apply {
+ putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
+ putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
+ }
+ val playbackBuilder = PlaybackState.Builder().apply {
+ setState(PlaybackState.STATE_PAUSED, 6000L, 1f)
+ setActions(PlaybackState.ACTION_PLAY)
+ }
+ session = MediaSession(context, SESSION_KEY).apply {
+ setMetadata(metadataBuilder.build())
+ setPlaybackState(playbackBuilder.build())
+ }
+ session.setActive(true)
+ }
+
+ @After
+ fun tearDown() {
+ session.release()
+ player.onDestroy()
+ }
+
+ @Test
+ fun bindWhenUnattached() {
+ val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
+ emptyList(), PACKAGE, null, null, MediaDeviceData(null, DEVICE_NAME))
+ player.bind(state)
+ assertThat(player.isPlaying()).isFalse()
+ }
+
+ @Test
+ fun bindText() {
+ player.attach(holder)
+ val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
+ emptyList(), PACKAGE, session.getSessionToken(), null,
+ MediaDeviceData(null, DEVICE_NAME))
+ player.bind(state)
+ assertThat(appName.getText()).isEqualTo(APP)
+ assertThat(titleText.getText()).isEqualTo(TITLE)
+ assertThat(artistText.getText()).isEqualTo(ARTIST)
+ }
+
+ @Test
+ fun bindBackgroundColor() {
+ player.attach(holder)
+ val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
+ emptyList(), PACKAGE, session.getSessionToken(), null,
+ MediaDeviceData(null, DEVICE_NAME))
+ player.bind(state)
+ assertThat(background.getBackgroundTintList()).isEqualTo(ColorStateList.valueOf(BG_COLOR))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
new file mode 100644
index 0000000..64a180f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.graphics.Color;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class MediaDataCombineLatestTest extends SysuiTestCase {
+
+ private static final String KEY = "TEST_KEY";
+ private static final String APP = "APP";
+ private static final String PACKAGE = "PKG";
+ private static final int BG_COLOR = Color.RED;
+ private static final String ARTIST = "ARTIST";
+ private static final String TITLE = "TITLE";
+ private static final String DEVICE_NAME = "DEVICE_NAME";
+
+ private MediaDataCombineLatest mManager;
+
+ @Mock private MediaDataManager mDataSource;
+ @Mock private MediaDeviceManager mDeviceSource;
+ @Mock private MediaDataManager.Listener mListener;
+
+ private MediaDataManager.Listener mDataListener;
+ private MediaDeviceManager.Listener mDeviceListener;
+
+ private MediaData mMediaData;
+ private MediaDeviceData mDeviceData;
+
+ @Before
+ public void setUp() {
+ mDataSource = mock(MediaDataManager.class);
+ mDeviceSource = mock(MediaDeviceManager.class);
+ mListener = mock(MediaDataManager.Listener.class);
+
+ mManager = new MediaDataCombineLatest(mDataSource, mDeviceSource);
+
+ mDataListener = captureDataListener();
+ mDeviceListener = captureDeviceListener();
+
+ mManager.addListener(mListener);
+
+ mMediaData = new MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null,
+ new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null);
+ mDeviceData = new MediaDeviceData(null, DEVICE_NAME);
+ }
+
+ @Test
+ public void eventNotEmittedWithoutDevice() {
+ // WHEN data source emits an event without device data
+ mDataListener.onMediaDataLoaded(KEY, mMediaData);
+ // THEN an event isn't emitted
+ verify(mListener, never()).onMediaDataLoaded(eq(KEY), any());
+ }
+
+ @Test
+ public void eventNotEmittedWithoutMedia() {
+ // WHEN device source emits an event without media data
+ mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData);
+ // THEN an event isn't emitted
+ verify(mListener, never()).onMediaDataLoaded(eq(KEY), any());
+ }
+
+ @Test
+ public void emitEventAfterDeviceFirst() {
+ // GIVEN that a device event has already been received
+ mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData);
+ // WHEN media event is received
+ mDataListener.onMediaDataLoaded(KEY, mMediaData);
+ // THEN the listener receives a combined event
+ ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
+ verify(mListener).onMediaDataLoaded(eq(KEY), captor.capture());
+ assertThat(captor.getValue().getDevice()).isNotNull();
+ }
+
+ @Test
+ public void emitEventAfterMediaFirst() {
+ // GIVEN that media event has already been received
+ mDataListener.onMediaDataLoaded(KEY, mMediaData);
+ // WHEN device event is received
+ mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData);
+ // THEN the listener receives a combined event
+ ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
+ verify(mListener).onMediaDataLoaded(eq(KEY), captor.capture());
+ assertThat(captor.getValue().getDevice()).isNotNull();
+ }
+
+ @Test
+ public void mediaDataRemoved() {
+ // WHEN media data is removed without first receiving device or data
+ mDataListener.onMediaDataRemoved(KEY);
+ // THEN a removed event isn't emitted
+ verify(mListener, never()).onMediaDataRemoved(eq(KEY));
+ }
+
+ @Test
+ public void mediaDataRemovedAfterMediaEvent() {
+ mDataListener.onMediaDataLoaded(KEY, mMediaData);
+ mDataListener.onMediaDataRemoved(KEY);
+ verify(mListener).onMediaDataRemoved(eq(KEY));
+ }
+
+ @Test
+ public void mediaDataRemovedAfterDeviceEvent() {
+ mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData);
+ mDataListener.onMediaDataRemoved(KEY);
+ verify(mListener).onMediaDataRemoved(eq(KEY));
+ }
+
+ private MediaDataManager.Listener captureDataListener() {
+ ArgumentCaptor<MediaDataManager.Listener> captor = ArgumentCaptor.forClass(
+ MediaDataManager.Listener.class);
+ verify(mDataSource).addListener(captor.capture());
+ return captor.getValue();
+ }
+
+ private MediaDeviceManager.Listener captureDeviceListener() {
+ ArgumentCaptor<MediaDeviceManager.Listener> captor = ArgumentCaptor.forClass(
+ MediaDeviceManager.Listener.class);
+ verify(mDeviceSource).addListener(captor.capture());
+ return captor.getValue();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
new file mode 100644
index 0000000..ac6b5f6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import android.app.Notification
+import android.media.MediaMetadata
+import android.media.session.MediaSession
+import android.media.session.PlaybackState
+import android.os.Process
+import android.service.notification.StatusBarNotification
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+
+import com.android.settingslib.media.LocalMediaManager
+import com.android.settingslib.media.MediaDevice
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+
+import com.google.common.truth.Truth.assertThat
+
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.any
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+private const val KEY = "TEST_KEY"
+private const val PACKAGE = "PKG"
+private const val SESSION_KEY = "SESSION_KEY"
+private const val SESSION_ARTIST = "SESSION_ARTIST"
+private const val SESSION_TITLE = "SESSION_TITLE"
+private const val DEVICE_NAME = "DEVICE_NAME"
+
+private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+public class MediaDeviceManagerTest : SysuiTestCase() {
+
+ private lateinit var manager: MediaDeviceManager
+
+ @Mock private lateinit var lmmFactory: LocalMediaManagerFactory
+ @Mock private lateinit var lmm: LocalMediaManager
+ @Mock private lateinit var featureFlag: MediaFeatureFlag
+ private lateinit var fakeExecutor: FakeExecutor
+
+ @Mock private lateinit var device: MediaDevice
+ private lateinit var session: MediaSession
+ private lateinit var metadataBuilder: MediaMetadata.Builder
+ private lateinit var playbackBuilder: PlaybackState.Builder
+ private lateinit var notifBuilder: Notification.Builder
+ private lateinit var sbn: StatusBarNotification
+
+ @Before
+ fun setup() {
+ lmmFactory = mock(LocalMediaManagerFactory::class.java)
+ lmm = mock(LocalMediaManager::class.java)
+ device = mock(MediaDevice::class.java)
+ whenever(device.name).thenReturn(DEVICE_NAME)
+ whenever(lmmFactory.create(PACKAGE)).thenReturn(lmm)
+ whenever(lmm.getCurrentConnectedDevice()).thenReturn(device)
+ featureFlag = mock(MediaFeatureFlag::class.java)
+ whenever(featureFlag.enabled).thenReturn(true)
+
+ fakeExecutor = FakeExecutor(FakeSystemClock())
+
+ manager = MediaDeviceManager(context, lmmFactory, featureFlag, fakeExecutor)
+
+ // Create a media sesssion and notification for testing.
+ metadataBuilder = MediaMetadata.Builder().apply {
+ putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
+ putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
+ }
+ playbackBuilder = PlaybackState.Builder().apply {
+ setState(PlaybackState.STATE_PAUSED, 6000L, 1f)
+ setActions(PlaybackState.ACTION_PLAY)
+ }
+ session = MediaSession(context, SESSION_KEY).apply {
+ setMetadata(metadataBuilder.build())
+ setPlaybackState(playbackBuilder.build())
+ }
+ session.setActive(true)
+ notifBuilder = Notification.Builder(context, "NONE").apply {
+ setContentTitle(SESSION_TITLE)
+ setContentText(SESSION_ARTIST)
+ setSmallIcon(android.R.drawable.ic_media_pause)
+ setStyle(Notification.MediaStyle().setMediaSession(session.getSessionToken()))
+ }
+ sbn = StatusBarNotification(PACKAGE, PACKAGE, 0, "TAG", Process.myUid(), 0, 0,
+ notifBuilder.build(), Process.myUserHandle(), 0)
+ }
+
+ @After
+ fun tearDown() {
+ session.release()
+ }
+
+ @Test
+ fun removeUnknown() {
+ manager.onNotificationRemoved("unknown")
+ }
+
+ @Test
+ fun addNotification() {
+ manager.onNotificationAdded(KEY, sbn)
+ verify(lmmFactory).create(PACKAGE)
+ }
+
+ @Test
+ fun featureDisabled() {
+ whenever(featureFlag.enabled).thenReturn(false)
+ manager.onNotificationAdded(KEY, sbn)
+ verify(lmmFactory, never()).create(PACKAGE)
+ }
+
+ @Test
+ fun addAndRemoveNotification() {
+ manager.onNotificationAdded(KEY, sbn)
+ manager.onNotificationRemoved(KEY)
+ verify(lmm).unregisterCallback(any())
+ }
+
+ @Test
+ fun deviceListUpdate() {
+ val listener = mock(MediaDeviceManager.Listener::class.java)
+ manager.addListener(listener)
+ manager.onNotificationAdded(KEY, sbn)
+ val deviceCallback = captureCallback()
+ // WHEN the device list changes
+ deviceCallback.onDeviceListUpdate(mutableListOf(device))
+ assertThat(fakeExecutor.runAllReady()).isEqualTo(1)
+ // THEN the update is dispatched to the listener
+ val captor = ArgumentCaptor.forClass(MediaDeviceData::class.java)
+ verify(listener).onMediaDeviceChanged(eq(KEY), captor.capture())
+ val data = captor.getValue()
+ assertThat(data.name).isEqualTo(DEVICE_NAME)
+ }
+
+ @Test
+ fun selectedDeviceStateChanged() {
+ val listener = mock(MediaDeviceManager.Listener::class.java)
+ manager.addListener(listener)
+ manager.onNotificationAdded(KEY, sbn)
+ val deviceCallback = captureCallback()
+ // WHEN the selected device changes state
+ deviceCallback.onSelectedDeviceStateChanged(device, 1)
+ assertThat(fakeExecutor.runAllReady()).isEqualTo(1)
+ // THEN the update is dispatched to the listener
+ val captor = ArgumentCaptor.forClass(MediaDeviceData::class.java)
+ verify(listener).onMediaDeviceChanged(eq(KEY), captor.capture())
+ val data = captor.getValue()
+ assertThat(data.name).isEqualTo(DEVICE_NAME)
+ }
+
+ @Test
+ fun listenerReceivesKeyRemoved() {
+ manager.onNotificationAdded(KEY, sbn)
+ val listener = mock(MediaDeviceManager.Listener::class.java)
+ manager.addListener(listener)
+ // WHEN the notification is removed
+ manager.onNotificationRemoved(KEY)
+ // THEN the listener receives key removed event
+ verify(listener).onKeyRemoved(eq(KEY))
+ }
+
+ fun captureCallback(): LocalMediaManager.DeviceCallback {
+ val captor = ArgumentCaptor.forClass(LocalMediaManager.DeviceCallback::class.java)
+ verify(lmm).registerCallback(captor.capture())
+ return captor.getValue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
index 58ee79e..75018df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
@@ -22,7 +22,6 @@
import android.widget.SeekBar
import android.widget.TextView
import androidx.test.filters.SmallTest
-import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -38,23 +37,21 @@
public class SeekBarObserverTest : SysuiTestCase() {
private lateinit var observer: SeekBarObserver
- @Mock private lateinit var mockView: View
+ @Mock private lateinit var mockHolder: PlayerViewHolder
private lateinit var seekBarView: SeekBar
private lateinit var elapsedTimeView: TextView
private lateinit var totalTimeView: TextView
@Before
fun setUp() {
- mockView = mock(View::class.java)
+ mockHolder = mock(PlayerViewHolder::class.java)
seekBarView = SeekBar(context)
elapsedTimeView = TextView(context)
totalTimeView = TextView(context)
- whenever<SeekBar>(
- mockView.findViewById(R.id.media_progress_bar)).thenReturn(seekBarView)
- whenever<TextView>(
- mockView.findViewById(R.id.media_elapsed_time)).thenReturn(elapsedTimeView)
- whenever<TextView>(mockView.findViewById(R.id.media_total_time)).thenReturn(totalTimeView)
- observer = SeekBarObserver(mockView)
+ whenever(mockHolder.seekBar).thenReturn(seekBarView)
+ whenever(mockHolder.elapsedTimeView).thenReturn(elapsedTimeView)
+ whenever(mockHolder.totalTimeView).thenReturn(totalTimeView)
+ observer = SeekBarObserver(mockHolder)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
index 1bbf24f..19e15b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
@@ -18,6 +18,7 @@
import android.media.MediaMetadata
import android.media.session.MediaController
+import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -35,9 +36,12 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
import org.mockito.Mock
+import org.mockito.Mockito.any
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
@@ -61,12 +65,15 @@
}
@Mock private lateinit var mockController: MediaController
@Mock private lateinit var mockTransport: MediaController.TransportControls
+ private val token1 = MediaSession.Token(1, null)
+ private val token2 = MediaSession.Token(2, null)
@Before
fun setUp() {
fakeExecutor = FakeExecutor(FakeSystemClock())
viewModel = SeekBarViewModel(fakeExecutor)
mockController = mock(MediaController::class.java)
+ whenever(mockController.sessionToken).thenReturn(token1)
mockTransport = mock(MediaController.TransportControls::class.java)
// LiveData to run synchronously
@@ -79,6 +86,42 @@
}
@Test
+ fun updateRegistersCallback() {
+ viewModel.updateController(mockController)
+ verify(mockController).registerCallback(any())
+ }
+
+ @Test
+ fun updateSecondTimeDoesNotRepeatRegistration() {
+ viewModel.updateController(mockController)
+ viewModel.updateController(mockController)
+ verify(mockController, times(1)).registerCallback(any())
+ }
+
+ @Test
+ fun updateDifferentControllerUnregistersCallback() {
+ viewModel.updateController(mockController)
+ viewModel.updateController(mock(MediaController::class.java))
+ verify(mockController).unregisterCallback(any())
+ }
+
+ @Test
+ fun updateDifferentControllerRegistersCallback() {
+ viewModel.updateController(mockController)
+ val controller2 = mock(MediaController::class.java)
+ whenever(controller2.sessionToken).thenReturn(token2)
+ viewModel.updateController(controller2)
+ verify(controller2).registerCallback(any())
+ }
+
+ @Test
+ fun updateToNullUnregistersCallback() {
+ viewModel.updateController(mockController)
+ viewModel.updateController(null)
+ verify(mockController).unregisterCallback(any())
+ }
+
+ @Test
fun updateDurationWithPlayback() {
// GIVEN that the duration is contained within the metadata
val duration = 12000L
@@ -375,6 +418,26 @@
}
@Test
+ fun playbackChangeQueuesPollTask() {
+ viewModel.updateController(mockController)
+ val captor = ArgumentCaptor.forClass(MediaController.Callback::class.java)
+ verify(mockController).registerCallback(captor.capture())
+ val callback = captor.value
+ // WHEN the callback receives an new state
+ val state = PlaybackState.Builder().run {
+ setState(PlaybackState.STATE_PLAYING, 100L, 1f)
+ build()
+ }
+ callback.onPlaybackStateChanged(state)
+ with(fakeExecutor) {
+ advanceClockToNext()
+ runAllReady()
+ }
+ // THEN an update task is queued
+ assertThat(fakeExecutor.numPending()).isEqualTo(1)
+ }
+
+ @Test
fun clearSeekBar() {
// GIVEN that the duration is contained within the metadata
val metadata = MediaMetadata.Builder().run {
@@ -399,4 +462,20 @@
// THEN the seek bar is disabled
assertThat(viewModel.progress.value!!.enabled).isFalse()
}
+
+ @Test
+ fun clearSeekBarUnregistersCallback() {
+ viewModel.updateController(mockController)
+ viewModel.clearController()
+ fakeExecutor.runAllReady()
+ verify(mockController).unregisterCallback(any())
+ }
+
+ @Test
+ fun destroyUnregistersCallback() {
+ viewModel.updateController(mockController)
+ viewModel.onDestroy()
+ fakeExecutor.runAllReady()
+ verify(mockController).unregisterCallback(any())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index a24fa84..808f17b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -52,7 +52,6 @@
import androidx.test.filters.SmallTest;
-import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -86,7 +85,7 @@
@Mock
private DevicePolicyManager mDevicePolicyManager;
@Mock
- private IStatusBarService mIStatusBarService;
+ private NotificationClickNotifier mClickNotifier;
@Mock
private KeyguardManager mKeyguardManager;
@Mock
@@ -397,7 +396,7 @@
extends NotificationLockscreenUserManagerImpl {
public TestNotificationLockscreenUserManager(Context context) {
super(context, mBroadcastDispatcher, mDevicePolicyManager, mUserManager,
- mIStatusBarService, NotificationLockscreenUserManagerTest.this.mKeyguardManager,
+ mClickNotifier, NotificationLockscreenUserManagerTest.this.mKeyguardManager,
mStatusBarStateController, Handler.createAsync(Looper.myLooper()),
mDeviceProvisionedController, mKeyguardStateController);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index 5a7dea4..eaef43d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -62,6 +62,7 @@
@Mock private ExpandableNotificationRow mRow;
@Mock private StatusBarStateController mStateController;
@Mock private RemoteInputUriController mRemoteInputUriController;
+ @Mock private NotificationClickNotifier mClickNotifier;
// Dependency mocks:
@Mock private NotificationEntryManager mEntryManager;
@@ -83,6 +84,7 @@
mStateController,
Handler.createAsync(Looper.myLooper()),
mRemoteInputUriController,
+ mClickNotifier,
mock(ActionClickLogger.class));
mEntry = new NotificationEntryBuilder()
.setPkg(TEST_PACKAGE_NAME)
@@ -266,6 +268,7 @@
StatusBarStateController statusBarStateController,
Handler mainHandler,
RemoteInputUriController remoteInputUriController,
+ NotificationClickNotifier clickNotifier,
ActionClickLogger actionClickLogger) {
super(
context,
@@ -276,6 +279,7 @@
statusBarStateController,
mainHandler,
remoteInputUriController,
+ clickNotifier,
actionClickLogger);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index c46ac47..e889dad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator
import com.android.systemui.statusbar.phone.BiometricUnlockController
+import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.NotificationShadeWindowController
import com.android.systemui.statusbar.policy.KeyguardStateController
import org.junit.Before
@@ -67,6 +68,7 @@
@Mock private lateinit var shadeAnimation: NotificationShadeDepthController.DepthAnimation
@Mock private lateinit var globalActionsSpring: NotificationShadeDepthController.DepthAnimation
@Mock private lateinit var brightnessSpring: NotificationShadeDepthController.DepthAnimation
+ @Mock private lateinit var dozeParameters: DozeParameters
@JvmField @Rule val mockitoRule = MockitoJUnit.rule()
private lateinit var statusBarStateListener: StatusBarStateController.StateListener
@@ -87,7 +89,7 @@
notificationShadeDepthController = NotificationShadeDepthController(
statusBarStateController, blurUtils, biometricUnlockController,
keyguardStateController, choreographer, wallpaperManager,
- notificationShadeWindowController, dumpManager)
+ notificationShadeWindowController, dozeParameters, dumpManager)
notificationShadeDepthController.shadeSpring = shadeSpring
notificationShadeDepthController.shadeAnimation = shadeAnimation
notificationShadeDepthController.brightnessMirrorSpring = brightnessSpring
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
index 79507e9..7cbc4e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
@@ -75,6 +75,7 @@
@Mock private IStatusBarService mIStatusBarService;
@Mock private StatusBarStateController mStatusBarStateController;
@Mock private RemoteInputUriController mRemoteInputUriController;
+ @Mock private NotificationClickNotifier mClickNotifier;
@Before
public void setUp() {
@@ -83,7 +84,7 @@
mNotificationEntryManager);
mSmartReplyController = new SmartReplyController(mNotificationEntryManager,
- mIStatusBarService);
+ mIStatusBarService, mClickNotifier);
mDependency.injectTestDependency(SmartReplyController.class,
mSmartReplyController);
@@ -93,6 +94,7 @@
mStatusBarStateController,
Handler.createAsync(Looper.myLooper()),
mRemoteInputUriController,
+ mClickNotifier,
mock(ActionClickLogger.class));
mRemoteInputManager.setUpWithCallback(mCallback, mDelegate);
mNotification = new Notification.Builder(mContext, "")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index d583048..a5a5f81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -210,6 +210,28 @@
}
@Test
+ public void testAddNotification_noDuplicateEntriesCreated() {
+ // GIVEN a notification has been added
+ mEntryManager.addNotification(mSbn, mRankingMap);
+
+ // WHEN the same notification is added multiple times before the previous entry (with
+ // the same key) didn't finish inflating
+ mEntryManager.addNotification(mSbn, mRankingMap);
+ mEntryManager.addNotification(mSbn, mRankingMap);
+ mEntryManager.addNotification(mSbn, mRankingMap);
+
+ // THEN getAllNotifs() only contains exactly one notification with this key
+ int count = 0;
+ for (NotificationEntry entry : mEntryManager.getAllNotifs()) {
+ if (entry.getKey().equals(mSbn.getKey())) {
+ count++;
+ }
+ }
+ assertEquals("Should only be one entry with key=" + mSbn.getKey() + " in mAllNotifs. "
+ + "Instead there are " + count, 1, count);
+ }
+
+ @Test
public void testAddNotification_setsUserSentiment() {
mEntryManager.addNotification(mSbn, mRankingMap);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 82de4a3..ca9cc29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -34,6 +34,7 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
@@ -65,6 +66,7 @@
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.dump.LogBufferEulogizer;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.notification.collection.NoManSimulator.NotifEvent;
@@ -100,11 +102,13 @@
public class NotifCollectionTest extends SysuiTestCase {
@Mock private IStatusBarService mStatusBarService;
+ @Mock private FeatureFlags mFeatureFlags;
@Mock private NotifCollectionLogger mLogger;
+ @Mock private LogBufferEulogizer mEulogizer;
+
@Mock private GroupCoalescer mGroupCoalescer;
@Spy private RecordingCollectionListener mCollectionListener;
@Mock private CollectionReadyForBuildListener mBuildListener;
- @Mock private FeatureFlags mFeatureFlags;
@Spy private RecordingLifetimeExtender mExtender1 = new RecordingLifetimeExtender("Extender1");
@Spy private RecordingLifetimeExtender mExtender2 = new RecordingLifetimeExtender("Extender2");
@@ -136,13 +140,16 @@
when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(true);
when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(true);
+ when(mEulogizer.record(any(Exception.class))).thenAnswer(i -> i.getArguments()[0]);
+
mListenerInOrder = inOrder(mCollectionListener);
mCollection = new NotifCollection(
mStatusBarService,
mock(DumpManager.class),
mFeatureFlags,
- mLogger);
+ mLogger,
+ mEulogizer);
mCollection.attach(mGroupCoalescer);
mCollection.addCollectionListener(mCollectionListener);
mCollection.setBuildListener(mBuildListener);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
index 9f47f4a..1c47131 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
@@ -38,9 +38,11 @@
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
+import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
import com.android.systemui.statusbar.phone.NotificationGroupManager
import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.google.common.truth.Truth.assertThat
import dagger.Lazy
import junit.framework.Assert.assertEquals
import org.junit.Before
@@ -353,6 +355,58 @@
assertEquals(false, e.hasFinishedInitialization())
}
+ @Test
+ fun testSort_colorizedForegroundService() {
+ whenever(sectionsManager.isFilteringEnabled()).thenReturn(true)
+
+ val a = NotificationEntryBuilder()
+ .setImportance(IMPORTANCE_HIGH)
+ .setPkg("pkg")
+ .setOpPkg("pkg")
+ .setTag("tag")
+ .setNotification(
+ Notification.Builder(mContext, "test")
+ .build())
+ .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
+ .setUser(mContext.getUser())
+ .setOverrideGroupKey("")
+ .build()
+
+ val b = NotificationEntryBuilder()
+ .setImportance(IMPORTANCE_DEFAULT) // high priority
+ .setPkg("pkg2")
+ .setOpPkg("pkg2")
+ .setTag("tag")
+ .setNotification(mock(Notification::class.java).also { notif ->
+ whenever(notif.isForegroundService).thenReturn(true)
+ whenever(notif.isColorized).thenReturn(true)
+ })
+ .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
+ .setUser(mContext.getUser())
+ .setOverrideGroupKey("")
+ .build()
+
+ val cN = Notification.Builder(mContext, "test")
+ .setStyle(Notification.MessagingStyle(""))
+ .build()
+ val c = NotificationEntryBuilder()
+ .setImportance(IMPORTANCE_HIGH)
+ .setPkg("pkg")
+ .setOpPkg("pkg")
+ .setTag("tag")
+ .setNotification(cN)
+ .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
+ .setUser(mContext.user)
+ .setOverrideGroupKey("")
+ .build()
+ whenever(personNotificationIdentifier.getPeopleNotificationType(a.sbn, a.ranking))
+ .thenReturn(TYPE_IMPORTANT_PERSON)
+
+ assertThat(rankingManager.updateRanking(null, listOf(a, b, c), "test"))
+ .containsExactly(b, c, a)
+ assertThat(b.bucket).isEqualTo(BUCKET_FOREGROUND_SERVICE)
+ }
+
internal class TestableNotificationRankingManager(
mediaManager: Lazy<NotificationMediaManager>,
groupManager: NotificationGroupManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index a93b54a..6fa5055 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -44,6 +44,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.NotificationInteractionTracker;
import com.android.systemui.statusbar.notification.collection.ShadeListBuilder.OnRenderListListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
@@ -86,6 +87,7 @@
@Mock private ShadeListBuilderLogger mLogger;
@Mock private NotifCollection mNotifCollection;
+ @Mock private NotificationInteractionTracker mInteractionTracker;
@Spy private OnBeforeTransformGroupsListener mOnBeforeTransformGroupsListener;
@Spy private OnBeforeSortListener mOnBeforeSortListener;
@Spy private OnBeforeFinalizeFilterListener mOnBeforeFinalizeFilterListener;
@@ -107,7 +109,8 @@
MockitoAnnotations.initMocks(this);
allowTestableLooperAsMainThread();
- mListBuilder = new ShadeListBuilder(mSystemClock, mLogger, mock(DumpManager.class));
+ mListBuilder = new ShadeListBuilder(
+ mSystemClock, mLogger, mock(DumpManager.class), mInteractionTracker);
mListBuilder.setOnRenderListListener(mOnRenderListListener);
mListBuilder.attach(mNotifCollection);
@@ -1280,7 +1283,7 @@
} catch (AssertionError err) {
throw new AssertionError(
"List under test failed verification:\n" + dumpTree(mBuiltList,
- true, ""), err);
+ mInteractionTracker, true, ""), err);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
index f21b1a6..545b59a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
@@ -47,6 +47,7 @@
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.text.SpannableString;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
@@ -151,8 +152,11 @@
NotificationChannel.DEFAULT_CHANNEL_ID, TEST_CHANNEL_NAME,
IMPORTANCE_LOW);
mDefaultNotificationChannelSet.add(mDefaultNotificationChannel);
+ Notification n = new Notification.Builder(mContext, mNotificationChannel.getId())
+ .setContentTitle(new SpannableString("title"))
+ .build();
mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
- new Notification(), UserHandle.CURRENT, null, 0);
+ n, UserHandle.CURRENT, null, 0);
mEntry = new NotificationEntryBuilder().setSbn(mSbn).build();
}
@@ -176,6 +180,23 @@
}
@Test
+ public void testBindNotification_SetsName() {
+ mInfo.bindNotification(
+ mMockPackageManager,
+ mMockINotificationManager,
+ mChannelEditorDialogController,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mNotificationChannelSet,
+ mEntry,
+ null,
+ true,
+ false);
+ final TextView textView = mInfo.findViewById(R.id.name);
+ assertTrue(textView.getText().toString().contains("title"));
+ }
+
+ @Test
public void testBindNotification_groupSetsPackageIcon() {
mEntry.getSbn().getNotification().extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, true);
final Drawable iconDrawable = mock(Drawable.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index b5663d5..c2d2181 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -43,6 +43,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
import androidx.test.filters.SmallTest;
@@ -80,11 +81,13 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
+import java.util.List;
import java.util.function.Consumer;
@SmallTest
@@ -176,9 +179,12 @@
private MediaHierarchyManager mMediaHiearchyManager;
@Mock
private ConversationNotificationManager mConversationNotificationManager;
+ @Mock
+ private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
private NotificationPanelViewController mNotificationPanelViewController;
+ private View.AccessibilityDelegate mAccessibiltyDelegate;
@Before
public void setup() {
@@ -231,11 +237,18 @@
mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
mMetricsLogger, mActivityManager, mZenModeController, mConfigurationController,
mFlingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
- mConversationNotificationManager, mMediaHiearchyManager);
+ mConversationNotificationManager, mMediaHiearchyManager,
+ mStatusBarKeyguardViewManager);
mNotificationPanelViewController.initDependencies(mStatusBar, mGroupManager,
mNotificationShelf, mNotificationAreaController, mScrimController);
mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
mNotificationPanelViewController.setBar(mPanelBar);
+
+ ArgumentCaptor<View.AccessibilityDelegate> accessibilityDelegateArgumentCaptor =
+ ArgumentCaptor.forClass(View.AccessibilityDelegate.class);
+ verify(mView)
+ .setAccessibilityDelegate(accessibilityDelegateArgumentCaptor.capture());
+ mAccessibiltyDelegate = accessibilityDelegateArgumentCaptor.getValue();
}
@Test
@@ -305,6 +318,39 @@
verify(mKeyguardStatusBar, never()).setVisibility(View.VISIBLE);
}
+ @Test
+ public void testA11y_initializeNode() {
+ AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
+ mAccessibiltyDelegate.onInitializeAccessibilityNodeInfo(mView, nodeInfo);
+
+ List<AccessibilityNodeInfo.AccessibilityAction> actionList = nodeInfo.getActionList();
+ assertThat(actionList).containsAllIn(
+ new AccessibilityNodeInfo.AccessibilityAction[] {
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD,
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP}
+ );
+ }
+
+ @Test
+ public void testA11y_scrollForward() {
+ mAccessibiltyDelegate.performAccessibilityAction(
+ mView,
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId(),
+ null);
+
+ verify(mStatusBarKeyguardViewManager).showBouncer(true);
+ }
+
+ @Test
+ public void testA11y_scrollUp() {
+ mAccessibiltyDelegate.performAccessibilityAction(
+ mView,
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP.getId(),
+ null);
+
+ verify(mStatusBarKeyguardViewManager).showBouncer(true);
+ }
+
private void onTouchEvent(MotionEvent ev) {
mTouchHandler.onTouch(mView, ev);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.java
new file mode 100644
index 0000000..a14d575
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlarmManager;
+import android.app.IActivityManager;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.os.UserManager;
+import android.telecom.TelecomManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.media.MediaDataManager;
+import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.BluetoothController;
+import com.android.systemui.statusbar.policy.CastController;
+import com.android.systemui.statusbar.policy.DataSaverController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.HotspotController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.LocationController;
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.RotationLockController;
+import com.android.systemui.statusbar.policy.SensorPrivacyController;
+import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.util.RingerModeLiveData;
+import com.android.systemui.util.RingerModeTracker;
+import com.android.systemui.util.time.DateFormatUtil;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.concurrent.Executor;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class PhoneStatusBarPolicyTest extends SysuiTestCase {
+
+ private static final int DISPLAY_ID = 0;
+ @Mock
+ private StatusBarIconController mIconController;
+ @Mock
+ private CommandQueue mCommandQueue;
+ @Mock
+ private BroadcastDispatcher mBroadcastDispatcher;
+ @Mock
+ private Executor mBackgroundExecutor;
+ @Mock
+ private CastController mCastController;
+ @Mock
+ private HotspotController mHotSpotController;
+ @Mock
+ private BluetoothController mBluetoothController;
+ @Mock
+ private NextAlarmController mNextAlarmController;
+ @Mock
+ private UserInfoController mUserInfoController;
+ @Mock
+ private RotationLockController mRotationLockController;
+ @Mock
+ private DataSaverController mDataSaverController;
+ @Mock
+ private ZenModeController mZenModeController;
+ @Mock
+ private DeviceProvisionedController mDeviceProvisionerController;
+ @Mock
+ private KeyguardStateController mKeyguardStateController;
+ @Mock
+ private LocationController mLocationController;
+ @Mock
+ private SensorPrivacyController mSensorPrivacyController;
+ @Mock
+ private IActivityManager mIActivityManager;
+ @Mock
+ private AlarmManager mAlarmManager;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private RecordingController mRecordingController;
+ @Mock
+ private MediaDataManager mMediaDataManager;
+ @Mock
+ private TelecomManager mTelecomManager;
+ @Mock
+ private SharedPreferences mSharedPreferences;
+ @Mock
+ private DateFormatUtil mDateFormatUtil;
+ @Mock
+ private RingerModeTracker mRingerModeTracker;
+ @Mock
+ private RingerModeLiveData mRingerModeLiveData;
+ @Rule
+ public MockitoRule rule = MockitoJUnit.rule();
+ private Resources mResources;
+ private PhoneStatusBarPolicy mPhoneStatusBarPolicy;
+
+ @Before
+ public void setup() {
+ mResources = spy(getContext().getResources());
+ mPhoneStatusBarPolicy = new PhoneStatusBarPolicy(mIconController, mCommandQueue,
+ mBroadcastDispatcher, mBackgroundExecutor, mResources, mCastController,
+ mHotSpotController, mBluetoothController, mNextAlarmController, mUserInfoController,
+ mRotationLockController, mDataSaverController, mZenModeController,
+ mDeviceProvisionerController, mKeyguardStateController, mLocationController,
+ mSensorPrivacyController, mIActivityManager, mAlarmManager, mUserManager,
+ mRecordingController, mMediaDataManager, mTelecomManager, DISPLAY_ID,
+ mSharedPreferences, mDateFormatUtil, mRingerModeTracker);
+ when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData);
+ when(mRingerModeTracker.getRingerModeInternal()).thenReturn(mRingerModeLiveData);
+ clearInvocations(mIconController);
+ }
+
+ @Test
+ public void testInit_registerMediaCallback() {
+ mPhoneStatusBarPolicy.init();
+ verify(mMediaDataManager).addListener(eq(mPhoneStatusBarPolicy));
+ }
+
+ @Test
+ public void testOnMediaDataLoaded_updatesIcon_hasMedia() {
+ String mediaSlot = mResources.getString(com.android.internal.R.string.status_bar_media);
+ when(mMediaDataManager.hasActiveMedia()).thenReturn(true);
+ mPhoneStatusBarPolicy.onMediaDataLoaded(null, null);
+ verify(mMediaDataManager).hasActiveMedia();
+ verify(mIconController).setIconVisibility(eq(mediaSlot), eq(true));
+ }
+
+ @Test
+ public void testOnMediaDataRemoved_updatesIcon_noMedia() {
+ String mediaSlot = mResources.getString(com.android.internal.R.string.status_bar_media);
+ mPhoneStatusBarPolicy.onMediaDataRemoved(null);
+ verify(mMediaDataManager).hasActiveMedia();
+ verify(mIconController).setIconVisibility(eq(mediaSlot), eq(false));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 1afe132..53a1773 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -48,7 +48,6 @@
import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.ActivityIntentHelper;
@@ -59,6 +58,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.NotificationClickNotifier;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -98,7 +98,7 @@
@Mock
private ActivityStarter mActivityStarter;
@Mock
- private IStatusBarService mStatusBarService;
+ private NotificationClickNotifier mClickNotifier;
@Mock
private StatusBarStateController mStatusBarStateController;
@Mock
@@ -188,7 +188,7 @@
mNotifCollection,
mock(HeadsUpManagerPhone.class),
mActivityStarter,
- mStatusBarService,
+ mClickNotifier,
mock(StatusBarStateController.class),
mStatusBarKeyguardViewManager,
mock(KeyguardManager.class),
@@ -263,7 +263,7 @@
verify(mAssistManager).hideAssist();
- verify(mStatusBarService).onNotificationClick(
+ verify(mClickNotifier).onNotificationClick(
eq(sbn.getKey()), any(NotificationVisibility.class));
// Notification is removed due to FLAG_AUTO_CANCEL
@@ -289,7 +289,7 @@
verify(mAssistManager).hideAssist();
- verify(mStatusBarService).onNotificationClick(
+ verify(mClickNotifier).onNotificationClick(
eq(sbn.getKey()), any(NotificationVisibility.class));
// The content intent should NOT be sent on click.
@@ -319,7 +319,7 @@
verify(mAssistManager).hideAssist();
- verify(mStatusBarService).onNotificationClick(
+ verify(mClickNotifier).onNotificationClick(
eq(sbn.getKey()), any(NotificationVisibility.class));
// The content intent should NOT be sent on click.
@@ -349,7 +349,7 @@
verify(mAssistManager).hideAssist();
- verify(mStatusBarService).onNotificationClick(
+ verify(mClickNotifier).onNotificationClick(
eq(sbn.getKey()), any(NotificationVisibility.class));
// The content intent should NOT be sent on click.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/io/FakeBasicFileAttributes.java b/packages/SystemUI/tests/src/com/android/systemui/util/io/FakeBasicFileAttributes.java
new file mode 100644
index 0000000..f7a04dc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/io/FakeBasicFileAttributes.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.io;
+
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Fake implementation of {@link BasicFileAttributes} (for use in tests)
+ */
+public class FakeBasicFileAttributes implements BasicFileAttributes {
+ private FileTime mLastModifiedTime = FileTime.from(0, TimeUnit.MILLISECONDS);
+ private FileTime mLastAccessTime = FileTime.from(0, TimeUnit.MILLISECONDS);
+ private FileTime mCreationTime = FileTime.from(0, TimeUnit.MILLISECONDS);
+ private boolean mIsRegularFile = true;
+ private boolean mIsDirectory = false;
+ private boolean mIsSymbolicLink = false;
+ private boolean mIsOther = false;
+ private long mSize = 0;
+ private Object mFileKey = null;
+
+ @Override
+ public FileTime lastModifiedTime() {
+ return mLastModifiedTime;
+ }
+
+ @Override
+ public FileTime lastAccessTime() {
+ return mLastAccessTime;
+ }
+
+ @Override
+ public FileTime creationTime() {
+ return mCreationTime;
+ }
+
+ @Override
+ public boolean isRegularFile() {
+ return mIsRegularFile;
+ }
+
+ @Override
+ public boolean isDirectory() {
+ return mIsDirectory;
+ }
+
+ @Override
+ public boolean isSymbolicLink() {
+ return mIsSymbolicLink;
+ }
+
+ @Override
+ public boolean isOther() {
+ return mIsOther;
+ }
+
+ @Override
+ public long size() {
+ return mSize;
+ }
+
+ @Override
+ public Object fileKey() {
+ return mFileKey;
+ }
+
+ public FakeBasicFileAttributes setLastModifiedTime(long millis) {
+ mLastModifiedTime = FileTime.from(millis, TimeUnit.MILLISECONDS);
+ return this;
+ }
+
+ public FakeBasicFileAttributes setLastAccessTime(long millis) {
+ mLastAccessTime = FileTime.from(millis, TimeUnit.MILLISECONDS);
+ return this;
+ }
+
+ public FakeBasicFileAttributes setCreationTime(long millis) {
+ mCreationTime = FileTime.from(millis, TimeUnit.MILLISECONDS);
+ return this;
+ }
+
+ public FakeBasicFileAttributes setRegularFile(boolean regularFile) {
+ mIsRegularFile = regularFile;
+ return this;
+ }
+
+ public FakeBasicFileAttributes setDirectory(boolean directory) {
+ mIsDirectory = directory;
+ return this;
+ }
+
+ public FakeBasicFileAttributes setSymbolicLink(boolean symbolicLink) {
+ mIsSymbolicLink = symbolicLink;
+ return this;
+ }
+
+ public FakeBasicFileAttributes setOther(boolean other) {
+ mIsOther = other;
+ return this;
+ }
+
+ public FakeBasicFileAttributes setSize(long size) {
+ mSize = size;
+ return this;
+ }
+
+ public FakeBasicFileAttributes setFileKey(Object fileKey) {
+ mFileKey = fileKey;
+ return this;
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java
index 31d884c..bd697fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java
@@ -20,6 +20,7 @@
public class FakeProximitySensor extends ProximitySensor {
private boolean mAvailable;
+ private boolean mRegistered;
public FakeProximitySensor(Resources resources, AsyncSensorManager sensorManager) {
super(resources, sensorManager);
@@ -35,17 +36,22 @@
}
@Override
+ public boolean isRegistered() {
+ return mRegistered;
+ }
+
+ @Override
public boolean getSensorAvailable() {
return mAvailable;
}
@Override
protected void registerInternal() {
- // no-op
+ mRegistered = !mPaused;
}
@Override
protected void unregisterInternal() {
- // no-op
+ mRegistered = false;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java
new file mode 100644
index 0000000..7221095
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.sensors;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Handler;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class ProximityCheckTest extends SysuiTestCase {
+
+ private FakeProximitySensor mFakeProximitySensor;
+ private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+
+ private TestableCallback mTestableCallback = new TestableCallback();
+
+ private ProximitySensor.ProximityCheck mProximityCheck;
+
+ @Before
+ public void setUp() throws Exception {
+ AsyncSensorManager asyncSensorManager =
+ new AsyncSensorManager(new FakeSensorManager(mContext), null, new Handler());
+ mFakeProximitySensor = new FakeProximitySensor(mContext.getResources(), asyncSensorManager);
+
+ mProximityCheck = new ProximitySensor.ProximityCheck(mFakeProximitySensor, mFakeExecutor);
+ }
+
+ @Test
+ public void testCheck() {
+ mProximityCheck.check(100, mTestableCallback);
+
+ assertNull(mTestableCallback.mLastResult);
+
+ mFakeProximitySensor.setLastEvent(new ProximitySensor.ProximityEvent(true, 0));
+ mFakeProximitySensor.alertListeners();
+
+ assertTrue(mTestableCallback.mLastResult);
+ }
+
+ @Test
+ public void testTimeout() {
+ mProximityCheck.check(100, mTestableCallback);
+
+ assertTrue(mFakeProximitySensor.isRegistered());
+
+ mFakeExecutor.advanceClockToNext();
+ mFakeExecutor.runAllReady();
+
+ assertFalse(mFakeProximitySensor.isRegistered());
+ }
+
+ private static class TestableCallback implements Consumer<Boolean> {
+ Boolean mLastResult;
+ @Override
+ public void accept(Boolean result) {
+ mLastResult = result;
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java
index 526fba7..914790b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java
@@ -219,6 +219,26 @@
waitForSensorManager();
}
+ @Test
+ public void testPreventRecursiveAlert() {
+ TestableListener listenerA = new TestableListener() {
+ @Override
+ public void onSensorEvent(ProximitySensor.ProximityEvent proximityEvent) {
+ super.onSensorEvent(proximityEvent);
+ if (mCallCount < 2) {
+ mProximitySensor.alertListeners();
+ }
+ }
+ };
+
+ mProximitySensor.register(listenerA);
+
+ mProximitySensor.alertListeners();
+
+ assertEquals(1, listenerA.mCallCount);
+ }
+
+
class TestableListener implements ProximitySensor.ProximitySensorListener {
ProximitySensor.ProximityEvent mLastEvent;
int mCallCount = 0;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java b/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java
index 601f88e..ecfb357 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java
@@ -36,8 +36,9 @@
private long mElapsedRealtime = 10000;
private long mCurrentThreadTimeMillis = 10000;
- private final List<ClockTickListener> mListeners = new ArrayList<>();
+ private long mCurrentTimeMillis = 1555555500000L;
+ private final List<ClockTickListener> mListeners = new ArrayList<>();
@Override
public long uptimeMillis() {
return mUptimeMillis;
@@ -58,10 +59,19 @@
return mCurrentThreadTimeMillis;
}
+ @Override
+ public long currentTimeMillis() {
+ return mCurrentTimeMillis;
+ }
+
public void setUptimeMillis(long uptime) {
advanceTime(uptime - mUptimeMillis);
}
+ public void setCurrentTimeMillis(long millis) {
+ mCurrentTimeMillis = millis;
+ }
+
public void advanceTime(long uptime) {
advanceTime(uptime, 0);
}
@@ -74,6 +84,7 @@
if (uptime > 0 || sleepTime > 0) {
mUptimeMillis += uptime;
mElapsedRealtime += uptime + sleepTime;
+ mCurrentTimeMillis += uptime + sleepTime;
mCurrentThreadTimeMillis += Math.ceil(uptime * 0.5);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index b3867a3..07bb335 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -51,6 +51,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.database.ContentObserver;
@@ -2934,11 +2935,19 @@
public class AccessibilityDisplayListener implements DisplayManager.DisplayListener {
private final DisplayManager mDisplayManager;
private final ArrayList<Display> mDisplaysList = new ArrayList<>();
+ private int mSystemUiUid = 0;
AccessibilityDisplayListener(Context context, MainHandler handler) {
mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
mDisplayManager.registerDisplayListener(this, handler);
initializeDisplayList();
+
+ final PackageManagerInternal pm =
+ LocalServices.getService(PackageManagerInternal.class);
+ if (pm != null) {
+ mSystemUiUid = pm.getPackageUid(pm.getSystemUiServiceComponent().getPackageName(),
+ PackageManager.MATCH_SYSTEM_ONLY, mCurrentUserId);
+ }
}
ArrayList<Display> getValidDisplayList() {
@@ -3034,8 +3043,13 @@
}
// Private virtual displays are created by the ap and is not allowed to access by other
// aps. We assume we could ignore them.
+ // The exceptional case is for bubbles. Because the bubbles use the activityView, and
+ // the virtual display of the activityView is private, so if the owner UID of the
+ // private virtual display is the one of system ui which creates the virtual display of
+ // bubbles, then this private virtual display should track the windows.
if (display.getType() == Display.TYPE_VIRTUAL
- && (display.getFlags() & Display.FLAG_PRIVATE) != 0) {
+ && (display.getFlags() & Display.FLAG_PRIVATE) != 0
+ && display.getOwnerUid() != mSystemUiUid) {
return false;
}
return true;
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java
index e5340f1..642a841 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java
@@ -197,6 +197,7 @@
if (getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR) {
// Needs more fingers lifted within the tap timeout
// after reaching the target number of fingers are down.
+ cancelAfterTapTimeout(event, rawEvent, policyFlags);
} else {
cancelGesture(event, rawEvent, policyFlags);
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 1197154..f7ace06 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -7134,6 +7134,14 @@
networkAgent.networkCapabilities.addCapability(NET_CAPABILITY_FOREGROUND);
if (!createNativeNetwork(networkAgent)) return;
+ if (networkAgent.isVPN()) {
+ // Initialize the VPN capabilities to their starting values according to the
+ // underlying networks. This will avoid a spurious callback to
+ // onCapabilitiesUpdated being sent in updateAllVpnCapabilities below as
+ // the VPN would switch from its default, blank capabilities to those
+ // that reflect the capabilities of its underlying networks.
+ updateAllVpnsCapabilities();
+ }
networkAgent.created = true;
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index e2a0c29..ee0f71b 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -42,8 +42,8 @@
import static android.os.storage.OnObbStateChangeListener.ERROR_PERMISSION_DENIED;
import static android.os.storage.OnObbStateChangeListener.MOUNTED;
import static android.os.storage.OnObbStateChangeListener.UNMOUNTED;
+import static android.os.storage.StorageManager.PROP_FORCED_SCOPED_STORAGE_WHITELIST;
import static android.os.storage.StorageManager.PROP_FUSE;
-import static android.os.storage.StorageManager.PROP_LEGACY_OP_STICKY;
import static android.os.storage.StorageManager.PROP_SETTINGS_FUSE;
import static com.android.internal.util.XmlUtils.readIntAttribute;
@@ -914,7 +914,6 @@
refreshIsolatedStorageSettings();
}
});
- updateLegacyStorageOpSticky();
// For now, simply clone property when it changes
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
mContext.getMainExecutor(), (properties) -> {
@@ -1836,13 +1835,6 @@
}
}
- private void updateLegacyStorageOpSticky() {
- final boolean propertyValue = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
- "legacy_storage_op_sticky", true);
- SystemProperties.set(PROP_LEGACY_OP_STICKY, propertyValue ? "true" : "false");
- }
-
private void start() {
connectStoraged();
connectVold();
@@ -4442,6 +4434,9 @@
pw.println("Isolated storage, remote feature flag: "
+ Settings.Global.getInt(cr, Settings.Global.ISOLATED_STORAGE_REMOTE, 0));
pw.println("Isolated storage, resolved: " + StorageManager.hasIsolatedStorage());
+ pw.println("Forced scoped storage app list: "
+ + DeviceConfig.getProperty(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+ PROP_FORCED_SCOPED_STORAGE_WHITELIST));
}
synchronized (mObbMounts) {
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index f42e32d..35936ba 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -337,6 +337,8 @@
mWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
mWindowManager = LocalServices.getService(WindowManagerInternal.class);
mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
+ TwilightManager twilightManager = getLocalService(TwilightManager.class);
+ if (twilightManager != null) mTwilightManager = twilightManager;
mLocalPowerManager =
LocalServices.getService(PowerManagerInternal.class);
initPowerSave();
@@ -381,7 +383,6 @@
com.android.internal.R.bool.config_enableCarDockHomeLaunch);
mUiModeLocked = res.getBoolean(com.android.internal.R.bool.config_lockUiMode);
mNightModeLocked = res.getBoolean(com.android.internal.R.bool.config_lockDayNightMode);
- mTwilightManager = getLocalService(TwilightManager.class);
final PackageManager pm = context.getPackageManager();
mTelevision = pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION)
|| pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
@@ -391,6 +392,8 @@
// Update the initial, static configurations.
SystemServerInitThreadPool.submit(() -> {
synchronized (mLock) {
+ TwilightManager twilightManager = getLocalService(TwilightManager.class);
+ if (twilightManager != null) mTwilightManager = twilightManager;
updateNightModeFromSettingsLocked(context, res, UserHandle.getCallingUserId());
updateSystemProperties();
}
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index f9d204f..43e3a04 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -45,6 +45,7 @@
import java.io.FileOutputStream;
import java.io.FileReader;
+import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -114,6 +115,14 @@
}
private PropertyChangedCallbackForTest mTestCallback;
+ // This interface is for functions related to the Process object that need a different
+ // implementation in the tests as we are not creating real processes when testing compaction.
+ @VisibleForTesting
+ interface ProcessDependencies {
+ long[] getRss(int pid);
+ void performCompaction(String action, int pid) throws IOException;
+ }
+
// Handler constants.
static final int COMPACT_PROCESS_SOME = 1;
static final int COMPACT_PROCESS_FULL = 2;
@@ -215,13 +224,16 @@
@VisibleForTesting final Set<Integer> mProcStateThrottle;
// Handler on which compaction runs.
- private Handler mCompactionHandler;
+ @VisibleForTesting
+ Handler mCompactionHandler;
private Handler mFreezeHandler;
// Maps process ID to last compaction statistics for processes that we've fully compacted. Used
// when evaluating throttles that we only consider for "full" compaction, so we don't store
- // data for "some" compactions.
- private Map<Integer, LastCompactionStats> mLastCompactionStats =
+ // data for "some" compactions. Uses LinkedHashMap to ensure insertion order is kept and
+ // facilitate removal of the oldest entry.
+ @VisibleForTesting
+ LinkedHashMap<Integer, LastCompactionStats> mLastCompactionStats =
new LinkedHashMap<Integer, LastCompactionStats>() {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
@@ -233,17 +245,20 @@
private int mFullCompactionCount;
private int mPersistentCompactionCount;
private int mBfgsCompactionCount;
+ private final ProcessDependencies mProcessDependencies;
public CachedAppOptimizer(ActivityManagerService am) {
- mAm = am;
- mCachedAppOptimizerThread = new ServiceThread("CachedAppOptimizerThread",
- THREAD_PRIORITY_FOREGROUND, true);
- mProcStateThrottle = new HashSet<>();
+ this(am, null, new DefaultProcessDependencies());
}
@VisibleForTesting
- CachedAppOptimizer(ActivityManagerService am, PropertyChangedCallbackForTest callback) {
- this(am);
+ CachedAppOptimizer(ActivityManagerService am, PropertyChangedCallbackForTest callback,
+ ProcessDependencies processDependencies) {
+ mAm = am;
+ mCachedAppOptimizerThread = new ServiceThread("CachedAppOptimizerThread",
+ THREAD_PRIORITY_FOREGROUND, true);
+ mProcStateThrottle = new HashSet<>();
+ mProcessDependencies = processDependencies;
mTestCallback = callback;
}
@@ -659,7 +674,8 @@
}
}
- private static final class LastCompactionStats {
+ @VisibleForTesting
+ static final class LastCompactionStats {
private final long[] mRssAfterCompaction;
LastCompactionStats(long[] rss) {
@@ -712,9 +728,7 @@
lastCompactAction = proc.lastCompactAction;
lastCompactTime = proc.lastCompactTime;
- // remove rather than get so that insertion order will be updated when we
- // put the post-compaction stats back into the map.
- lastCompactionStats = mLastCompactionStats.remove(pid);
+ lastCompactionStats = mLastCompactionStats.get(pid);
}
if (pid == 0) {
@@ -806,7 +820,7 @@
return;
}
- long[] rssBefore = Process.getRss(pid);
+ long[] rssBefore = mProcessDependencies.getRss(pid);
long anonRssBefore = rssBefore[2];
if (rssBefore[0] == 0 && rssBefore[1] == 0 && rssBefore[2] == 0
@@ -863,16 +877,13 @@
default:
break;
}
-
try {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact "
+ ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full")
+ ": " + name);
long zramFreeKbBefore = Debug.getZramFreeKb();
- FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim");
- fos.write(action.getBytes());
- fos.close();
- long[] rssAfter = Process.getRss(pid);
+ mProcessDependencies.performCompaction(action, pid);
+ long[] rssAfter = mProcessDependencies.getRss(pid);
long end = SystemClock.uptimeMillis();
long time = end - start;
long zramFreeKbAfter = Debug.getZramFreeKb();
@@ -882,7 +893,6 @@
rssAfter[2] - rssBefore[2], rssAfter[3] - rssBefore[3], time,
lastCompactAction, lastCompactTime, lastOomAdj, procState,
zramFreeKbBefore, zramFreeKbAfter - zramFreeKbBefore);
-
// Note that as above not taking mPhenoTypeFlagLock here to avoid locking
// on every single compaction for a flag that will seldom change and the
// impact of reading the wrong value here is low.
@@ -894,14 +904,14 @@
lastOomAdj, ActivityManager.processStateAmToProto(procState),
zramFreeKbBefore, zramFreeKbAfter);
}
-
synchronized (mAm) {
proc.lastCompactTime = end;
proc.lastCompactAction = pendingAction;
}
-
if (action.equals(COMPACT_ACTION_FULL)
|| action.equals(COMPACT_ACTION_ANON)) {
+ // Remove entry and insert again to update insertion order.
+ mLastCompactionStats.remove(pid);
mLastCompactionStats.put(pid, new LastCompactionStats(rssAfter));
}
} catch (Exception e) {
@@ -1018,4 +1028,23 @@
}
}
}
+
+ /**
+ * Default implementation for ProcessDependencies, public vor visibility to OomAdjuster class.
+ */
+ private static final class DefaultProcessDependencies implements ProcessDependencies {
+ // Get memory RSS from process.
+ @Override
+ public long[] getRss(int pid) {
+ return Process.getRss(pid);
+ }
+
+ // Compact process.
+ @Override
+ public void performCompaction(String action, int pid) throws IOException {
+ try (FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim")) {
+ fos.write(action.getBytes());
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index fac4a1e..ea70598 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -90,7 +90,6 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
-import android.util.SparseLongArray;
import android.util.proto.ProtoOutputStream;
import com.android.internal.R;
@@ -154,6 +153,15 @@
static final int REPORT_LOCKED_BOOT_COMPLETE_MSG = 110;
static final int START_USER_SWITCH_FG_MSG = 120;
+ // Message constant to clear {@link UserJourneySession} from {@link mUserIdToUserJourneyMap} if
+ // the user journey, defined in the UserLifecycleJourneyReported atom for statsd, is not
+ // complete within {@link USER_JOURNEY_TIMEOUT}.
+ private static final int CLEAR_USER_JOURNEY_SESSION_MSG = 200;
+ // Wait time for completing the user journey. If a user journey is not complete within this
+ // time, the remaining lifecycle events for the journey would not be logged in statsd.
+ // Timeout set for 90 seconds.
+ private static final int USER_JOURNEY_TIMEOUT_MS = 90_000;
+
// UI thread message constants
static final int START_USER_SWITCH_UI_MSG = 1000;
@@ -194,14 +202,37 @@
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__START_USER;
private static final int USER_LIFECYCLE_EVENT_CREATE_USER =
FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__CREATE_USER;
+ private static final int USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED =
+ FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__USER_RUNNING_LOCKED;
+ private static final int USER_LIFECYCLE_EVENT_UNLOCKING_USER =
+ FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNLOCKING_USER;
+ private static final int USER_LIFECYCLE_EVENT_UNLOCKED_USER =
+ FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNLOCKED_USER;
@IntDef(prefix = { "USER_LIFECYCLE_EVENT" }, value = {
USER_LIFECYCLE_EVENT_UNKNOWN,
USER_LIFECYCLE_EVENT_SWITCH_USER,
USER_LIFECYCLE_EVENT_START_USER,
USER_LIFECYCLE_EVENT_CREATE_USER,
+ USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
+ USER_LIFECYCLE_EVENT_UNLOCKING_USER,
+ USER_LIFECYCLE_EVENT_UNLOCKED_USER,
})
@interface UserLifecycleEvent {}
+ // User lifecyle event state, defined in the UserLifecycleEventOccurred atom for statsd
+ private static final int USER_LIFECYCLE_EVENT_STATE_BEGIN =
+ FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__BEGIN;
+ private static final int USER_LIFECYCLE_EVENT_STATE_FINISH =
+ FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__FINISH;
+ private static final int USER_LIFECYCLE_EVENT_STATE_NONE =
+ FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__NONE;
+ @IntDef(prefix = { "USER_LIFECYCLE_EVENT_STATE" }, value = {
+ USER_LIFECYCLE_EVENT_STATE_BEGIN,
+ USER_LIFECYCLE_EVENT_STATE_FINISH,
+ USER_LIFECYCLE_EVENT_STATE_NONE,
+ })
+ @interface UserLifecycleEventState {}
+
/**
* Maximum number of users we allow to be running at a time, including system user.
*
@@ -311,11 +342,11 @@
private final ArrayList<Integer> mLastActiveUsers = new ArrayList<>();
/**
- * A per-user, journey to session id map, used for statsd logging for the
+ * {@link UserIdInt} to {@link UserJourneySession} mapping used for statsd logging for the
* UserLifecycleJourneyReported and UserLifecycleEventOccurred atoms.
*/
- @GuardedBy("mUserJourneyToSessionIdMap")
- private final SparseArray<SparseLongArray> mUserJourneyToSessionIdMap = new SparseArray<>();
+ @GuardedBy("mUserIdToUserJourneyMap")
+ private final SparseArray<UserJourneySession> mUserIdToUserJourneyMap = new SparseArray<>();
UserController(ActivityManagerService service) {
this(new Injector(service));
@@ -447,6 +478,8 @@
// but we might immediately step into RUNNING below if the user
// storage is already unlocked.
if (uss.setState(STATE_BOOTING, STATE_RUNNING_LOCKED)) {
+ logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
+ USER_LIFECYCLE_EVENT_STATE_NONE);
mInjector.getUserManagerInternal().setUserState(userId, uss.state);
// Do not report secondary users, runtime restarts or first boot/upgrade
if (userId == UserHandle.USER_SYSTEM
@@ -503,6 +536,8 @@
private boolean finishUserUnlocking(final UserState uss) {
final int userId = uss.mHandle.getIdentifier();
EventLog.writeEvent(EventLogTags.UC_FINISH_USER_UNLOCKING, userId);
+ logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_UNLOCKING_USER,
+ USER_LIFECYCLE_EVENT_STATE_BEGIN);
// Only keep marching forward if user is actually unlocked
if (!StorageManager.isUserKeyUnlocked(userId)) return false;
synchronized (mLock) {
@@ -2396,8 +2431,8 @@
case START_USER_SWITCH_FG_MSG:
logUserJourneyInfo(getUserInfo(getCurrentUserId()), getUserInfo(msg.arg1),
USER_JOURNEY_USER_SWITCH_FG);
- logUserLifecycleEvent(msg.arg1, USER_JOURNEY_USER_SWITCH_FG,
- USER_LIFECYCLE_EVENT_SWITCH_USER, true);
+ logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_SWITCH_USER,
+ USER_LIFECYCLE_EVENT_STATE_BEGIN);
startUserInForeground(msg.arg1);
break;
case REPORT_USER_SWITCH_MSG:
@@ -2420,14 +2455,14 @@
BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
Integer.toString(msg.arg1), msg.arg1);
logUserJourneyInfo(null, getUserInfo(msg.arg1), USER_JOURNEY_USER_START);
- logUserLifecycleEvent(msg.arg1, USER_JOURNEY_USER_START,
- USER_LIFECYCLE_EVENT_START_USER, true);
+ logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_START_USER,
+ USER_LIFECYCLE_EVENT_STATE_BEGIN);
mInjector.getSystemServiceManager().startUser(TimingsTraceAndSlog.newAsyncLog(),
msg.arg1);
- logUserLifecycleEvent(msg.arg1, USER_JOURNEY_USER_START,
- USER_LIFECYCLE_EVENT_START_USER, false);
+ logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_START_USER,
+ USER_LIFECYCLE_EVENT_STATE_FINISH);
clearSessionId(msg.arg1, USER_JOURNEY_USER_START);
break;
case USER_UNLOCK_MSG:
@@ -2437,10 +2472,17 @@
FgThread.getHandler().post(() -> {
mInjector.loadUserRecents(userId);
});
+ logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_UNLOCKING_USER,
+ USER_LIFECYCLE_EVENT_STATE_FINISH);
+ logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_UNLOCKED_USER,
+ USER_LIFECYCLE_EVENT_STATE_BEGIN);
finishUserUnlocked((UserState) msg.obj);
break;
case USER_UNLOCKED_MSG:
mInjector.getSystemServiceManager().onUserUnlocked(msg.arg1);
+ logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_UNLOCKED_USER,
+ USER_LIFECYCLE_EVENT_STATE_FINISH);
+ clearSessionId(msg.arg1);
break;
case USER_CURRENT_MSG:
mInjector.batteryStatsServiceNoteEvent(
@@ -2458,11 +2500,8 @@
case REPORT_USER_SWITCH_COMPLETE_MSG:
dispatchUserSwitchComplete(msg.arg1);
- final int currentJourney = mUserSwitchUiEnabled ? USER_JOURNEY_USER_SWITCH_UI
- : USER_JOURNEY_USER_SWITCH_FG;
- logUserLifecycleEvent(msg.arg1, currentJourney,
- USER_LIFECYCLE_EVENT_SWITCH_USER, false);
- clearSessionId(msg.arg1, currentJourney);
+ logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_SWITCH_USER,
+ USER_LIFECYCLE_EVENT_STATE_FINISH);
break;
case REPORT_LOCKED_BOOT_COMPLETE_MSG:
dispatchLockedBootComplete(msg.arg1);
@@ -2471,10 +2510,13 @@
final Pair<UserInfo, UserInfo> fromToUserPair = (Pair<UserInfo, UserInfo>) msg.obj;
logUserJourneyInfo(fromToUserPair.first, fromToUserPair.second,
USER_JOURNEY_USER_SWITCH_UI);
- logUserLifecycleEvent(fromToUserPair.second.id, USER_JOURNEY_USER_SWITCH_UI,
- USER_LIFECYCLE_EVENT_SWITCH_USER, true);
+ logUserLifecycleEvent(fromToUserPair.second.id, USER_LIFECYCLE_EVENT_SWITCH_USER,
+ USER_LIFECYCLE_EVENT_STATE_BEGIN);
showUserSwitchDialog(fromToUserPair);
break;
+ case CLEAR_USER_JOURNEY_SESSION_MSG:
+ clearSessionId(msg.arg1);
+ break;
}
return false;
}
@@ -2482,27 +2524,61 @@
/**
* statsd helper method for logging the start of a user journey via a UserLifecycleEventOccurred
* atom given the originating and targeting users for the journey.
- *
- * Note: these info atoms are currently logged more than once per journey since there is no
- * state associated with the user's ongoing journey - this will be updated in a later CL.
*/
private void logUserJourneyInfo(UserInfo origin, UserInfo target, @UserJourney int journey) {
final long newSessionId = ThreadLocalRandom.current().nextLong(1, Long.MAX_VALUE);
- synchronized (mUserJourneyToSessionIdMap) {
- SparseLongArray userSessions = mUserJourneyToSessionIdMap.get(target.id);
- if (userSessions == null) {
- userSessions = new SparseLongArray();
- mUserJourneyToSessionIdMap.put(target.id, userSessions);
- }
- final long oldSessionId = userSessions.get(journey);
- if (oldSessionId != INVALID_SESSION_ID) {
- // potentially an incomplete or timed-out session
+ synchronized (mUserIdToUserJourneyMap) {
+ UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(target.id);
+ if (userJourneySession != null) {
+ // TODO(b/157007231): Move this logic to a separate class/file.
+ if ((userJourneySession.mJourney == USER_JOURNEY_USER_SWITCH_UI
+ && journey == USER_JOURNEY_USER_START)
+ || (userJourneySession.mJourney == USER_JOURNEY_USER_SWITCH_FG
+ && journey == USER_JOURNEY_USER_START)) {
+ /*
+ * There is already a user switch journey, and a user start journey for the same
+ * target user received. User start journey is most likely a part of user switch
+ * journey so no need to create a new journey for user start.
+ */
+ if (DEBUG_MU) {
+ Slog.d(TAG, journey + " not logged as it is expected to be part of "
+ + userJourneySession.mJourney);
+ }
+ return;
+ }
+ /*
+ * Possible reasons for this condition to be true:
+ * - A user switch journey is received while another user switch journey is in
+ * process for the same user.
+ * - A user switch journey is received while user start journey is in process for
+ * the same user.
+ * - A user start journey is received while another user start journey is in process
+ * for the same user.
+ * In all cases potentially an incomplete, timed-out session or multiple
+ * simultaneous requests. It is not possible to keep track of multiple sessions for
+ * the same user, so previous session is abandoned.
+ */
FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED,
- oldSessionId, target.id, USER_LIFECYCLE_EVENT_UNKNOWN,
- FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__NONE);
+ userJourneySession.mSessionId, target.id, USER_LIFECYCLE_EVENT_UNKNOWN,
+ USER_LIFECYCLE_EVENT_STATE_NONE);
}
- // update session id
- userSessions.put(journey, newSessionId);
+
+ if (DEBUG_MU) {
+ Slog.d(TAG,
+ "Starting a new journey: " + journey + " with session id: " + newSessionId);
+ }
+
+ userJourneySession = new UserJourneySession(newSessionId, journey);
+ mUserIdToUserJourneyMap.put(target.id, userJourneySession);
+ /*
+ * User lifecyle journey would be complete when {@code #clearSessionId} is called after
+ * the last expected lifecycle event for the journey. It may be possible that the last
+ * event is not called, e.g., user not unlocked after user switching. In such cases user
+ * journey is cleared after {@link USER_JOURNEY_TIMEOUT}.
+ */
+ mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(CLEAR_USER_JOURNEY_SESSION_MSG,
+ target.id), USER_JOURNEY_TIMEOUT_MS);
}
FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED, newSessionId,
@@ -2511,41 +2587,65 @@
}
/**
- * statsd helper method for logging the begin or finish of the given event for the
- * UserLifecycleEventOccurred statsd atom.
- * Note: This does not clear the user's journey session id - if this event represents the end of
- * a particular journey, call {@link #clearSessionId} to indicate that the session is over.
+ * statsd helper method for logging the given event for the UserLifecycleEventOccurred statsd
+ * atom.
*/
- private void logUserLifecycleEvent(@UserIdInt int userId, @UserJourney int journey,
- @UserLifecycleEvent int event, boolean begin) {
+ private void logUserLifecycleEvent(@UserIdInt int userId, @UserLifecycleEvent int event,
+ @UserLifecycleEventState int eventState) {
final long sessionId;
- synchronized (mUserJourneyToSessionIdMap) {
- final SparseLongArray eventToSessionMap = mUserJourneyToSessionIdMap.get(userId);
- if (eventToSessionMap == null || eventToSessionMap.size() == 0) {
+ synchronized (mUserIdToUserJourneyMap) {
+ final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(userId);
+ if (userJourneySession == null || userJourneySession.mSessionId == INVALID_SESSION_ID) {
+ Slog.w(TAG, "UserLifecycleEvent " + event
+ + " received without an active userJourneySession.");
return;
}
- sessionId = eventToSessionMap.get(journey);
- if (sessionId == INVALID_SESSION_ID) {
- return;
- }
+ sessionId = userJourneySession.mSessionId;
}
FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED, sessionId, userId,
- event, begin ? FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__BEGIN
- : FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__FINISH);
+ event, eventState);
}
/**
- * Clears the user's session id associated with the given UserJourney (for statsd).
+ * Clears the {@link UserJourneySession} for a given {@link UserIdInt} and {@link UserJourney}.
*/
private void clearSessionId(@UserIdInt int userId, @UserJourney int journey) {
- synchronized (mUserJourneyToSessionIdMap) {
- if (mUserJourneyToSessionIdMap.get(userId) != null) {
- mUserJourneyToSessionIdMap.get(userId).delete(journey);
+ synchronized (mUserIdToUserJourneyMap) {
+ final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(userId);
+ if (userJourneySession != null && userJourneySession.mJourney == journey) {
+ clearSessionId(userId);
}
}
}
+ /**
+ * Clears the {@link UserJourneySession} for a given {@link UserIdInt}.
+ */
+ private void clearSessionId(@UserIdInt int userId) {
+ synchronized (mUserIdToUserJourneyMap) {
+ mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG);
+ mUserIdToUserJourneyMap.delete(userId);
+ }
+ }
+
+ /**
+ * Helper class to store user journey and session id.
+ *
+ * <p> User journey tracks a chain of user lifecycle events occurring during different user
+ * activities such as user start, user switch, and user creation.
+ */
+ // TODO(b/157007231): Move this class and user journey tracking logic to a separate file.
+ private static class UserJourneySession {
+ final long mSessionId;
+ @UserJourney final int mJourney;
+
+ UserJourneySession(long sessionId, @UserJourney int journey) {
+ mJourney = journey;
+ mSessionId = sessionId;
+ }
+ }
+
private static class UserProgressListener extends IProgressListener.Stub {
private volatile long mUnlockStarted;
@Override
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 98d662a..78e06a3 100755
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -955,6 +955,8 @@
mMicMuteFromSystemCached = mAudioSystem.isMicrophoneMuted();
setMicMuteFromSwitchInput();
+
+ initMinStreamVolumeWithoutModifyAudioSettings();
}
RoleObserver mRoleObserver;
@@ -1308,7 +1310,7 @@
mStreamStates[streamType].setIndex(
mStreamStates[mStreamVolumeAlias[streamType]]
.getIndex(AudioSystem.DEVICE_OUT_DEFAULT),
- device, caller);
+ device, caller, true /*hasModifyAudioSettings*/);
}
mStreamStates[streamType].checkFixedVolumeDevices();
}
@@ -1873,13 +1875,16 @@
direction, 0 /*ignored*/,
extVolCtlr, 0 /*delay*/);
} else {
+ final boolean hasModifyAudioSettings =
+ mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS)
+ == PackageManager.PERMISSION_GRANTED;
adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage,
- caller, Binder.getCallingUid());
+ caller, Binder.getCallingUid(), hasModifyAudioSettings);
}
}
private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
- String callingPackage, String caller, int uid) {
+ String callingPackage, String caller, int uid, boolean hasModifyAudioSettings) {
if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream=" + suggestedStreamType
+ ", flags=" + flags + ", caller=" + caller
+ ", volControlStream=" + mVolumeControlStream
@@ -1933,10 +1938,12 @@
if (DEBUG_VOL) Log.d(TAG, "Volume controller suppressed adjustment");
}
- adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid);
+ adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid,
+ hasModifyAudioSettings);
}
- /** @see AudioManager#adjustStreamVolume(int, int, int) */
+ /** @see AudioManager#adjustStreamVolume(int, int, int)
+ * Part of service interface, check permissions here */
public void adjustStreamVolume(int streamType, int direction, int flags,
String callingPackage) {
if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) {
@@ -1944,14 +1951,17 @@
+ "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage);
return;
}
+ final boolean hasModifyAudioSettings =
+ mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS)
+ == PackageManager.PERMISSION_GRANTED;
sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,
direction/*val1*/, flags/*val2*/, callingPackage));
adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage,
- Binder.getCallingUid());
+ Binder.getCallingUid(), hasModifyAudioSettings);
}
protected void adjustStreamVolume(int streamType, int direction, int flags,
- String callingPackage, String caller, int uid) {
+ String callingPackage, String caller, int uid, boolean hasModifyAudioSettings) {
if (mUseFixedVolume) {
return;
}
@@ -2105,7 +2115,8 @@
Log.e(TAG, "adjustStreamVolume() safe volume index = " + oldIndex);
mVolumeController.postDisplaySafeVolumeWarning(flags);
} else if (!isFullVolumeDevice(device)
- && (streamState.adjustIndex(direction * step, device, caller)
+ && (streamState.adjustIndex(direction * step, device, caller,
+ hasModifyAudioSettings)
|| streamState.mIsMuted)) {
// Post message to set system volume (it in turn will post a
// message to persist).
@@ -2327,9 +2338,9 @@
}
private void onSetStreamVolume(int streamType, int index, int flags, int device,
- String caller) {
+ String caller, boolean hasModifyAudioSettings) {
final int stream = mStreamVolumeAlias[streamType];
- setStreamVolumeInt(stream, index, device, false, caller);
+ setStreamVolumeInt(stream, index, device, false, caller, hasModifyAudioSettings);
// setting volume on ui sounds stream type also controls silent mode
if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
(stream == getUiSoundsStreamType())) {
@@ -2377,7 +2388,7 @@
continue;
}
setStreamVolume(groupedStream, index, flags, callingPackage, callingPackage,
- Binder.getCallingUid());
+ Binder.getCallingUid(), true /*hasModifyAudioSettings*/);
}
}
@@ -2419,7 +2430,8 @@
return AudioSystem.getMinVolumeIndexForAttributes(attr);
}
- /** @see AudioManager#setStreamVolume(int, int, int) */
+ /** @see AudioManager#setStreamVolume(int, int, int)
+ * Part of service interface, check permissions here */
public void setStreamVolume(int streamType, int index, int flags, String callingPackage) {
if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) {
Log.w(TAG, "Trying to call setStreamVolume() for a11y without"
@@ -2442,10 +2454,13 @@
+ " MODIFY_AUDIO_ROUTING callingPackage=" + callingPackage);
return;
}
+ final boolean hasModifyAudioSettings =
+ mContext.checkCallingOrSelfPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS)
+ == PackageManager.PERMISSION_GRANTED;
sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
index/*val1*/, flags/*val2*/, callingPackage));
setStreamVolume(streamType, index, flags, callingPackage, callingPackage,
- Binder.getCallingUid());
+ Binder.getCallingUid(), hasModifyAudioSettings);
}
private boolean canChangeAccessibilityVolume() {
@@ -2569,7 +2584,7 @@
}
private void setStreamVolume(int streamType, int index, int flags, String callingPackage,
- String caller, int uid) {
+ String caller, int uid, boolean hasModifyAudioSettings) {
if (DEBUG_VOL) {
Log.d(TAG, "setStreamVolume(stream=" + streamType+", index=" + index
+ ", calling=" + callingPackage + ")");
@@ -2660,7 +2675,7 @@
mPendingVolumeCommand = new StreamVolumeCommand(
streamType, index, flags, device);
} else {
- onSetStreamVolume(streamType, index, flags, device, caller);
+ onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings);
index = mStreamStates[streamType].getIndex(device);
}
}
@@ -2872,19 +2887,22 @@
* @param index Desired volume index of the stream
* @param device the device whose volume must be changed
* @param force If true, set the volume even if the desired volume is same
+ * @param caller
+ * @param hasModifyAudioSettings true if the caller is granted MODIFY_AUDIO_SETTINGS or
+ * MODIFY_AUDIO_ROUTING permission
* as the current volume.
*/
private void setStreamVolumeInt(int streamType,
int index,
int device,
boolean force,
- String caller) {
+ String caller, boolean hasModifyAudioSettings) {
if (isFullVolumeDevice(device)) {
return;
}
VolumeStreamState streamState = mStreamStates[streamType];
- if (streamState.setIndex(index, device, caller) || force) {
+ if (streamState.setIndex(index, device, caller, hasModifyAudioSettings) || force) {
// Post message to set system volume (it in turn will post a message
// to persist).
sendMsg(mAudioHandler,
@@ -3417,7 +3435,7 @@
int device = vss.mIndexMap.keyAt(i);
int value = vss.mIndexMap.valueAt(i);
if (value == 0) {
- vss.setIndex(10, device, TAG);
+ vss.setIndex(10, device, TAG, true /*hasModifyAudioSettings*/);
}
}
// Persist volume for stream ring when it is changed here
@@ -3762,7 +3780,8 @@
int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
int device = getDeviceForStream(streamType);
int index = mStreamStates[mStreamVolumeAlias[streamType]].getIndex(device);
- setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, true, caller);
+ setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, true, caller,
+ true /*hasModifyAudioSettings*/);
updateStreamVolumeAlias(true /*updateVolumes*/, caller);
@@ -4671,6 +4690,44 @@
return false;
}
+ /**
+ * Minimum attenuation that can be set for alarms over speaker by an application that
+ * doesn't have the MODIFY_AUDIO_SETTINGS permission.
+ */
+ protected static final float MIN_ALARM_ATTENUATION_NON_PRIVILEGED_DB = -36.0f;
+
+ /**
+ * Configures the VolumeStreamState instances for minimum stream index that can be accessed
+ * without MODIFY_AUDIO_SETTINGS permission.
+ * Can only be done successfully once audio policy has finished reading its configuration files
+ * for the volume curves. If not, getStreamVolumeDB will return NaN, and the min value will
+ * remain at the stream min index value.
+ */
+ protected void initMinStreamVolumeWithoutModifyAudioSettings() {
+ int idx;
+ int deviceForAlarm = AudioSystem.DEVICE_OUT_SPEAKER_SAFE;
+ if (Float.isNaN(AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_ALARM,
+ MIN_STREAM_VOLUME[AudioSystem.STREAM_ALARM], deviceForAlarm))) {
+ deviceForAlarm = AudioSystem.DEVICE_OUT_SPEAKER;
+ }
+ for (idx = MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM];
+ idx >= MIN_STREAM_VOLUME[AudioSystem.STREAM_ALARM]; idx--) {
+ if (AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_ALARM, idx, deviceForAlarm)
+ < MIN_ALARM_ATTENUATION_NON_PRIVILEGED_DB) {
+ break;
+ }
+ }
+ final int safeIndex = idx <= MIN_STREAM_VOLUME[AudioSystem.STREAM_ALARM]
+ ? MIN_STREAM_VOLUME[AudioSystem.STREAM_ALARM]
+ : Math.min(idx + 1, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]);
+ // update the VolumeStreamState for STREAM_ALARM and its aliases
+ for (int stream : mStreamVolumeAlias) {
+ if (mStreamVolumeAlias[stream] == AudioSystem.STREAM_ALARM) {
+ mStreamStates[stream].updateNoPermMinIndex(safeIndex);
+ }
+ }
+ }
+
/** only public for mocking/spying, do not call outside of AudioService */
@VisibleForTesting
public int getDeviceForStream(int stream) {
@@ -5335,6 +5392,8 @@
private class VolumeStreamState {
private final int mStreamType;
private int mIndexMin;
+ // min index when user doesn't have permission to change audio settings
+ private int mIndexMinNoPerm;
private int mIndexMax;
private boolean mIsMuted;
@@ -5376,6 +5435,7 @@
mStreamType = streamType;
mIndexMin = MIN_STREAM_VOLUME[streamType] * 10;
+ mIndexMinNoPerm = mIndexMin; // may be overwritten later in updateNoPermMinIndex()
mIndexMax = MAX_STREAM_VOLUME[streamType] * 10;
AudioSystem.initStreamVolume(streamType, mIndexMin / 10, mIndexMax / 10);
@@ -5386,6 +5446,18 @@
mStreamDevicesChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mStreamType);
}
+ /**
+ * Update the minimum index that can be used without MODIFY_AUDIO_SETTINGS permission
+ * @param index minimum index expressed in "UI units", i.e. no 10x factor
+ */
+ public void updateNoPermMinIndex(int index) {
+ mIndexMinNoPerm = index * 10;
+ if (mIndexMinNoPerm < mIndexMin) {
+ Log.e(TAG, "Invalid mIndexMinNoPerm for stream " + mStreamType);
+ mIndexMinNoPerm = mIndexMin;
+ }
+ }
+
public int observeDevicesForStream_syncVSS(boolean checkOthers) {
if (!mSystemServer.isPrivileged()) {
return AudioSystem.DEVICE_NONE;
@@ -5467,7 +5539,8 @@
continue;
}
- mIndexMap.put(device, getValidIndex(10 * index));
+ mIndexMap.put(device, getValidIndex(10 * index,
+ true /*hasModifyAudioSettings*/));
}
}
}
@@ -5555,17 +5628,20 @@
}
}
- public boolean adjustIndex(int deltaIndex, int device, String caller) {
- return setIndex(getIndex(device) + deltaIndex, device, caller);
+ public boolean adjustIndex(int deltaIndex, int device, String caller,
+ boolean hasModifyAudioSettings) {
+ return setIndex(getIndex(device) + deltaIndex, device, caller,
+ hasModifyAudioSettings);
}
- public boolean setIndex(int index, int device, String caller) {
+ public boolean setIndex(int index, int device, String caller,
+ boolean hasModifyAudioSettings) {
boolean changed;
int oldIndex;
synchronized (mSettingsLock) {
synchronized (VolumeStreamState.class) {
oldIndex = getIndex(device);
- index = getValidIndex(index);
+ index = getValidIndex(index, hasModifyAudioSettings);
if ((mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED) && mCameraSoundForced) {
index = mIndexMax;
}
@@ -5585,10 +5661,12 @@
mStreamVolumeAlias[streamType] == mStreamType &&
(changed || !aliasStreamState.hasIndexForDevice(device))) {
final int scaledIndex = rescaleIndex(index, mStreamType, streamType);
- aliasStreamState.setIndex(scaledIndex, device, caller);
+ aliasStreamState.setIndex(scaledIndex, device, caller,
+ hasModifyAudioSettings);
if (isCurrentDevice) {
aliasStreamState.setIndex(scaledIndex,
- getDeviceForStream(streamType), caller);
+ getDeviceForStream(streamType), caller,
+ hasModifyAudioSettings);
}
}
}
@@ -5678,7 +5756,7 @@
index = srcMap.valueAt(i);
index = rescaleIndex(index, srcStreamType, mStreamType);
- setIndex(index, device, caller);
+ setIndex(index, device, caller, true /*hasModifyAudioSettings*/);
}
}
@@ -5745,9 +5823,10 @@
}
}
- private int getValidIndex(int index) {
- if (index < mIndexMin) {
- return mIndexMin;
+ private int getValidIndex(int index, boolean hasModifyAudioSettings) {
+ final int indexMin = hasModifyAudioSettings ? mIndexMin : mIndexMinNoPerm;
+ if (index < indexMin) {
+ return indexMin;
} else if (mUseFixedVolume || index > mIndexMax) {
return mIndexMax;
}
@@ -5759,7 +5838,13 @@
pw.print(" Muted: ");
pw.println(mIsMuted);
pw.print(" Min: ");
- pw.println((mIndexMin + 5) / 10);
+ pw.print((mIndexMin + 5) / 10);
+ if (mIndexMin != mIndexMinNoPerm) {
+ pw.print(" w/o perm:");
+ pw.println((mIndexMinNoPerm + 5) / 10);
+ } else {
+ pw.println();
+ }
pw.print(" Max: ");
pw.println((mIndexMax + 5) / 10);
pw.print(" streamVolume:"); pw.println(getStreamVolume(mStreamType));
@@ -5880,7 +5965,9 @@
final VolumeStreamState streamState = mStreamStates[update.mStreamType];
if (update.hasVolumeIndex()) {
final int index = update.getVolumeIndex();
- streamState.setIndex(index, update.mDevice, update.mCaller);
+ streamState.setIndex(index, update.mDevice, update.mCaller,
+ // trusted as index is always validated before message is posted
+ true /*hasModifyAudioSettings*/);
sVolumeLogger.log(new AudioEventLogger.StringEvent(update.mCaller + " dev:0x"
+ Integer.toHexString(update.mDevice) + " volIdx:" + index));
} else {
@@ -6823,7 +6910,8 @@
for (int device : devices) {
int index = streamState.getIndex(device);
if (index > safeMediaVolumeIndex(device)) {
- streamState.setIndex(safeMediaVolumeIndex(device), device, caller);
+ streamState.setIndex(safeMediaVolumeIndex(device), device, caller,
+ true /*hasModifyAudioSettings*/);
sendMsg(mAudioHandler,
MSG_SET_DEVICE_VOLUME,
SENDMSG_QUEUE,
@@ -6857,7 +6945,7 @@
mPendingVolumeCommand.mIndex,
mPendingVolumeCommand.mFlags,
mPendingVolumeCommand.mDevice,
- callingPackage);
+ callingPackage, true /*hasModifyAudioSettings*/);
mPendingVolumeCommand = null;
}
}
@@ -7465,29 +7553,39 @@
@Override
public void adjustSuggestedStreamVolumeForUid(int streamType, int direction, int flags,
- String callingPackage, int uid) {
+ String callingPackage, int uid, int pid) {
+ final boolean hasModifyAudioSettings =
+ mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid)
+ == PackageManager.PERMISSION_GRANTED;
// direction and stream type swap here because the public
// adjustSuggested has a different order than the other methods.
adjustSuggestedStreamVolume(direction, streamType, flags, callingPackage,
- callingPackage, uid);
+ callingPackage, uid, hasModifyAudioSettings);
}
@Override
public void adjustStreamVolumeForUid(int streamType, int direction, int flags,
- String callingPackage, int uid) {
+ String callingPackage, int uid, int pid) {
if (direction != AudioManager.ADJUST_SAME) {
sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_VOL_UID, streamType,
direction/*val1*/, flags/*val2*/, new StringBuilder(callingPackage)
.append(" uid:").append(uid).toString()));
}
+ final boolean hasModifyAudioSettings =
+ mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid)
+ == PackageManager.PERMISSION_GRANTED;
adjustStreamVolume(streamType, direction, flags, callingPackage,
- callingPackage, uid);
+ callingPackage, uid, hasModifyAudioSettings);
}
@Override
public void setStreamVolumeForUid(int streamType, int direction, int flags,
- String callingPackage, int uid) {
- setStreamVolume(streamType, direction, flags, callingPackage, callingPackage, uid);
+ String callingPackage, int uid, int pid) {
+ final boolean hasModifyAudioSettings =
+ mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid)
+ == PackageManager.PERMISSION_GRANTED;
+ setStreamVolume(streamType, direction, flags, callingPackage, callingPackage, uid,
+ hasModifyAudioSettings);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/AuthenticationClient.java
index 5d334c2..edc8f15 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationClient.java
@@ -84,11 +84,8 @@
@Override
public void binderDied() {
- super.binderDied();
- // When the binder dies, we should stop the client. This probably belongs in
- // ClientMonitor's binderDied(), but testing all the cases would be tricky.
- // AuthenticationClient is the most user-visible case.
- stop(false /* initiatedByClient */);
+ final boolean clearListener = !isBiometricPrompt();
+ binderDiedInternal(clearListener);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 7e28e94..4ddfe1b 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -113,6 +113,7 @@
private static final int MSG_ON_AUTHENTICATION_TIMED_OUT = 11;
private static final int MSG_ON_DEVICE_CREDENTIAL_PRESSED = 12;
private static final int MSG_ON_SYSTEM_EVENT = 13;
+ private static final int MSG_CLIENT_DIED = 14;
/**
* Authentication either just called and we have not transitioned to the CALLED state, or
@@ -151,8 +152,13 @@
* Device credential in AuthController is showing
*/
static final int STATE_SHOWING_DEVICE_CREDENTIAL = 8;
+ /**
+ * The client binder died, and sensors were authenticating at the time. Cancel has been
+ * requested and we're waiting for the HAL(s) to send ERROR_CANCELED.
+ */
+ static final int STATE_CLIENT_DIED_CANCELLING = 9;
- final class AuthSession {
+ final class AuthSession implements IBinder.DeathRecipient {
// Map of Authenticator/Cookie pairs. We expect to receive the cookies back from
// <Biometric>Services before we can start authenticating. Pairs that have been returned
// are moved to mModalitiesMatched.
@@ -211,7 +217,14 @@
mCallingUserId = callingUserId;
mModality = modality;
mRequireConfirmation = requireConfirmation;
+
Slog.d(TAG, "New AuthSession, mSysUiSessionId: " + mSysUiSessionId);
+
+ try {
+ mClientReceiver.asBinder().linkToDeath(this, 0 /* flags */);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Unable to link to death");
+ }
}
boolean isCrypto() {
@@ -231,6 +244,12 @@
boolean isAllowDeviceCredential() {
return Utils.isCredentialRequested(mBundle);
}
+
+ @Override
+ public void binderDied() {
+ Slog.e(TAG, "Binder died, sysUiSessionId: " + mSysUiSessionId);
+ mHandler.obtainMessage(MSG_CLIENT_DIED).sendToTarget();
+ }
}
private final Injector mInjector;
@@ -370,6 +389,11 @@
break;
}
+ case MSG_CLIENT_DIED: {
+ handleClientDied();
+ break;
+ }
+
default:
Slog.e(TAG, "Unknown message: " + msg);
break;
@@ -1391,6 +1415,7 @@
}
private void handleOnError(int cookie, int modality, int error, int vendorCode) {
+
Slog.d(TAG, "handleOnError: " + error + " cookie: " + cookie);
// Errors can either be from the current auth session or the pending auth session.
// The pending auth session may receive errors such as ERROR_LOCKOUT before
@@ -1431,6 +1456,9 @@
} else if (mCurrentAuthSession.mState == STATE_SHOWING_DEVICE_CREDENTIAL) {
Slog.d(TAG, "Biometric canceled, ignoring from state: "
+ mCurrentAuthSession.mState);
+ } else if (mCurrentAuthSession.mState == STATE_CLIENT_DIED_CANCELLING) {
+ mStatusBarService.hideAuthenticationDialog();
+ mCurrentAuthSession = null;
} else {
Slog.e(TAG, "Impossible session error state: "
+ mCurrentAuthSession.mState);
@@ -1622,6 +1650,36 @@
}
}
+ private void handleClientDied() {
+ if (mCurrentAuthSession == null) {
+ Slog.e(TAG, "Auth session null");
+ return;
+ }
+
+ Slog.e(TAG, "SysUiSessionId: " + mCurrentAuthSession.mSysUiSessionId
+ + " State: " + mCurrentAuthSession.mState);
+
+ try {
+ // Check if any sensors are authenticating. If so, need to cancel them. When
+ // ERROR_CANCELED is received from the HAL, we hide the dialog and cleanup the session.
+ if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) {
+ mCurrentAuthSession.mState = STATE_CLIENT_DIED_CANCELLING;
+ cancelInternal(mCurrentAuthSession.mToken,
+ mCurrentAuthSession.mOpPackageName,
+ mCurrentAuthSession.mCallingUid,
+ mCurrentAuthSession.mCallingPid,
+ mCurrentAuthSession.mCallingUserId,
+ false /* fromClient */);
+ } else {
+ // If the sensors are not authenticating, set the auth session to null.
+ mStatusBarService.hideAuthenticationDialog();
+ mCurrentAuthSession = null;
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception: " + e);
+ }
+ }
+
/**
* Invoked when each service has notified that its client is ready to be started. When
* all biometrics are ready, this invokes the SystemUI dialog through StatusBar.
@@ -1822,11 +1880,11 @@
void cancelInternal(IBinder token, String opPackageName, int callingUid, int callingPid,
int callingUserId, boolean fromClient) {
-
if (mCurrentAuthSession == null) {
Slog.w(TAG, "Skipping cancelInternal");
return;
- } else if (mCurrentAuthSession.mState != STATE_AUTH_STARTED) {
+ } else if (mCurrentAuthSession.mState != STATE_AUTH_STARTED
+ && mCurrentAuthSession.mState != STATE_CLIENT_DIED_CANCELLING) {
Slog.w(TAG, "Skipping cancelInternal, state: " + mCurrentAuthSession.mState);
return;
}
diff --git a/services/core/java/com/android/server/biometrics/ClientMonitor.java b/services/core/java/com/android/server/biometrics/ClientMonitor.java
index 942e050..b029695 100644
--- a/services/core/java/com/android/server/biometrics/ClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/ClientMonitor.java
@@ -233,11 +233,17 @@
@Override
public void binderDied() {
+ binderDiedInternal(true /* clearListener */);
+ }
+
+ void binderDiedInternal(boolean clearListener) {
// If the current client dies we should cancel the current operation.
Slog.e(getLogTag(), "Binder died, cancelling client");
stop(false /* initiatedByClient */);
mToken = null;
- mListener = null;
+ if (clearListener) {
+ mListener = null;
+ }
}
@Override
diff --git a/services/core/java/com/android/server/compat/TEST_MAPPING b/services/core/java/com/android/server/compat/TEST_MAPPING
index 0c30c79..bc1c728 100644
--- a/services/core/java/com/android/server/compat/TEST_MAPPING
+++ b/services/core/java/com/android/server/compat/TEST_MAPPING
@@ -15,7 +15,7 @@
},
// CTS tests
{
- "name": "CtsAppCompatHostTestCases#"
+ "name": "CtsAppCompatHostTestCases"
}
]
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 093e906..f3c7874 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -21,6 +21,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
import static android.net.RouteInfo.RTN_THROW;
import static android.net.RouteInfo.RTN_UNREACHABLE;
@@ -317,8 +318,7 @@
*
* @param defaultNetwork underlying network for VPNs following platform's default
*/
- public synchronized NetworkCapabilities updateCapabilities(
- @Nullable Network defaultNetwork) {
+ public synchronized NetworkCapabilities updateCapabilities(@Nullable Network defaultNetwork) {
if (mConfig == null) {
// VPN is not running.
return null;
@@ -350,11 +350,10 @@
int[] transportTypes = new int[] { NetworkCapabilities.TRANSPORT_VPN };
int downKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
int upKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
- // VPN's meteredness is OR'd with isAlwaysMetered and meteredness of its underlying
- // networks.
- boolean metered = isAlwaysMetered;
- boolean roaming = false;
- boolean congested = false;
+ boolean metered = isAlwaysMetered; // metered if any underlying is metered, or alwaysMetered
+ boolean roaming = false; // roaming if any underlying is roaming
+ boolean congested = false; // congested if any underlying is congested
+ boolean suspended = true; // suspended if all underlying are suspended
boolean hadUnderlyingNetworks = false;
if (null != underlyingNetworks) {
@@ -367,15 +366,24 @@
transportTypes = ArrayUtils.appendInt(transportTypes, underlyingType);
}
- // When we have multiple networks, we have to assume the
- // worst-case link speed and restrictions.
+ // Merge capabilities of this underlying network. For bandwidth, assume the
+ // worst case.
downKbps = NetworkCapabilities.minBandwidth(downKbps,
underlyingCaps.getLinkDownstreamBandwidthKbps());
upKbps = NetworkCapabilities.minBandwidth(upKbps,
underlyingCaps.getLinkUpstreamBandwidthKbps());
+ // If this underlying network is metered, the VPN is metered (it may cost money
+ // to send packets on this network).
metered |= !underlyingCaps.hasCapability(NET_CAPABILITY_NOT_METERED);
+ // If this underlying network is roaming, the VPN is roaming (the billing structure
+ // is different than the usual, local one).
roaming |= !underlyingCaps.hasCapability(NET_CAPABILITY_NOT_ROAMING);
+ // If this underlying network is congested, the VPN is congested (the current
+ // condition of the network affects the performance of this network).
congested |= !underlyingCaps.hasCapability(NET_CAPABILITY_NOT_CONGESTED);
+ // If this network is not suspended, the VPN is not suspended (the VPN
+ // is able to transfer some data).
+ suspended &= !underlyingCaps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED);
}
}
if (!hadUnderlyingNetworks) {
@@ -383,6 +391,7 @@
metered = true;
roaming = false;
congested = false;
+ suspended = false;
}
caps.setTransportTypes(transportTypes);
@@ -391,6 +400,7 @@
caps.setCapability(NET_CAPABILITY_NOT_METERED, !metered);
caps.setCapability(NET_CAPABILITY_NOT_ROAMING, !roaming);
caps.setCapability(NET_CAPABILITY_NOT_CONGESTED, !congested);
+ caps.setCapability(NET_CAPABILITY_NOT_SUSPENDED, !suspended);
}
/**
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index d7bd794..c65800a 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -808,14 +808,11 @@
userRecord.mHandler, manager));
for (RouterRecord routerRecord : userRecord.mRouterRecords) {
- // TODO: Do not use notifyPreferredFeaturesChangedToManagers since it updates all
- // managers. Instead, Notify only to the manager that is currently being registered.
-
// TODO: UserRecord <-> routerRecord, why do they reference each other?
// How about removing mUserRecord from routerRecord?
routerRecord.mUserRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::notifyPreferredFeaturesChangedToManagers,
- routerRecord.mUserRecord.mHandler, routerRecord));
+ obtainMessage(UserHandler::notifyPreferredFeaturesChangedToManager,
+ routerRecord.mUserRecord.mHandler, routerRecord, manager));
}
}
@@ -1928,6 +1925,17 @@
}
}
+ private void notifyPreferredFeaturesChangedToManager(@NonNull RouterRecord routerRecord,
+ @NonNull IMediaRouter2Manager manager) {
+ try {
+ manager.notifyPreferredFeaturesChanged(routerRecord.mPackageName,
+ routerRecord.mDiscoveryPreference.getPreferredFeatures());
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to notify preferred features changed."
+ + " Manager probably died.", ex);
+ }
+ }
+
private void notifyPreferredFeaturesChangedToManagers(@NonNull RouterRecord routerRecord) {
MediaRouter2ServiceImpl service = mServiceRef.get();
if (service == null) {
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 67f9782..02b7582 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -328,7 +328,7 @@
public void run() {
try {
mAudioManagerInternal.setStreamVolumeForUid(stream, volumeValue, flags,
- opPackageName, uid);
+ opPackageName, uid, pid);
} catch (IllegalArgumentException | SecurityException e) {
Log.e(TAG, "Cannot set volume: stream=" + stream + ", value=" + volumeValue
+ ", flags=" + flags, e);
@@ -501,12 +501,15 @@
// Must use opPackageName for adjusting volumes with UID.
final String opPackageName;
final int uid;
+ final int pid;
if (asSystemService) {
opPackageName = mContext.getOpPackageName();
uid = Process.SYSTEM_UID;
+ pid = Process.myPid();
} else {
opPackageName = callingOpPackageName;
uid = callingUid;
+ pid = callingPid;
}
mHandler.post(new Runnable() {
@Override
@@ -515,15 +518,15 @@
if (useSuggested) {
if (AudioSystem.isStreamActive(stream, 0)) {
mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(stream,
- direction, flags, opPackageName, uid);
+ direction, flags, opPackageName, uid, pid);
} else {
mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(
AudioManager.USE_DEFAULT_STREAM_TYPE, direction,
- flags | previousFlagPlaySound, opPackageName, uid);
+ flags | previousFlagPlaySound, opPackageName, uid, pid);
}
} else {
mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags,
- opPackageName, uid);
+ opPackageName, uid, pid);
}
} catch (IllegalArgumentException | SecurityException e) {
Log.e(TAG, "Cannot adjust volume: direction=" + direction + ", stream="
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 84ec440..f360a4a 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -342,13 +342,10 @@
updateUser();
}
- // Called when the user with the userId is removed.
@Override
- public void onStopUser(int userId) {
- if (DEBUG) Log.d(TAG, "onStopUser: " + userId);
+ public void onCleanupUser(int userId) {
+ if (DEBUG) Log.d(TAG, "onCleanupUser: " + userId);
synchronized (mLock) {
- // TODO: Also handle removing user in updateUser() because adding/switching user is
- // handled in updateUser().
FullUserRecord user = getFullUserRecordLocked(userId);
if (user != null) {
if (user.mFullUserId == userId) {
@@ -2107,16 +2104,19 @@
public void run() {
final String callingOpPackageName;
final int callingUid;
+ final int callingPid;
if (asSystemService) {
callingOpPackageName = mContext.getOpPackageName();
callingUid = Process.myUid();
+ callingPid = Process.myPid();
} else {
callingOpPackageName = opPackageName;
callingUid = uid;
+ callingPid = pid;
}
try {
mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(suggestedStream,
- direction, flags, callingOpPackageName, callingUid);
+ direction, flags, callingOpPackageName, callingUid, callingPid);
} catch (SecurityException | IllegalArgumentException e) {
Log.e(TAG, "Cannot adjust volume: direction=" + direction
+ ", suggestedStream=" + suggestedStream + ", flags=" + flags
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index f9d805e..830388d 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -720,7 +720,7 @@
return false;
}
if (callingSetting == null) {
- Slog.w(TAG, "No setting found for non system uid " + callingUid);
+ Slog.wtf(TAG, "No setting found for non system uid " + callingUid);
return true;
}
final PackageSetting callingPkgSetting;
@@ -760,7 +760,7 @@
final AndroidPackage targetPkg = targetPkgSetting.pkg;
if (targetPkg == null) {
if (DEBUG_LOGGING) {
- Slog.w(TAG, "shouldFilterApplication: " + "targetPkg is null");
+ Slog.wtf(TAG, "shouldFilterApplication: " + "targetPkg is null");
}
return true;
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 1741aa7..f8278de 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -2822,8 +2822,7 @@
}
case IDataLoaderStatusListener.DATA_LOADER_UNAVAILABLE: {
// Don't fail or commit the session. Allow caller to commit again.
- sendPendingStreaming(mContext, mRemoteStatusReceiver, sessionId,
- "DataLoader unavailable");
+ sendPendingStreaming("DataLoader unavailable");
break;
}
case IDataLoaderStatusListener.DATA_LOADER_UNRECOVERABLE:
@@ -2835,8 +2834,7 @@
} catch (RemoteException e) {
// In case of streaming failure we don't want to fail or commit the session.
// Just return from this method and allow caller to commit again.
- sendPendingStreaming(mContext, mRemoteStatusReceiver, sessionId,
- e.getMessage());
+ sendPendingStreaming(e.getMessage());
}
}
};
@@ -3196,8 +3194,17 @@
}
}
- private static void sendPendingStreaming(Context context, IntentSender target, int sessionId,
- @Nullable String cause) {
+ private void sendPendingStreaming(@Nullable String cause) {
+ final IntentSender statusReceiver;
+ synchronized (mLock) {
+ statusReceiver = mRemoteStatusReceiver;
+ }
+
+ if (statusReceiver == null) {
+ Slog.e(TAG, "Missing receiver for pending streaming status.");
+ return;
+ }
+
final Intent intent = new Intent();
intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
intent.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_PENDING_STREAMING);
@@ -3208,7 +3215,7 @@
intent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, "Staging Image Not Ready");
}
try {
- target.sendIntent(context, 0, intent, null, null);
+ statusReceiver.sendIntent(mContext, 0, intent, null, null);
} catch (IntentSender.SendIntentException ignored) {
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index d73d872..23a765f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3181,7 +3181,7 @@
}
}
- final int cachedSystemApps = PackageParser.sCachedPackageReadCount.get();
+ final int cachedSystemApps = PackageCacher.sCachedPackageReadCount.get();
// Remove any shared userIDs that have no associated packages
mSettings.pruneSharedUsersLPw();
@@ -3315,7 +3315,7 @@
// This must be done last to ensure all stubs are replaced or disabled.
installSystemStubPackages(stubSystemApps, scanFlags);
- final int cachedNonSystemApps = PackageParser.sCachedPackageReadCount.get()
+ final int cachedNonSystemApps = PackageCacher.sCachedPackageReadCount.get()
- cachedSystemApps;
final long dataScanTime = SystemClock.uptimeMillis() - systemScanTime - startTime;
@@ -8604,7 +8604,7 @@
// Shared lib filtering done in generateApplicationInfoFromSettingsLPw
// and already converts to externally visible package name
ai = generateApplicationInfoFromSettingsLPw(ps.name,
- callingUid, effectiveFlags, userId);
+ effectiveFlags, callingUid, userId);
}
if (ai != null) {
list.add(ai);
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 9de34a9..a5b1bf9 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -5595,10 +5595,7 @@
userId);
} else if (packageSetting.sharedUser == null && !isUpgradeToR) {
Slog.w(TAG, "Missing permission state for package: " + packageName);
- generateFallbackPermissionsStateLpr(
- packageSetting.pkg.getRequestedPermissions(),
- packageSetting.pkg.getTargetSdkVersion(),
- packageSetting.getPermissionsState(), userId);
+ packageSetting.getPermissionsState().setMissing(true, userId);
}
}
@@ -5616,22 +5613,7 @@
userId);
} else if (!isUpgradeToR) {
Slog.w(TAG, "Missing permission state for shared user: " + sharedUserName);
- ArraySet<String> requestedPermissions = new ArraySet<>();
- int targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
- int sharedUserPackagesSize = sharedUserSetting.packages.size();
- for (int packagesI = 0; packagesI < sharedUserPackagesSize; packagesI++) {
- PackageSetting packageSetting = sharedUserSetting.packages.valueAt(
- packagesI);
- if (packageSetting == null || packageSetting.pkg == null
- || !packageSetting.getInstalled(userId)) {
- continue;
- }
- AndroidPackage pkg = packageSetting.pkg;
- requestedPermissions.addAll(pkg.getRequestedPermissions());
- targetSdkVersion = Math.min(targetSdkVersion, pkg.getTargetSdkVersion());
- }
- generateFallbackPermissionsStateLpr(requestedPermissions, targetSdkVersion,
- sharedUserSetting.getPermissionsState(), userId);
+ sharedUserSetting.getPermissionsState().setMissing(true, userId);
}
}
}
@@ -5663,30 +5645,6 @@
}
}
- private void generateFallbackPermissionsStateLpr(
- @NonNull Collection<String> requestedPermissions, int targetSdkVersion,
- @NonNull PermissionsState permissionsState, @UserIdInt int userId) {
- for (String permissionName : requestedPermissions) {
- BasePermission permission = mPermissions.getPermission(permissionName);
- if (Objects.equals(permission.getSourcePackageName(), PLATFORM_PACKAGE_NAME)
- && permission.isRuntime() && !permission.isRemoved()) {
- if (permission.isHardOrSoftRestricted() || permission.isImmutablyRestricted()) {
- permissionsState.updatePermissionFlags(permission, userId,
- PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT,
- PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT);
- }
- if (targetSdkVersion < Build.VERSION_CODES.M) {
- permissionsState.updatePermissionFlags(permission, userId,
- PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
- | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
- PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
- | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT);
- permissionsState.grantRuntimePermission(permission, userId);
- }
- }
- }
- }
-
@GuardedBy("Settings.this.mLock")
private void readLegacyStateForUserSyncLPr(int userId) {
File permissionsFile = getUserRuntimePermissionsFile(userId);
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index 0fecb631..0ebe5961 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -22,20 +22,29 @@
import android.content.pm.ShortcutInfo;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.AtomicFile;
import android.util.Slog;
+import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.pm.ShortcutService.DumpFilter;
import com.android.server.pm.ShortcutUser.PackageWithUser;
+import libcore.io.IoUtils;
+
import org.json.JSONException;
import org.json.JSONObject;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@@ -254,6 +263,53 @@
out.endTag(null, TAG_ROOT);
}
+ public static ShortcutLauncher loadFromFile(File path, ShortcutUser shortcutUser,
+ int ownerUserId, boolean fromBackup) {
+
+ final AtomicFile file = new AtomicFile(path);
+ final FileInputStream in;
+ try {
+ in = file.openRead();
+ } catch (FileNotFoundException e) {
+ if (ShortcutService.DEBUG) {
+ Slog.d(TAG, "Not found " + path);
+ }
+ return null;
+ }
+
+ try {
+ final BufferedInputStream bis = new BufferedInputStream(in);
+
+ ShortcutLauncher ret = null;
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(bis, StandardCharsets.UTF_8.name());
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ final int depth = parser.getDepth();
+
+ final String tag = parser.getName();
+ if (ShortcutService.DEBUG_LOAD) {
+ Slog.d(TAG, String.format("depth=%d type=%d name=%s", depth, type, tag));
+ }
+ if ((depth == 1) && TAG_ROOT.equals(tag)) {
+ ret = loadFromXml(parser, shortcutUser, ownerUserId, fromBackup);
+ continue;
+ }
+ ShortcutService.throwForInvalidTag(depth, tag);
+ }
+ return ret;
+ } catch (IOException | XmlPullParserException e) {
+ Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
+ return null;
+ } finally {
+ IoUtils.closeQuietly(in);
+ }
+ }
+
/**
* Load.
*/
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 71a4bb4..1642607 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -27,12 +27,15 @@
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.content.res.Resources;
+import android.graphics.drawable.Icon;
import android.os.PersistableBundle;
import android.text.format.Formatter;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.AtomicFile;
import android.util.Log;
import android.util.Slog;
+import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
@@ -43,15 +46,21 @@
import com.android.server.pm.ShortcutService.ShortcutOperation;
import com.android.server.pm.ShortcutService.Stats;
+import libcore.io.IoUtils;
+
import org.json.JSONException;
import org.json.JSONObject;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
+import java.io.BufferedInputStream;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -248,6 +257,28 @@
}
}
+ public void ensureNoBitmapIconIfShortcutIsLongLived(@NonNull List<ShortcutInfo> shortcuts) {
+ for (int i = shortcuts.size() - 1; i >= 0; i--) {
+ final ShortcutInfo si = shortcuts.get(i);
+ if (!si.isLongLived()) {
+ continue;
+ }
+ final Icon icon = si.getIcon();
+ if (icon != null && icon.getType() != Icon.TYPE_BITMAP
+ && icon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
+ continue;
+ }
+ if (icon == null && !si.hasIconFile()) {
+ continue;
+ }
+
+ // TODO: Throw IllegalArgumentException instead.
+ Slog.e(TAG, "Invalid icon type in shortcut " + si.getId() + ". Bitmaps are not allowed"
+ + " in long-lived shortcuts. Use Resource icons, or Uri-based icons instead.");
+ return; // Do not spam and return early.
+ }
+ }
+
/**
* Delete a shortcut by ID. This will *always* remove it even if it's immutable or invisible.
*/
@@ -1444,10 +1475,6 @@
// Don't adjust ranks for manifest shortcuts.
continue;
}
- if (si.isCached() && !si.isDynamic()) {
- // Don't adjust ranks for cached shortcuts that are not dynamic anymore.
- continue;
- }
// At this point, it must be dynamic.
if (!si.isDynamic()) {
s.wtf("Non-dynamic shortcut found.");
@@ -1720,6 +1747,53 @@
out.endTag(null, TAG_SHORTCUT);
}
+ public static ShortcutPackage loadFromFile(ShortcutService s, ShortcutUser shortcutUser,
+ File path, boolean fromBackup) {
+
+ final AtomicFile file = new AtomicFile(path);
+ final FileInputStream in;
+ try {
+ in = file.openRead();
+ } catch (FileNotFoundException e) {
+ if (ShortcutService.DEBUG) {
+ Slog.d(TAG, "Not found " + path);
+ }
+ return null;
+ }
+
+ try {
+ final BufferedInputStream bis = new BufferedInputStream(in);
+
+ ShortcutPackage ret = null;
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(bis, StandardCharsets.UTF_8.name());
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ final int depth = parser.getDepth();
+
+ final String tag = parser.getName();
+ if (ShortcutService.DEBUG_LOAD) {
+ Slog.d(TAG, String.format("depth=%d type=%d name=%s", depth, type, tag));
+ }
+ if ((depth == 1) && TAG_ROOT.equals(tag)) {
+ ret = loadFromXml(s, shortcutUser, parser, fromBackup);
+ continue;
+ }
+ ShortcutService.throwForInvalidTag(depth, tag);
+ }
+ return ret;
+ } catch (IOException | XmlPullParserException e) {
+ Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
+ return null;
+ } finally {
+ IoUtils.closeQuietly(in);
+ }
+ }
+
public static ShortcutPackage loadFromXml(ShortcutService s, ShortcutUser shortcutUser,
XmlPullParser parser, boolean fromBackup)
throws IOException, XmlPullParserException {
@@ -1984,7 +2058,7 @@
dynamicList.removeIf((si) -> !si.isDynamic());
final ArrayList<ShortcutInfo> manifestList = new ArrayList<>(list);
- dynamicList.removeIf((si) -> !si.isManifestShortcut());
+ manifestList.removeIf((si) -> !si.isManifestShortcut());
verifyRanksSequential(dynamicList);
verifyRanksSequential(manifestList);
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index 6d9d69e..801c6cb 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -18,8 +18,10 @@
import android.annotation.NonNull;
import android.content.pm.PackageInfo;
import android.content.pm.ShortcutInfo;
+import android.util.AtomicFile;
import android.util.Slog;
+import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.Preconditions;
import org.json.JSONException;
@@ -27,7 +29,11 @@
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
@@ -143,6 +149,31 @@
public abstract void saveToXml(@NonNull XmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException;
+ public void saveToFile(File path, boolean forBackup) {
+ final AtomicFile file = new AtomicFile(path);
+ FileOutputStream os = null;
+ try {
+ os = file.startWrite();
+ final BufferedOutputStream bos = new BufferedOutputStream(os);
+
+ // Write to XML
+ XmlSerializer itemOut = new FastXmlSerializer();
+ itemOut.setOutput(bos, StandardCharsets.UTF_8.name());
+ itemOut.startDocument(null, true);
+
+ saveToXml(itemOut, forBackup);
+
+ itemOut.endDocument();
+
+ bos.flush();
+ os.flush();
+ file.finishWrite(os);
+ } catch (XmlPullParserException | IOException e) {
+ Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
+ file.failWrite(os);
+ }
+ }
+
public JSONObject dumpCheckin(boolean clear) throws JSONException {
final JSONObject result = new JSONObject();
result.put(KEY_NAME, mPackageName);
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index e4ae007..3732b47 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -169,7 +169,7 @@
static final int DEFAULT_MAX_UPDATES_PER_INTERVAL = 10;
@VisibleForTesting
- static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 10;
+ static final int DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY = 15;
@VisibleForTesting
static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96;
@@ -730,7 +730,7 @@
ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL, DEFAULT_MAX_UPDATES_PER_INTERVAL));
mMaxShortcuts = Math.max(0, (int) parser.getLong(
- ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_APP));
+ ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY));
final int iconDimensionDp = Math.max(1, injectIsLowRamDevice()
? (int) parser.getLong(
@@ -1849,6 +1849,7 @@
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true);
+ ps.ensureNoBitmapIconIfShortcutIsLongLived(newShortcuts);
fillInDefaultActivity(newShortcuts);
@@ -1915,6 +1916,7 @@
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true);
+ ps.ensureNoBitmapIconIfShortcutIsLongLived(newShortcuts);
// For update, don't fill in the default activity. Having null activity means
// "don't update the activity" here.
@@ -2013,6 +2015,7 @@
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true);
+ ps.ensureNoBitmapIconIfShortcutIsLongLived(newShortcuts);
fillInDefaultActivity(newShortcuts);
@@ -3103,15 +3106,15 @@
}
if (doCache) {
- if (si.isDynamic() && si.isLongLived()) {
+ if (si.isLongLived()) {
si.addFlags(ShortcutInfo.FLAG_CACHED);
if (changedShortcuts == null) {
changedShortcuts = new ArrayList<>(1);
}
changedShortcuts.add(si);
} else {
- Log.w(TAG, "Only dynamic long lived shortcuts can get cached. Ignoring"
- + "shortcut " + si.getId());
+ Log.w(TAG, "Only long lived shortcuts can get cached. Ignoring id "
+ + si.getId());
}
} else {
ShortcutInfo removed = null;
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index eab3f4d..df6d321 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -21,6 +21,7 @@
import android.content.ComponentName;
import android.content.pm.ShortcutManager;
import android.metrics.LogMaker;
+import android.os.FileUtils;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.util.ArrayMap;
@@ -30,7 +31,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.Preconditions;
import com.android.server.pm.ShortcutService.DumpFilter;
import com.android.server.pm.ShortcutService.InvalidFileFormatException;
@@ -55,6 +55,9 @@
class ShortcutUser {
private static final String TAG = ShortcutService.TAG;
+ static final String DIRECTORY_PACKAGES = "packages";
+ static final String DIRECTORY_LUANCHERS = "launchers";
+
static final String TAG_ROOT = "user";
private static final String TAG_LAUNCHER = "launcher";
@@ -354,6 +357,13 @@
mService.injectBuildFingerprint());
}
+ if (!forBackup) {
+ // Since we are not handling package deletion yet, or any single package changes, just
+ // clean the directory and rewrite all the ShortcutPackageItems.
+ final File root = mService.injectUserDataPath(mUserId);
+ FileUtils.deleteContents(new File(root, DIRECTORY_PACKAGES));
+ FileUtils.deleteContents(new File(root, DIRECTORY_LUANCHERS));
+ }
// Can't use forEachPackageItem due to the checked exceptions.
{
final int size = mLaunchers.size();
@@ -371,20 +381,47 @@
out.endTag(null, TAG_ROOT);
}
- private void saveShortcutPackageItem(XmlSerializer out,
- ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException {
+ private void saveShortcutPackageItem(XmlSerializer out, ShortcutPackageItem spi,
+ boolean forBackup) throws IOException, XmlPullParserException {
if (forBackup) {
if (spi.getPackageUserId() != spi.getOwnerUserId()) {
return; // Don't save cross-user information.
}
+ spi.saveToXml(out, forBackup);
+ } else {
+ // Save each ShortcutPackageItem in a separate Xml file.
+ final File path = getShortcutPackageItemFile(spi);
+ if (ShortcutService.DEBUG) {
+ Slog.d(TAG, "Saving package item " + spi.getPackageName() + " to " + path);
+ }
+
+ path.getParentFile().mkdirs();
+ spi.saveToFile(path, forBackup);
}
- spi.saveToXml(out, forBackup);
+ }
+
+ private File getShortcutPackageItemFile(ShortcutPackageItem spi) {
+ boolean isShortcutLauncher = spi instanceof ShortcutLauncher;
+
+ final File path = new File(mService.injectUserDataPath(mUserId),
+ isShortcutLauncher ? DIRECTORY_LUANCHERS : DIRECTORY_PACKAGES);
+
+ final String fileName;
+ if (isShortcutLauncher) {
+ // Package user id and owner id can have different values for ShortcutLaunchers. Adding
+ // user Id to the file name to create a unique path. Owner id is used in the root path.
+ fileName = spi.getPackageName() + spi.getPackageUserId() + ".xml";
+ } else {
+ fileName = spi.getPackageName() + ".xml";
+ }
+
+ return new File(path, fileName);
}
public static ShortcutUser loadFromXml(ShortcutService s, XmlPullParser parser, int userId,
boolean fromBackup) throws IOException, XmlPullParserException, InvalidFileFormatException {
final ShortcutUser ret = new ShortcutUser(s, userId);
-
+ boolean readShortcutItems = false;
try {
ret.mKnownLocales = ShortcutService.parseStringAttribute(parser,
ATTR_KNOWN_LOCALES);
@@ -422,12 +459,14 @@
// Don't use addShortcut(), we don't need to save the icon.
ret.mPackages.put(shortcuts.getPackageName(), shortcuts);
+ readShortcutItems = true;
continue;
}
case ShortcutLauncher.TAG_ROOT: {
ret.addLauncher(
ShortcutLauncher.loadFromXml(parser, ret, userId, fromBackup));
+ readShortcutItems = true;
continue;
}
}
@@ -438,9 +477,44 @@
throw new ShortcutService.InvalidFileFormatException(
"Unable to parse file", e);
}
+
+ if (readShortcutItems) {
+ // If the shortcuts info was read from the main Xml, skip reading from individual files.
+ // Data will get stored in the new format during the next call to saveToXml().
+ // TODO: ret.forAllPackageItems((ShortcutPackageItem item) -> item.markDirty());
+ s.scheduleSaveUser(userId);
+ } else {
+ final File root = s.injectUserDataPath(userId);
+
+ forAllFilesIn(new File(root, DIRECTORY_PACKAGES), (File f) -> {
+ final ShortcutPackage sp = ShortcutPackage.loadFromFile(s, ret, f, fromBackup);
+ if (sp != null) {
+ ret.mPackages.put(sp.getPackageName(), sp);
+ }
+ });
+
+ forAllFilesIn(new File(root, DIRECTORY_LUANCHERS), (File f) -> {
+ final ShortcutLauncher sl =
+ ShortcutLauncher.loadFromFile(f, ret, userId, fromBackup);
+ if (sl != null) {
+ ret.addLauncher(sl);
+ }
+ });
+ }
+
return ret;
}
+ private static void forAllFilesIn(File path, Consumer<File> callback) {
+ if (!path.exists()) {
+ return;
+ }
+ File[] list = path.listFiles();
+ for (File f : list) {
+ callback.accept(f);
+ }
+ }
+
public ComponentName getLastKnownLauncher() {
return mLastKnownLauncher;
}
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index d3cd1a9..2614076 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -118,6 +118,9 @@
},
{
"include-filter": "com.android.server.pm.UserSystemPackageInstallerTest"
+ },
+ {
+ "include-filter": "com.android.server.pm.parsing.SystemPartitionParseTest"
}
]
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 163504c..b0d4d95 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -155,6 +155,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -2478,13 +2479,60 @@
}
final PermissionsState permissionsState = ps.getPermissionsState();
- PermissionsState origPermissions = permissionsState;
final int[] currentUserIds = UserManagerService.getInstance().getUserIds();
boolean runtimePermissionsRevoked = false;
int[] updatedUserIds = EMPTY_INT_ARRAY;
+ for (int userId : currentUserIds) {
+ if (permissionsState.isMissing(userId)) {
+ Collection<String> requestedPermissions;
+ int targetSdkVersion;
+ if (!ps.isSharedUser()) {
+ requestedPermissions = pkg.getRequestedPermissions();
+ targetSdkVersion = pkg.getTargetSdkVersion();
+ } else {
+ requestedPermissions = new ArraySet<>();
+ targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+ List<AndroidPackage> packages = ps.getSharedUser().getPackages();
+ int packagesSize = packages.size();
+ for (int i = 0; i < packagesSize; i++) {
+ AndroidPackage sharedUserPackage = packages.get(i);
+ requestedPermissions.addAll(sharedUserPackage.getRequestedPermissions());
+ targetSdkVersion = Math.min(targetSdkVersion,
+ sharedUserPackage.getTargetSdkVersion());
+ }
+ }
+
+ for (String permissionName : requestedPermissions) {
+ BasePermission permission = mSettings.getPermission(permissionName);
+ if (Objects.equals(permission.getSourcePackageName(), PLATFORM_PACKAGE_NAME)
+ && permission.isRuntime() && !permission.isRemoved()) {
+ if (permission.isHardOrSoftRestricted()
+ || permission.isImmutablyRestricted()) {
+ permissionsState.updatePermissionFlags(permission, userId,
+ PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT,
+ PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT);
+ }
+ if (targetSdkVersion < Build.VERSION_CODES.M) {
+ permissionsState.updatePermissionFlags(permission, userId,
+ PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+ | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
+ PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+ | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT);
+ permissionsState.grantRuntimePermission(permission, userId);
+ }
+ }
+ }
+
+ permissionsState.setMissing(false, userId);
+ updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
+ }
+ }
+
+ PermissionsState origPermissions = permissionsState;
+
boolean changedInstallPermission = false;
if (replace) {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionsState.java b/services/core/java/com/android/server/pm/permission/PermissionsState.java
index 11e29a0..bad59cb 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionsState.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionsState.java
@@ -16,6 +16,8 @@
package com.android.server.pm.permission;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.util.ArrayMap;
@@ -30,6 +32,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
/**
@@ -70,6 +73,9 @@
private int[] mGlobalGids = NO_GIDS;
+ @Nullable
+ private SparseBooleanArray mMissing;
+
private SparseBooleanArray mPermissionReviewRequired;
public PermissionsState() {
@@ -132,6 +138,23 @@
other.mGlobalGids.length);
}
+ if (mMissing != null) {
+ if (other.mMissing == null) {
+ mMissing = null;
+ } else {
+ mMissing.clear();
+ }
+ }
+ if (other.mMissing != null) {
+ if (mMissing == null) {
+ mMissing = new SparseBooleanArray();
+ }
+ final int missingSize = other.mMissing.size();
+ for (int i = 0; i < missingSize; i++) {
+ mMissing.put(other.mMissing.keyAt(i), other.mMissing.valueAt(i));
+ }
+ }
+
if (mPermissionReviewRequired != null) {
if (other.mPermissionReviewRequired == null) {
mPermissionReviewRequired = null;
@@ -175,6 +198,10 @@
}
}
+ if (!Objects.equals(mMissing, other.mMissing)) {
+ return false;
+ }
+
if (mPermissionReviewRequired == null) {
if (other.mPermissionReviewRequired != null) {
return false;
@@ -185,6 +212,35 @@
return Arrays.equals(mGlobalGids, other.mGlobalGids);
}
+ /**
+ * Check whether the permissions state is missing for a user. This can happen if permission
+ * state is rolled back and we'll need to generate a reasonable default state to keep the app
+ * usable.
+ */
+ public boolean isMissing(@UserIdInt int userId) {
+ return mMissing != null && mMissing.get(userId);
+ }
+
+ /**
+ * Set whether the permissions state is missing for a user. This can happen if permission state
+ * is rolled back and we'll need to generate a reasonable default state to keep the app usable.
+ */
+ public void setMissing(boolean missing, @UserIdInt int userId) {
+ if (missing) {
+ if (mMissing == null) {
+ mMissing = new SparseBooleanArray();
+ }
+ mMissing.put(userId, true);
+ } else {
+ if (mMissing != null) {
+ mMissing.delete(userId);
+ if (mMissing.size() == 0) {
+ mMissing = null;
+ }
+ }
+ }
+ }
+
public boolean isPermissionReviewRequired(int userId) {
return mPermissionReviewRequired != null && mPermissionReviewRequired.get(userId);
}
@@ -569,6 +625,7 @@
invalidateCache();
}
+ mMissing = null;
mPermissionReviewRequired = null;
}
diff --git a/services/core/java/com/android/server/policy/LegacyGlobalActions.java b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
index 39f7ac0..9c3a394 100644
--- a/services/core/java/com/android/server/policy/LegacyGlobalActions.java
+++ b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
@@ -339,7 +339,7 @@
});
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
// Don't acquire soft keyboard focus, to avoid destroying state when capturing bugreports
- mDialog.getWindow().setFlags(FLAG_ALT_FOCUSABLE_IM, FLAG_ALT_FOCUSABLE_IM);
+ dialog.getWindow().setFlags(FLAG_ALT_FOCUSABLE_IM, FLAG_ALT_FOCUSABLE_IM);
dialog.setOnDismissListener(this);
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 76c6a7a..5e2c4eb 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -16,19 +16,14 @@
package com.android.server.policy;
-import static android.Manifest.permission.READ_PHONE_STATE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_FOREGROUND;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.AppOpsManager.OP_NONE;
import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
-import static android.content.pm.PackageManager.MATCH_ALL;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -366,46 +361,12 @@
// Force synchronization as permissions might have changed
synchronizePermissionsAndAppOpsForUser(userId);
- restoreReadPhoneStatePermissions(userId);
-
// Tell observers we are initialized for this user.
if (callback != null) {
callback.onInitialized(userId);
}
}
- /**
- * Ensure READ_PHONE_STATE user sensitive flags are assigned properly
- * TODO ntmyren: Remove once propagated, and state is repaired
- */
- private void restoreReadPhoneStatePermissions(int userId) {
- PackageManager pm = getContext().getPackageManager();
- List<PackageInfo> packageInfos = pm.getInstalledPackagesAsUser(
- MATCH_ALL | GET_PERMISSIONS, userId);
- for (int i = packageInfos.size() - 1; i >= 0; i--) {
- PackageInfo pI = packageInfos.get(i);
- if (pI.requestedPermissions == null) {
- continue;
- }
-
- UserHandle user = UserHandle.getUserHandleForUid(pI.applicationInfo.uid);
- for (int j = pI.requestedPermissions.length - 1; j >= 0; j--) {
- if (pI.requestedPermissions[j].equals(READ_PHONE_STATE)) {
- int flags = pm.getPermissionFlags(READ_PHONE_STATE, pI.packageName, user);
- // If the app is auto revoked for read phone state, and is only user sensitive
- // when granted, clear auto revoked flag.
- if ((flags & FLAG_PERMISSION_AUTO_REVOKED) != 0
- && (flags & FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) != 0
- && (flags & FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED) == 0) {
- pm.updatePermissionFlags(READ_PHONE_STATE, pI.packageName,
- FLAG_PERMISSION_AUTO_REVOKED, 0, user);
- }
- break;
- }
- }
- }
- }
-
@Override
public void onStopUser(@UserIdInt int userId) {
if (DEBUG) Slog.i(LOG_TAG, "onStopUser(" + userId + ")");
diff --git a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
index d6c48a0..cc36935 100644
--- a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
+++ b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
@@ -26,7 +26,6 @@
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.os.storage.StorageManager.PROP_LEGACY_OP_STICKY;
import static java.lang.Integer.min;
@@ -37,13 +36,17 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;
-import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.storage.StorageManager;
import android.os.storage.StorageManagerInternal;
+import android.provider.DeviceConfig;
import com.android.server.LocalServices;
import com.android.server.pm.parsing.pkg.AndroidPackage;
+import java.util.Arrays;
+import java.util.HashSet;
+
/**
* The behavior of soft restricted permissions is different for each permission. This class collects
* the policies in one place.
@@ -65,8 +68,8 @@
}
};
- private static final boolean isLegacyStorageAppOpStickyGlobal = SystemProperties.getBoolean(
- PROP_LEGACY_OP_STICKY, /*defaultValue*/true);
+ private static final HashSet<String> sForcedScopedStorageAppWhitelist = new HashSet<>(
+ Arrays.asList(getForcedScopedStorageAppWhitelist()));
/**
* TargetSDK is per package. To make sure two apps int the same shared UID do not fight over
@@ -141,12 +144,13 @@
shouldPreserveLegacyExternalStorage = pkg.hasPreserveLegacyExternalStorage()
&& smInternal.hasLegacyExternalStorage(appInfo.uid);
targetSDK = getMinimumTargetSDK(context, appInfo, user);
- // LEGACY_STORAGE op is normally sticky for apps targetig <= Q.
- // However, this device can be configured to make it non-sticky.
- boolean isLegacyAppOpSticky = isLegacyStorageAppOpStickyGlobal
- && targetSDK <= Build.VERSION_CODES.Q;
+
shouldApplyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0
- || (!isLegacyAppOpSticky && !shouldPreserveLegacyExternalStorage);
+ || (targetSDK > Build.VERSION_CODES.Q
+ && !shouldPreserveLegacyExternalStorage)
+ // If the device is configured to force this app into scoped storage,
+ // then we should apply the restriction
+ || sForcedScopedStorageAppWhitelist.contains(appInfo.packageName);
} else {
isWhiteListed = false;
shouldApplyRestriction = false;
@@ -245,6 +249,15 @@
return false;
}
+ private static String[] getForcedScopedStorageAppWhitelist() {
+ final String rawList = DeviceConfig.getString(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+ StorageManager.PROP_FORCED_SCOPED_STORAGE_WHITELIST, /*defaultValue*/"");
+ if (rawList == null || rawList.equals("")) {
+ return new String[0];
+ }
+ return rawList.split(",");
+ }
+
/**
* @return If the permission can be granted
*/
diff --git a/services/core/java/com/android/server/textclassifier/IconsContentProvider.java b/services/core/java/com/android/server/textclassifier/IconsContentProvider.java
index d19a707..9b3176d 100644
--- a/services/core/java/com/android/server/textclassifier/IconsContentProvider.java
+++ b/services/core/java/com/android/server/textclassifier/IconsContentProvider.java
@@ -25,6 +25,7 @@
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
+import android.os.UserHandle;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -51,7 +52,7 @@
try {
final ResourceInfo res = IconsUriHelper.getInstance().getResourceInfo(uri);
final Drawable drawable = Icon.createWithResource(res.packageName, res.id)
- .loadDrawable(getContext());
+ .loadDrawableAsUser(getContext(), UserHandle.getCallingUserId());
final byte[] data = getBitmapData(drawable);
final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
final ParcelFileDescriptor readSide = pipe[0];
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 85c439c..d93e976 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -493,12 +493,10 @@
* The launching activity which is using fixed rotation transformation.
*
* @see #handleTopActivityLaunchingInDifferentOrientation
- * @see #setFixedRotationLaunchingApp
+ * @see #setFixedRotationLaunchingApp(ActivityRecord, int)
* @see DisplayRotation#shouldRotateSeamlessly
*/
- ActivityRecord mFixedRotationLaunchingApp;
-
- FixedRotationAnimationController mFixedRotationAnimationController;
+ private ActivityRecord mFixedRotationLaunchingApp;
final FixedRotationTransitionListener mFixedRotationTransitionListener =
new FixedRotationTransitionListener();
@@ -1477,6 +1475,23 @@
return true;
}
+ @Nullable ActivityRecord getFixedRotationLaunchingApp() {
+ return mFixedRotationLaunchingApp;
+ }
+
+ void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r) {
+ setFixedRotationLaunchingAppUnchecked(r, ROTATION_UNDEFINED);
+ }
+
+ void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r, int rotation) {
+ if (mFixedRotationLaunchingApp == null && r != null) {
+ mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, rotation);
+ } else if (mFixedRotationLaunchingApp != null && r == null) {
+ mWmService.mDisplayNotificationController.dispatchFixedRotationFinished(this);
+ }
+ mFixedRotationLaunchingApp = r;
+ }
+
/**
* Sets the provided record to {@link mFixedRotationLaunchingApp} if possible to apply fixed
* rotation transform to it and indicate that the display may be rotated after it is launched.
@@ -1498,7 +1513,7 @@
if (!r.hasFixedRotationTransform()) {
startFixedRotationTransform(r, rotation);
}
- mFixedRotationLaunchingApp = r;
+ setFixedRotationLaunchingAppUnchecked(r, rotation);
if (prevRotatedLaunchingApp != null) {
prevRotatedLaunchingApp.finishFixedRotationTransform();
}
@@ -1526,7 +1541,7 @@
}
/**
- * Clears the {@link mFixedRotationLaunchingApp} without applying rotation to display. It is
+ * Clears the {@link #mFixedRotationLaunchingApp} without applying rotation to display. It is
* used when the display won't rotate (e.g. the orientation from sensor has updated again before
* applying rotation to display) but the launching app has been transformed. So the record need
* to be cleared and restored to stop using seamless rotation and rotated configuration.
@@ -1536,15 +1551,10 @@
return;
}
mFixedRotationLaunchingApp.finishFixedRotationTransform();
- mFixedRotationLaunchingApp = null;
+ setFixedRotationLaunchingAppUnchecked(null);
}
private void startFixedRotationTransform(WindowToken token, int rotation) {
- if (mFixedRotationAnimationController == null) {
- mFixedRotationAnimationController = new FixedRotationAnimationController(
- this);
- }
- mFixedRotationAnimationController.hide(rotation);
mTmpConfiguration.unset();
final DisplayInfo info = computeScreenConfiguration(mTmpConfiguration, rotation);
final WmDisplayCutout cutout = calculateDisplayCutoutForRotation(rotation);
@@ -1566,13 +1576,6 @@
}
}
- void finishFixedRotationAnimation() {
- if (mFixedRotationAnimationController != null
- && mFixedRotationAnimationController.show()) {
- mFixedRotationAnimationController = null;
- }
- }
-
/**
* Update rotation of the display.
*
@@ -5274,7 +5277,7 @@
rotatedLaunchingApp.finishFixedRotationTransform(
() -> applyRotation(oldRotation, newRotation));
- mFixedRotationLaunchingApp = null;
+ setFixedRotationLaunchingAppUnchecked(null);
}
/** Checks whether the given activity is in size compatibility mode and notifies the change. */
@@ -5588,7 +5591,7 @@
if (animatingRecents != null && animatingRecents == mFixedRotationLaunchingApp) {
// Because it won't affect display orientation, just finish the transform.
animatingRecents.finishFixedRotationTransform();
- mFixedRotationLaunchingApp = null;
+ setFixedRotationLaunchingAppUnchecked(null);
} else {
// If there is already a launching activity that is not the recents, before its
// transition is completed, the recents animation may be started. So if the recents
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index c3f9061..702df2a 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -560,7 +560,6 @@
}, true /* traverseTopToBottom */);
mSeamlessRotationCount = 0;
mRotatingSeamlessly = false;
- mDisplayContent.finishFixedRotationAnimation();
}
private void prepareSeamlessRotation() {
@@ -578,7 +577,7 @@
boolean shouldRotateSeamlessly(int oldRotation, int newRotation, boolean forceUpdate) {
// Display doesn't need to be frozen because application has been started in correct
// rotation already, so the rest of the windows can use seamless rotation.
- if (mDisplayContent.mFixedRotationLaunchingApp != null) {
+ if (mDisplayContent.getFixedRotationLaunchingApp() != null) {
return true;
}
@@ -647,7 +646,6 @@
"Performing post-rotate rotation after seamless rotation");
// Finish seamless rotation.
mRotatingSeamlessly = false;
- mDisplayContent.finishFixedRotationAnimation();
updateRotationAndSendNewConfigIfChanged();
}
diff --git a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
index af13e3a..b627b33 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
@@ -95,4 +95,27 @@
}
mDisplayListeners.finishBroadcast();
}
+
+ void dispatchFixedRotationStarted(DisplayContent display, int newRotation) {
+ int count = mDisplayListeners.beginBroadcast();
+ for (int i = 0; i < count; ++i) {
+ try {
+ mDisplayListeners.getBroadcastItem(i).onFixedRotationStarted(
+ display.mDisplayId, newRotation);
+ } catch (RemoteException e) {
+ }
+ }
+ mDisplayListeners.finishBroadcast();
+ }
+
+ void dispatchFixedRotationFinished(DisplayContent display) {
+ int count = mDisplayListeners.beginBroadcast();
+ for (int i = 0; i < count; ++i) {
+ try {
+ mDisplayListeners.getBroadcastItem(i).onFixedRotationFinished(display.mDisplayId);
+ } catch (RemoteException e) {
+ }
+ }
+ mDisplayListeners.finishBroadcast();
+ }
}
diff --git a/services/core/java/com/android/server/wm/FixedRotationAnimationController.java b/services/core/java/com/android/server/wm/FixedRotationAnimationController.java
deleted file mode 100644
index 7aca637..0000000
--- a/services/core/java/com/android/server/wm/FixedRotationAnimationController.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static com.android.server.wm.AnimationSpecProto.WINDOW;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;
-import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION;
-
-import android.content.res.Configuration;
-import android.util.proto.ProtoOutputStream;
-import android.view.SurfaceControl;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Transformation;
-
-import com.android.internal.R;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-/**
- * Controller to fade out and in system ui when applying a fixed rotation transform to a window
- * token.
- *
- * The system bars will be fade out when the fixed rotation transform starts and will be fade in
- * once all surfaces have been rotated.
- */
-public class FixedRotationAnimationController {
-
- private final WindowManagerService mWmService;
- private boolean mShowRequested = true;
- private int mTargetRotation = Configuration.ORIENTATION_UNDEFINED;
- private final ArrayList<WindowState> mAnimatedWindowStates = new ArrayList<>(2);
- private final Runnable[] mDeferredFinishCallbacks;
-
- public FixedRotationAnimationController(DisplayContent displayContent) {
- mWmService = displayContent.mWmService;
- addAnimatedWindow(displayContent.getDisplayPolicy().getStatusBar());
- addAnimatedWindow(displayContent.getDisplayPolicy().getNavigationBar());
- mDeferredFinishCallbacks = new Runnable[mAnimatedWindowStates.size()];
- }
-
- private void addAnimatedWindow(WindowState windowState) {
- if (windowState != null) {
- mAnimatedWindowStates.add(windowState);
- }
- }
-
- /**
- * Show the previously hidden {@link WindowToken} if their surfaces have already been rotated.
- *
- * @return True if the show animation has been started, in which case the caller no longer needs
- * this {@link FixedRotationAnimationController}.
- */
- boolean show() {
- if (!mShowRequested && readyToShow()) {
- mShowRequested = true;
- for (int i = mAnimatedWindowStates.size() - 1; i >= 0; i--) {
- WindowState windowState = mAnimatedWindowStates.get(i);
- fadeWindowToken(true, windowState.getParent(), i);
- }
- return true;
- }
- return false;
- }
-
- void hide(int targetRotation) {
- mTargetRotation = targetRotation;
- if (mShowRequested) {
- mShowRequested = false;
- for (int i = mAnimatedWindowStates.size() - 1; i >= 0; i--) {
- WindowState windowState = mAnimatedWindowStates.get(i);
- fadeWindowToken(false /* show */, windowState.getParent(), i);
- }
- }
- }
-
- void cancel() {
- for (int i = mAnimatedWindowStates.size() - 1; i >= 0; i--) {
- WindowState windowState = mAnimatedWindowStates.get(i);
- mShowRequested = true;
- fadeWindowToken(true /* show */, windowState.getParent(), i);
- }
- }
-
- private void fadeWindowToken(boolean show, WindowContainer<WindowToken> windowToken,
- int index) {
- Animation animation = AnimationUtils.loadAnimation(mWmService.mContext,
- show ? R.anim.fade_in : R.anim.fade_out);
- LocalAnimationAdapter.AnimationSpec windowAnimationSpec = createAnimationSpec(animation);
-
- FixedRotationAnimationAdapter animationAdapter = new FixedRotationAnimationAdapter(
- windowAnimationSpec, windowToken.getSurfaceAnimationRunner(), show, index);
-
- // We deferred the end of the animation when hiding the token, so we need to end it now that
- // it's shown again.
- SurfaceAnimator.OnAnimationFinishedCallback finishedCallback = show ? (t, r) -> {
- if (mDeferredFinishCallbacks[index] != null) {
- mDeferredFinishCallbacks[index].run();
- mDeferredFinishCallbacks[index] = null;
- }
- } : null;
- windowToken.startAnimation(windowToken.getPendingTransaction(), animationAdapter,
- mShowRequested, ANIMATION_TYPE_FIXED_TRANSFORM, finishedCallback);
- }
-
- /**
- * Check if all the mAnimatedWindowState's surfaces have been rotated to the
- * mTargetRotation.
- */
- private boolean readyToShow() {
- for (int i = mAnimatedWindowStates.size() - 1; i >= 0; i--) {
- WindowState windowState = mAnimatedWindowStates.get(i);
- if (windowState.getConfiguration().windowConfiguration.getRotation()
- != mTargetRotation || windowState.mPendingSeamlessRotate != null) {
- return false;
- }
- }
- return true;
- }
-
-
- private LocalAnimationAdapter.AnimationSpec createAnimationSpec(Animation animation) {
- return new LocalAnimationAdapter.AnimationSpec() {
-
- Transformation mTransformation = new Transformation();
-
- @Override
- public boolean getShowWallpaper() {
- return true;
- }
-
- @Override
- public long getDuration() {
- return animation.getDuration();
- }
-
- @Override
- public void apply(SurfaceControl.Transaction t, SurfaceControl leash,
- long currentPlayTime) {
- mTransformation.clear();
- animation.getTransformation(currentPlayTime, mTransformation);
- t.setAlpha(leash, mTransformation.getAlpha());
- }
-
- @Override
- public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix);
- pw.println(animation);
- }
-
- @Override
- public void dumpDebugInner(ProtoOutputStream proto) {
- final long token = proto.start(WINDOW);
- proto.write(ANIMATION, animation.toString());
- proto.end(token);
- }
- };
- }
-
- private class FixedRotationAnimationAdapter extends LocalAnimationAdapter {
- private final boolean mShow;
- private final int mIndex;
-
- FixedRotationAnimationAdapter(AnimationSpec windowAnimationSpec,
- SurfaceAnimationRunner surfaceAnimationRunner, boolean show, int index) {
- super(windowAnimationSpec, surfaceAnimationRunner);
- mShow = show;
- mIndex = index;
- }
-
- @Override
- public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
- // We defer the end of the hide animation to ensure the tokens stay hidden until
- // we show them again.
- if (!mShow) {
- mDeferredFinishCallbacks[mIndex] = endDeferFinishCallback;
- return true;
- }
- return false;
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index d8a4ecb..1cd94b4 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -1387,9 +1387,7 @@
return false;
}
- /**
- * @return whether the given task can be trimmed even if it is outside the visible range.
- */
+ /** @return whether the given task can be trimmed even if it is outside the visible range. */
protected boolean isTrimmable(Task task) {
final ActivityStack stack = task.getStack();
@@ -1404,9 +1402,13 @@
return false;
}
+ final ActivityStack rootHomeTask = stack.getDisplayArea().getRootHomeTask();
+ // Home stack does not exist. Don't trim the task.
+ if (rootHomeTask == null) {
+ return false;
+ }
// Trim tasks that are behind the home task.
- final TaskDisplayArea taskDisplayArea = stack.getDisplayArea();
- return task.compareTo(taskDisplayArea.getRootHomeTask()) < 0;
+ return task.compareTo(rootHomeTask) < 0;
}
/** Remove the tasks that user may not be able to return. */
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index cbc1bdf..f1b322e 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -406,8 +406,9 @@
}
// Save the minimized home height
- mMinimizedHomeBounds = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask()
- .getBounds();
+ final ActivityStack rootHomeTask =
+ mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask();
+ mMinimizedHomeBounds = rootHomeTask != null ? rootHomeTask.getBounds() : null;
mService.mWindowPlacerLocked.performSurfacePlacement();
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index c02e0a1..c7f7834 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -387,9 +387,11 @@
int getMode() {
final DisplayContent dc = mWindowContainer.getDisplayContent();
final ActivityRecord topActivity = mWindowContainer.getTopMostActivity();
+ // Note that opening/closing transitions are per-activity while changing transitions
+ // are per-task.
if (dc.mOpeningApps.contains(topActivity)) {
return RemoteAnimationTarget.MODE_OPENING;
- } else if (dc.mChangingContainers.contains(topActivity)) {
+ } else if (dc.mChangingContainers.contains(mWindowContainer)) {
return RemoteAnimationTarget.MODE_CHANGING;
} else {
return RemoteAnimationTarget.MODE_CLOSING;
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 0143eb1..42342a6 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -489,12 +489,6 @@
static final int ANIMATION_TYPE_INSETS_CONTROL = 1 << 5;
/**
- * Animation when a fixed rotation transform is applied to a window token.
- * @hide
- */
- static final int ANIMATION_TYPE_FIXED_TRANSFORM = 1 << 6;
-
- /**
* Bitmask to include all animation types. This is NOT an {@link AnimationType}
* @hide
*/
@@ -511,8 +505,7 @@
ANIMATION_TYPE_DIMMER,
ANIMATION_TYPE_RECENTS,
ANIMATION_TYPE_WINDOW_ANIMATION,
- ANIMATION_TYPE_INSETS_CONTROL,
- ANIMATION_TYPE_FIXED_TRANSFORM
+ ANIMATION_TYPE_INSETS_CONTROL
})
@Retention(RetentionPolicy.SOURCE)
@interface AnimationType {}
diff --git a/services/core/java/com/android/server/wm/TEST_MAPPING b/services/core/java/com/android/server/wm/TEST_MAPPING
deleted file mode 100644
index b2e8bbe..0000000
--- a/services/core/java/com/android/server/wm/TEST_MAPPING
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "postsubmit": [
- {
- "name": "CtsWindowManagerDeviceTestCases"
- },
- {
- "name": "FrameworksServicesTests",
- "options": [
- {
- "include-filter": "com.android.server.wm."
- }
- ]
- },
- {
- "name": "WmTests",
- "options": [
- {
- "include-filter": "com.android.server.wm."
- }
- ]
- }
- ]
-}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 6ce36f1..6661c30 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -195,7 +195,7 @@
return mChildren.indexOf(stack);
}
- ActivityStack getRootHomeTask() {
+ @Nullable ActivityStack getRootHomeTask() {
return mRootHomeTask;
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index e2023ae..a1fbb59 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2184,7 +2184,8 @@
final int appStackClipMode = getDisplayContent().mAppTransition.getAppStackClipMode();
// Separate position and size for use in animators.
- mTmpRect.set(getAnimationBounds(appStackClipMode));
+ final Rect screenBounds = getAnimationBounds(appStackClipMode);
+ mTmpRect.set(screenBounds);
getAnimationPosition(mTmpPoint);
if (!sHierarchicalAnimations) {
// Non-hierarchical animation uses position in global coordinates.
@@ -2203,7 +2204,7 @@
localBounds.offsetTo(mTmpPoint.x, mTmpPoint.y);
final RemoteAnimationController.RemoteAnimationRecord adapters =
controller.createRemoteAnimationRecord(this, mTmpPoint, localBounds,
- mTmpRect, (isChanging ? mSurfaceFreezer.mFreezeBounds : null));
+ screenBounds, (isChanging ? mSurfaceFreezer.mFreezeBounds : null));
resultAdapters = new Pair<>(adapters.mAdapter, adapters.mThumbnailAdapter);
} else if (isChanging) {
final float durationScale = mWmService.getTransitionAnimationScaleLocked();
@@ -2616,10 +2617,8 @@
return willSync;
}
- boolean prepareForSync(BLASTSyncEngine.TransactionReadyListener waitingListener,
- int waitingId) {
- boolean willSync = true;
-
+ boolean setPendingListener(BLASTSyncEngine.TransactionReadyListener waitingListener,
+ int waitingId) {
// If we are invisible, no need to sync, likewise if we are already engaged in a sync,
// we can't support overlapping syncs on a single container yet.
if (!isVisible() || mWaitingListener != null) {
@@ -2630,6 +2629,15 @@
// Make sure to set these before we call setReady in case the sync was a no-op
mWaitingSyncId = waitingId;
mWaitingListener = waitingListener;
+ return true;
+ }
+
+ boolean prepareForSync(BLASTSyncEngine.TransactionReadyListener waitingListener,
+ int waitingId) {
+ boolean willSync = setPendingListener(waitingListener, waitingId);
+ if (!willSync) {
+ return false;
+ }
int localId = mBLASTSyncEngine.startSyncSet(this);
willSync |= addChildrenToSyncSet(localId);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5c21b2b..159c59b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7922,18 +7922,23 @@
@Override
public void syncInputTransactions() {
- waitForAnimationsToComplete();
+ long token = Binder.clearCallingIdentity();
+ try {
+ waitForAnimationsToComplete();
- // Collect all input transactions from all displays to make sure we could sync all input
- // windows at same time.
- final SurfaceControl.Transaction t = mTransactionFactory.get();
- synchronized (mGlobalLock) {
- mWindowPlacerLocked.performSurfacePlacementIfScheduled();
- mRoot.forAllDisplays(displayContent ->
- displayContent.getInputMonitor().updateInputWindowsImmediately(t));
+ // Collect all input transactions from all displays to make sure we could sync all input
+ // windows at same time.
+ final SurfaceControl.Transaction t = mTransactionFactory.get();
+ synchronized (mGlobalLock) {
+ mWindowPlacerLocked.performSurfacePlacementIfScheduled();
+ mRoot.forAllDisplays(displayContent ->
+ displayContent.getInputMonitor().updateInputWindowsImmediately(t));
+ }
+
+ t.syncInputWindows().apply();
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
-
- t.syncInputWindows().apply();
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index fe68cd6..20b109b 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -1315,12 +1315,15 @@
*
* @param isCached whether or not the process is cached.
*/
+ @HotPath(caller = HotPath.OOM_ADJUSTMENT)
public void onProcCachedStateChanged(boolean isCached) {
- synchronized (mAtm.mGlobalLock) {
- if (!isCached && mPendingConfiguration != null) {
- final Configuration config = mPendingConfiguration;
- mPendingConfiguration = null;
- dispatchConfigurationChange(config);
+ if (!isCached) {
+ synchronized (mAtm.mGlobalLockWithoutBoost) {
+ if (mPendingConfiguration != null) {
+ final Configuration config = mPendingConfiguration;
+ mPendingConfiguration = null;
+ dispatchConfigurationChange(config);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index e925ce5..e9aff88 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1509,7 +1509,7 @@
return mActivityRecord != null ? mActivityRecord.getTask() : null;
}
- ActivityStack getRootTask() {
+ @Nullable ActivityStack getRootTask() {
final Task task = getTask();
if (task != null) {
return (ActivityStack) task.getRootTask();
@@ -2262,7 +2262,29 @@
mHasSurface = hasSurface;
}
+ /**
+ * Checks whether one of the Windows in a Display embedded in this Window can be an IME target.
+ */
+ private boolean canWindowInEmbeddedDisplayBeImeTarget() {
+ final int embeddedDisplayContentsSize = mEmbeddedDisplayContents.size();
+ for (int i = embeddedDisplayContentsSize - 1; i >= 0; i--) {
+ final DisplayContent edc = mEmbeddedDisplayContents.valueAt(i);
+ if (edc.forAllWindows(WindowState::canBeImeTarget, true)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
boolean canBeImeTarget() {
+ // If any of the embedded windows can be the IME target, this window will be the final IME
+ // target. This is because embedded windows are on a different display in WM so it would
+ // cause confusion trying to set the IME to a window on a different display. Instead, just
+ // make the host window the IME target.
+ if (canWindowInEmbeddedDisplayBeImeTarget()) {
+ return true;
+ }
+
if (mIsImWindow) {
// IME windows can't be IME targets. IME targets are required to be below the IME
// windows and that wouldn't be possible if the IME window is its own target...silly.
@@ -2527,7 +2549,7 @@
final Task task = getTask();
if (task != null) {
task.getDimBounds(mTmpRect);
- } else {
+ } else if (getRootTask() != null) {
getRootTask().getDimBounds(mTmpRect);
}
}
@@ -5707,16 +5729,20 @@
@Override
boolean prepareForSync(BLASTSyncEngine.TransactionReadyListener waitingListener,
int waitingId) {
- if (!isVisible()) {
+ boolean willSync = setPendingListener(waitingListener, waitingId);
+ if (!willSync) {
return false;
}
- mWaitingListener = waitingListener;
- mWaitingSyncId = waitingId;
- mUsingBLASTSyncTransaction = true;
mLocalSyncId = mBLASTSyncEngine.startSyncSet(this);
addChildrenToSyncSet(mLocalSyncId);
+ // In the WindowContainer implementation we immediately mark ready
+ // since a generic WindowContainer only needs to wait for its
+ // children to finish and is immediately ready from its own
+ // perspective but at the WindowState level we need to wait for ourselves
+ // to draw even if the children draw first our don't need to sync, so we omit
+ // the set ready call until later in finishDrawing()
mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this);
mWmService.mH.sendNewMessageDelayed(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this,
BLAST_TIMEOUT_DURATION);
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index c570cf1..b30d408 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1257,19 +1257,25 @@
mYOffset = dy;
mWallpaperScale = scale;
- try {
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setWallpaperOffset");
- mService.openSurfaceTransaction();
- setWallpaperPositionAndScale(dx, dy, scale, false);
- } catch (RuntimeException e) {
- Slog.w(TAG, "Error positioning surface of " + mWin
- + " pos=(" + dx + "," + dy + ")", e);
- } finally {
- mService.closeSurfaceTransaction("setWallpaperOffset");
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
- "<<< CLOSE TRANSACTION setWallpaperOffset");
- return true;
+ if (mSurfaceController != null) {
+ try {
+ if (SHOW_LIGHT_TRANSACTIONS) {
+ Slog.i(TAG, ">>> OPEN TRANSACTION setWallpaperOffset");
+ }
+ mService.openSurfaceTransaction();
+ setWallpaperPositionAndScale(dx, dy, scale, false);
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "Error positioning surface of " + mWin
+ + " pos=(" + dx + "," + dy + ")", e);
+ } finally {
+ mService.closeSurfaceTransaction("setWallpaperOffset");
+ if (SHOW_LIGHT_TRANSACTIONS) {
+ Slog.i(TAG, "<<< CLOSE TRANSACTION setWallpaperOffset");
+ }
+ }
}
+
+ return true;
}
private void setWallpaperPositionAndScale(int dx, int dy, float scale,
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 8739bad4..768f89e 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -642,9 +642,6 @@
final int originalRotation = getWindowConfiguration().getRotation();
onConfigurationChanged(parent.getConfiguration());
onCancelFixedRotationTransform(originalRotation);
- if (mDisplayContent.mFixedRotationAnimationController != null) {
- mDisplayContent.mFixedRotationAnimationController.cancel();
- }
}
/**
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 18c25c1..c1735ff 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -7973,6 +7973,9 @@
final int userHandle = UserHandle.getCallingUserId();
boolean requireAutoTimeChanged = false;
synchronized (getLockObject()) {
+ if (isManagedProfile(userHandle)) {
+ throw new SecurityException("Managed profile cannot set auto time required");
+ }
ActiveAdmin admin = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
if (admin.requireAutoTime != required) {
diff --git a/services/people/java/com/android/server/people/data/AppUsageStatsData.java b/services/people/java/com/android/server/people/data/AppUsageStatsData.java
new file mode 100644
index 0000000..6baef38
--- /dev/null
+++ b/services/people/java/com/android/server/people/data/AppUsageStatsData.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 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.people.data;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/** The data containing package usage info. */
+public class AppUsageStatsData {
+
+ private int mLaunchCount;
+
+ private int mChosenCount;
+
+ @VisibleForTesting
+ public AppUsageStatsData(int chosenCount, int launchCount) {
+ this.mChosenCount = chosenCount;
+ this.mLaunchCount = launchCount;
+ }
+
+ public AppUsageStatsData() {
+ }
+
+ public int getLaunchCount() {
+ return mLaunchCount;
+ }
+
+ void incrementLaunchCountBy(int launchCount) {
+ this.mLaunchCount += launchCount;
+ }
+
+ public int getChosenCount() {
+ return mChosenCount;
+ }
+
+ void incrementChosenCountBy(int chosenCount) {
+ this.mChosenCount += chosenCount;
+ }
+}
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index 107c41a..bbb0215 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -257,13 +257,16 @@
}
/**
- * Queries launch counts of apps within {@code packageNameFilter} between {@code startTime}
- * and {@code endTime}.
+ * Queries usage stats of apps within {@code packageNameFilter} between {@code startTime} and
+ * {@code endTime}.
+ *
+ * @return a map which keys are package names and values are {@link AppUsageStatsData}.
*/
@NonNull
- public Map<String, Integer> queryAppLaunchCount(@UserIdInt int callingUserId, long startTime,
+ public Map<String, AppUsageStatsData> queryAppUsageStats(
+ @UserIdInt int callingUserId, long startTime,
long endTime, Set<String> packageNameFilter) {
- return UsageStatsQueryHelper.queryAppLaunchCount(callingUserId, startTime, endTime,
+ return UsageStatsQueryHelper.queryAppUsageStats(callingUserId, startTime, endTime,
packageNameFilter);
}
diff --git a/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java b/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java
index 6e6fea9..d89bbe9 100644
--- a/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java
+++ b/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java
@@ -137,27 +137,48 @@
}
/**
- * Queries {@link UsageStatsManagerInternal} for launch count of apps within {@code
- * packageNameFilter} between {@code startTime} and {@code endTime}.obfuscateInstantApps
+ * Queries {@link UsageStatsManagerInternal} for usage stats of apps within {@code
+ * packageNameFilter} between {@code startTime} and {@code endTime}.
*
- * @return a map which keys are package names and values are app launch counts.
+ * @return a map which keys are package names and values are {@link AppUsageStatsData}.
*/
- static Map<String, Integer> queryAppLaunchCount(@UserIdInt int userId, long startTime,
+ static Map<String, AppUsageStatsData> queryAppUsageStats(@UserIdInt int userId, long startTime,
long endTime, Set<String> packageNameFilter) {
List<UsageStats> stats = getUsageStatsManagerInternal().queryUsageStatsForUser(userId,
UsageStatsManager.INTERVAL_BEST, startTime, endTime,
/* obfuscateInstantApps= */ false);
- Map<String, Integer> aggregatedStats = new ArrayMap<>();
+ Map<String, AppUsageStatsData> aggregatedStats = new ArrayMap<>();
for (UsageStats stat : stats) {
String packageName = stat.getPackageName();
if (packageNameFilter.contains(packageName)) {
- aggregatedStats.put(packageName,
- aggregatedStats.getOrDefault(packageName, 0) + stat.getAppLaunchCount());
+ AppUsageStatsData packageStats = aggregatedStats.computeIfAbsent(packageName,
+ (key) -> new AppUsageStatsData());
+ packageStats.incrementChosenCountBy(sumChooserCounts(stat.mChooserCounts));
+ packageStats.incrementLaunchCountBy(stat.getAppLaunchCount());
}
}
return aggregatedStats;
}
+ private static int sumChooserCounts(ArrayMap<String, ArrayMap<String, Integer>> chooserCounts) {
+ int sum = 0;
+ if (chooserCounts == null) {
+ return sum;
+ }
+ int chooserCountsSize = chooserCounts.size();
+ for (int i = 0; i < chooserCountsSize; i++) {
+ ArrayMap<String, Integer> counts = chooserCounts.valueAt(i);
+ if (counts == null) {
+ continue;
+ }
+ final int annotationSize = counts.size();
+ for (int j = 0; j < annotationSize; j++) {
+ sum += counts.valueAt(j);
+ }
+ }
+ return sum;
+ }
+
private void onInAppConversationEnded(@NonNull PackageData packageData,
@NonNull UsageEvents.Event endEvent) {
ComponentName activityName =
diff --git a/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java b/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java
index 76f252e..c77843c 100644
--- a/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java
+++ b/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java
@@ -27,17 +27,18 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ChooserActivity;
+import com.android.server.people.data.AppUsageStatsData;
import com.android.server.people.data.DataManager;
import com.android.server.people.data.Event;
import java.time.Duration;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
/** Ranking scorer for Sharesheet targets. */
class SharesheetModelScorer {
@@ -50,8 +51,8 @@
private static final float RECENCY_SCORE_SUBSEQUENT_DECAY = 0.02F;
private static final long ONE_MONTH_WINDOW = TimeUnit.DAYS.toMillis(30);
private static final long FOREGROUND_APP_PROMO_TIME_WINDOW = TimeUnit.MINUTES.toMillis(10);
+ private static final float USAGE_STATS_CHOOSER_SCORE_INITIAL_DECAY = 0.9F;
private static final float FREQUENTLY_USED_APP_SCORE_INITIAL_DECAY = 0.3F;
- private static final float FREQUENTLY_USED_APP_SCORE_DECAY = 0.9F;
@VisibleForTesting
static final float FOREGROUND_APP_WEIGHT = 0F;
@VisibleForTesting
@@ -192,14 +193,16 @@
targetsList.add(index, shareTarget);
}
promoteForegroundApp(shareTargetMap, dataManager, callingUserId);
- promoteFrequentlyUsedApps(shareTargetMap, targetsLimit, dataManager, callingUserId);
+ promoteMostChosenAndFrequentlyUsedApps(shareTargetMap, targetsLimit, dataManager,
+ callingUserId);
}
/**
- * Promotes frequently used sharing apps, if recommended apps based on sharing history have not
- * reached the limit (e.g. user did not share any content in last couple weeks)
+ * Promotes frequently chosen sharing apps and frequently used sharing apps as per
+ * UsageStatsManager, if recommended apps based on sharing history have not reached the limit
+ * (e.g. user did not share any content in last couple weeks)
*/
- private static void promoteFrequentlyUsedApps(
+ private static void promoteMostChosenAndFrequentlyUsedApps(
Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap, int targetsLimit,
@NonNull DataManager dataManager, @UserIdInt int callingUserId) {
int validPredictionNum = 0;
@@ -217,39 +220,50 @@
return;
}
long now = System.currentTimeMillis();
- Map<String, Integer> appLaunchCountsMap = dataManager.queryAppLaunchCount(
- callingUserId, now - ONE_MONTH_WINDOW, now, shareTargetMap.keySet());
- List<Pair<String, Integer>> appLaunchCounts = new ArrayList<>();
- minValidScore *= FREQUENTLY_USED_APP_SCORE_INITIAL_DECAY;
- for (Map.Entry<String, Integer> entry : appLaunchCountsMap.entrySet()) {
- if (entry.getValue() > 0) {
- appLaunchCounts.add(new Pair(entry.getKey(), entry.getValue()));
+ Map<String, AppUsageStatsData> appStatsMap =
+ dataManager.queryAppUsageStats(
+ callingUserId, now - ONE_MONTH_WINDOW, now, shareTargetMap.keySet());
+ // Promotes frequently chosen sharing apps as per UsageStatsManager.
+ minValidScore = promoteApp(shareTargetMap, appStatsMap, AppUsageStatsData::getChosenCount,
+ USAGE_STATS_CHOOSER_SCORE_INITIAL_DECAY * minValidScore, minValidScore);
+ // Promotes frequently used sharing apps as per UsageStatsManager.
+ promoteApp(shareTargetMap, appStatsMap, AppUsageStatsData::getLaunchCount,
+ FREQUENTLY_USED_APP_SCORE_INITIAL_DECAY * minValidScore, minValidScore);
+ }
+
+ private static float promoteApp(
+ Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap,
+ Map<String, AppUsageStatsData> appStatsMap,
+ Function<AppUsageStatsData, Integer> countFunc, float baseScore, float minValidScore) {
+ int maxCount = 0;
+ for (AppUsageStatsData data : appStatsMap.values()) {
+ maxCount = Math.max(maxCount, countFunc.apply(data));
+ }
+ if (maxCount > 0) {
+ for (Map.Entry<String, AppUsageStatsData> entry : appStatsMap.entrySet()) {
+ if (!shareTargetMap.containsKey(entry.getKey())) {
+ continue;
+ }
+ ShareTargetPredictor.ShareTarget target = shareTargetMap.get(entry.getKey()).get(0);
+ if (target.getScore() > 0f) {
+ continue;
+ }
+ float curScore = baseScore * countFunc.apply(entry.getValue()) / maxCount;
+ target.setScore(curScore);
+ if (curScore > 0) {
+ minValidScore = Math.min(minValidScore, curScore);
+ }
+ if (DEBUG) {
+ Slog.d(TAG, String.format(
+ "SharesheetModel: promote as per AppUsageStats packageName: %s, "
+ + "className: %s, total:%.2f",
+ target.getAppTarget().getPackageName(),
+ target.getAppTarget().getClassName(),
+ target.getScore()));
+ }
}
}
- Collections.sort(appLaunchCounts, (p1, p2) -> -Integer.compare(p1.second, p2.second));
- for (Pair<String, Integer> entry : appLaunchCounts) {
- if (!shareTargetMap.containsKey(entry.first)) {
- continue;
- }
- ShareTargetPredictor.ShareTarget target = shareTargetMap.get(entry.first).get(0);
- if (target.getScore() > 0f) {
- continue;
- }
- target.setScore(minValidScore);
- minValidScore *= FREQUENTLY_USED_APP_SCORE_DECAY;
- if (DEBUG) {
- Slog.d(TAG, String.format(
- "SharesheetModel: promoteFrequentUsedApps packageName: %s, className: %s,"
- + " total:%.2f",
- target.getAppTarget().getPackageName(),
- target.getAppTarget().getClassName(),
- target.getScore()));
- }
- validPredictionNum++;
- if (validPredictionNum == targetsLimit) {
- return;
- }
- }
+ return minValidScore;
}
/**
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index e5ec1f7..96a44a4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -16,14 +16,23 @@
package com.android.server.am;
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+
import static com.android.server.am.ActivityManagerService.Injector;
import static com.android.server.am.CachedAppOptimizer.compactActionIntToString;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.MessageQueue;
import android.os.Process;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
@@ -31,9 +40,11 @@
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.appop.AppOpsService;
import com.android.server.testables.TestableDeviceConfig;
+import com.android.server.wm.ActivityTaskManagerService;
import org.junit.After;
import org.junit.Assume;
@@ -45,6 +56,7 @@
import org.mockito.junit.MockitoJUnitRunner;
import java.io.File;
+import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
@@ -68,25 +80,36 @@
private HandlerThread mHandlerThread;
private Handler mHandler;
private CountDownLatch mCountDown;
+ private ActivityManagerService mAms;
+ private Context mContext;
+ private TestInjector mInjector;
+ private TestProcessDependencies mProcessDependencies;
+
+ @Mock
+ private PackageManagerInternal mPackageManagerInt;
@Rule
- public TestableDeviceConfig.TestableDeviceConfigRule
+ public final TestableDeviceConfig.TestableDeviceConfigRule
mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+ @Rule
+ public final ApplicationExitInfoTest.ServiceThreadRule
+ mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
@Before
public void setUp() {
mHandlerThread = new HandlerThread("");
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
-
mThread = new ServiceThread("TestServiceThread", Process.THREAD_PRIORITY_DEFAULT,
true /* allowIo */);
mThread.start();
-
- ActivityManagerService ams = new ActivityManagerService(
- new TestInjector(InstrumentationRegistry.getInstrumentation().getContext()),
- mThread);
- mCachedAppOptimizerUnderTest = new CachedAppOptimizer(ams,
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ mInjector = new TestInjector(mContext);
+ mAms = new ActivityManagerService(
+ new TestInjector(mContext), mServiceThreadRule.getThread());
+ doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
+ mProcessDependencies = new TestProcessDependencies();
+ mCachedAppOptimizerUnderTest = new CachedAppOptimizer(mAms,
new CachedAppOptimizer.PropertyChangedCallbackForTest() {
@Override
public void onPropertyChanged() {
@@ -94,7 +117,9 @@
mCountDown.countDown();
}
}
- });
+ }, mProcessDependencies);
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
}
@After
@@ -104,6 +129,19 @@
mCountDown = null;
}
+ private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, String processName,
+ String packageName) {
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = packageName;
+ ProcessRecord app = new ProcessRecord(mAms, ai, processName, uid);
+ app.pid = pid;
+ app.info.uid = packageUid;
+ // Exact value does not mater, it can be any state for which compaction is allowed.
+ app.setProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+ app.setAdj = 905;
+ return app;
+ }
+
@Test
public void init_setsDefaults() {
mCachedAppOptimizerUnderTest.init();
@@ -197,7 +235,7 @@
CachedAppOptimizer.DEFAULT_USE_FREEZER);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
CachedAppOptimizer.KEY_USE_FREEZER, CachedAppOptimizer.DEFAULT_USE_FREEZER
- ? "false" : "true" , false);
+ ? "false" : "true", false);
// Then calling init will read and set that flag.
mCachedAppOptimizerUnderTest.init();
@@ -790,6 +828,174 @@
.containsExactlyElementsIn(expected);
}
+ @Test
+ public void processWithDeltaRSSTooSmall_notFullCompacted() throws Exception {
+ // Initialize CachedAppOptimizer and set flags to (1) enable compaction, (2) set RSS
+ // throttle to 12000.
+ mCachedAppOptimizerUnderTest.init();
+ setFlag(CachedAppOptimizer.KEY_USE_COMPACTION, "true", true);
+ setFlag(CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "12000", false);
+ initActivityManagerService();
+
+ // Simulate RSS anon memory larger than throttle.
+ long[] rssBefore1 =
+ new long[]{/*totalRSS*/ 10000, /*fileRSS*/ 10000, /*anonRSS*/ 12000, /*swap*/
+ 10000};
+ long[] rssAfter1 =
+ new long[]{/*totalRSS*/ 9000, /*fileRSS*/ 9000, /*anonRSS*/ 11000, /*swap*/9000};
+ // Delta between rssAfter1 and rssBefore2 is below threshold (500).
+ long[] rssBefore2 =
+ new long[]{/*totalRSS*/ 9500, /*fileRSS*/ 9500, /*anonRSS*/ 11500, /*swap*/9500};
+ long[] rssAfter2 =
+ new long[]{/*totalRSS*/ 8000, /*fileRSS*/ 8000, /*anonRSS*/ 9000, /*swap*/8000};
+ // Delta between rssAfter1 and rssBefore3 is above threshold (13000).
+ long[] rssBefore3 =
+ new long[]{/*totalRSS*/ 10000, /*fileRSS*/ 18000, /*anonRSS*/ 13000, /*swap*/ 7000};
+ long[] rssAfter3 =
+ new long[]{/*totalRSS*/ 10000, /*fileRSS*/ 11000, /*anonRSS*/ 10000, /*swap*/ 6000};
+ long[] valuesAfter = {};
+ // Process that passes properties.
+ int pid = 1;
+ ProcessRecord processRecord = makeProcessRecord(pid, 2, 3, "p1", "app1");
+
+ // GIVEN we simulate RSS memory before above thresholds and it is the first time 'p1' is
+ // compacted.
+ mProcessDependencies.setRss(rssBefore1);
+ mProcessDependencies.setRssAfterCompaction(rssAfter1); //
+ // WHEN we try to run compaction
+ mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
+ waitForHandler();
+ // THEN process IS compacted.
+ assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull();
+ valuesAfter = mCachedAppOptimizerUnderTest.mLastCompactionStats.get(
+ pid).getRssAfterCompaction();
+ assertThat(valuesAfter).isEqualTo(rssAfter1);
+
+ // WHEN delta is below threshold (500).
+ mProcessDependencies.setRss(rssBefore2);
+ mProcessDependencies.setRssAfterCompaction(rssAfter2);
+ // This is to avoid throttle of compacting too soon.
+ processRecord.lastCompactTime = processRecord.lastCompactTime - 10_000;
+ // WHEN we try to run compaction.
+ mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
+ waitForHandler();
+ // THEN process IS NOT compacted - values after compaction for process 1 should remain the
+ // same as from the last compaction.
+ assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull();
+ valuesAfter = mCachedAppOptimizerUnderTest.mLastCompactionStats.get(
+ pid).getRssAfterCompaction();
+ assertThat(valuesAfter).isEqualTo(rssAfter1);
+
+ // WHEN delta is above threshold (13000).
+ mProcessDependencies.setRss(rssBefore3);
+ mProcessDependencies.setRssAfterCompaction(rssAfter3);
+ // This is to avoid throttle of compacting too soon.
+ processRecord.lastCompactTime = processRecord.lastCompactTime - 10_000;
+ // WHEN we try to run compaction
+ mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
+ waitForHandler();
+ // THEN process IS compacted - values after compaction for process 1 should be updated.
+ assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull();
+ valuesAfter = mCachedAppOptimizerUnderTest.mLastCompactionStats.get(
+ pid).getRssAfterCompaction();
+ assertThat(valuesAfter).isEqualTo(rssAfter3);
+
+ }
+
+ @Test
+ public void processWithAnonRSSTooSmall_notFullCompacted() throws Exception {
+ // Initialize CachedAppOptimizer and set flags to (1) enable compaction, (2) set RSS
+ // throttle to 8000.
+ mCachedAppOptimizerUnderTest.init();
+ setFlag(CachedAppOptimizer.KEY_USE_COMPACTION, "true", true);
+ setFlag(CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "8000", false);
+ initActivityManagerService();
+
+ // Simulate RSS anon memory larger than throttle.
+ long[] rssBelowThreshold =
+ new long[]{/*Total RSS*/ 10000, /*File RSS*/ 10000, /*Anon RSS*/ 7000, /*Swap*/
+ 10000};
+ long[] rssBelowThresholdAfter =
+ new long[]{/*Total RSS*/ 9000, /*File RSS*/ 7000, /*Anon RSS*/ 4000, /*Swap*/
+ 8000};
+ long[] rssAboveThreshold =
+ new long[]{/*Total RSS*/ 10000, /*File RSS*/ 10000, /*Anon RSS*/ 9000, /*Swap*/
+ 10000};
+ long[] rssAboveThresholdAfter =
+ new long[]{/*Total RSS*/ 8000, /*File RSS*/ 9000, /*Anon RSS*/ 6000, /*Swap*/5000};
+ // Process that passes properties.
+ int pid = 1;
+ ProcessRecord processRecord =
+ makeProcessRecord(pid, 2, 3, "p1",
+ "app1");
+
+ // GIVEN we simulate RSS memory before below threshold.
+ mProcessDependencies.setRss(rssBelowThreshold);
+ mProcessDependencies.setRssAfterCompaction(rssBelowThresholdAfter);
+ // WHEN we try to run compaction
+ mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
+ waitForHandler();
+ // THEN process IS NOT compacted.
+ assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNull();
+
+ // GIVEN we simulate RSS memory before above threshold.
+ mProcessDependencies.setRss(rssAboveThreshold);
+ mProcessDependencies.setRssAfterCompaction(rssAboveThresholdAfter);
+ // WHEN we try to run compaction
+ mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
+ waitForHandler();
+ // THEN process IS compacted.
+ assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull();
+ long[] valuesAfter = mCachedAppOptimizerUnderTest.mLastCompactionStats.get(
+ pid).getRssAfterCompaction();
+ assertThat(valuesAfter).isEqualTo(rssAboveThresholdAfter);
+ }
+
+
+ private void setFlag(String key, String value, boolean defaultValue) throws Exception {
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, key, value, defaultValue);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ }
+
+ private void waitForHandler() {
+ Idle idle = new Idle();
+ mCachedAppOptimizerUnderTest.mCompactionHandler.getLooper().getQueue().addIdleHandler(idle);
+ mCachedAppOptimizerUnderTest.mCompactionHandler.post(() -> { });
+ idle.waitForIdle();
+ }
+
+ private void initActivityManagerService() {
+ mAms = new ActivityManagerService(mInjector, mServiceThreadRule.getThread());
+ mAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
+ mAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
+ mAms.mAtmInternal = spy(mAms.mActivityTaskManager.getAtmInternal());
+ mAms.mPackageManagerInt = mPackageManagerInt;
+ }
+
+ private static final class Idle implements MessageQueue.IdleHandler {
+ private boolean mIdle;
+
+ @Override
+ public boolean queueIdle() {
+ synchronized (this) {
+ mIdle = true;
+ notifyAll();
+ }
+ return false;
+ }
+
+ public synchronized void waitForIdle() {
+ while (!mIdle) {
+ try {
+ // Wait with a timeout of 10s.
+ wait(10000);
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+
private class TestInjector extends Injector {
TestInjector(Context context) {
@@ -806,4 +1012,29 @@
return mHandler;
}
}
+
+ // Test implementation for ProcessDependencies.
+ private static final class TestProcessDependencies
+ implements CachedAppOptimizer.ProcessDependencies {
+ private long[] mRss;
+ private long[] mRssAfterCompaction;
+
+ @Override
+ public long[] getRss(int pid) {
+ return mRss;
+ }
+
+ @Override
+ public void performCompaction(String action, int pid) throws IOException {
+ mRss = mRssAfterCompaction;
+ }
+
+ public void setRss(long[] newValues) {
+ mRss = newValues;
+ }
+
+ public void setRssAfterCompaction(long[] newValues) {
+ mRssAfterCompaction = newValues;
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 285caf3..48ec529 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -115,6 +115,8 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
+ resetReceivers();
+
when(mContext.getContentResolver()).thenReturn(mContentResolver);
when(mContext.getResources()).thenReturn(mResources);
when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE))
@@ -147,6 +149,74 @@
}
@Test
+ public void testClientBinderDied_whenPaused() throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+
+ invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ true /* requireConfirmation */, null /* authenticators */);
+ waitForIdle();
+ verify(mReceiver1.asBinder()).linkToDeath(eq(mBiometricService.mCurrentAuthSession),
+ anyInt());
+
+ mBiometricService.mInternalReceiver.onError(
+ getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ BiometricAuthenticator.TYPE_FACE,
+ BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
+ 0 /* vendorCode */);
+ waitForIdle();
+
+ assertEquals(BiometricService.STATE_AUTH_PAUSED,
+ mBiometricService.mCurrentAuthSession.mState);
+
+ mBiometricService.mCurrentAuthSession.binderDied();
+ waitForIdle();
+
+ assertNull(mBiometricService.mCurrentAuthSession);
+ verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
+ verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
+ }
+
+ @Test
+ public void testClientBinderDied_whenAuthenticating() throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+
+ invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ true /* requireConfirmation */, null /* authenticators */);
+ waitForIdle();
+ verify(mReceiver1.asBinder()).linkToDeath(eq(mBiometricService.mCurrentAuthSession),
+ anyInt());
+
+ assertEquals(BiometricService.STATE_AUTH_STARTED,
+ mBiometricService.mCurrentAuthSession.mState);
+ mBiometricService.mCurrentAuthSession.binderDied();
+ waitForIdle();
+
+ assertNotNull(mBiometricService.mCurrentAuthSession);
+ verify(mBiometricService.mStatusBarService, never()).hideAuthenticationDialog();
+ assertEquals(BiometricService.STATE_CLIENT_DIED_CANCELLING,
+ mBiometricService.mCurrentAuthSession.mState);
+
+ verify(mBiometricService.mAuthenticators.get(0).impl).cancelAuthenticationFromService(
+ any(),
+ any(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ eq(false) /* fromClient */);
+
+ // Simulate ERROR_CANCELED received from HAL
+ mBiometricService.mInternalReceiver.onError(
+ getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+ BiometricAuthenticator.TYPE_FACE,
+ BiometricConstants.BIOMETRIC_ERROR_CANCELED,
+ 0 /* vendorCode */);
+ waitForIdle();
+ verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
+ verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
+ assertNull(mBiometricService.mCurrentAuthSession);
+ }
+
+ @Test
public void testAuthenticate_credentialAllowedButNotSetup_returnsNoDeviceCredential()
throws Exception {
when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(false);
@@ -311,7 +381,7 @@
eq(0 /* vendorCode */));
// Enrolled, not disabled in settings, user requires confirmation in settings
- resetReceiver();
+ resetReceivers();
when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true);
when(mBiometricService.mSettingObserver.getFaceAlwaysRequireConfirmation(anyInt()))
.thenReturn(true);
@@ -332,7 +402,7 @@
anyInt() /* callingUserId */);
// Enrolled, not disabled in settings, user doesn't require confirmation in settings
- resetReceiver();
+ resetReceivers();
when(mBiometricService.mSettingObserver.getFaceAlwaysRequireConfirmation(anyInt()))
.thenReturn(false);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
@@ -1198,7 +1268,7 @@
eq(0) /* vendorCode */);
// Request for weak auth works
- resetReceiver();
+ resetReceivers();
authenticators = Authenticators.BIOMETRIC_WEAK;
assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
invokeCanAuthenticate(mBiometricService, authenticators));
@@ -1217,7 +1287,7 @@
anyInt() /* sysUiSessionId */);
// Requesting strong and credential, when credential is setup
- resetReceiver();
+ resetReceivers();
authenticators = Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL;
when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
@@ -1244,7 +1314,7 @@
}
}
- resetReceiver();
+ resetReceivers();
authenticators = Authenticators.BIOMETRIC_STRONG;
assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
invokeCanAuthenticate(mBiometricService, authenticators));
@@ -1449,9 +1519,12 @@
}
}
- private void resetReceiver() {
+ private void resetReceivers() {
mReceiver1 = mock(IBiometricServiceReceiver.class);
mReceiver2 = mock(IBiometricServiceReceiver.class);
+
+ when(mReceiver1.asBinder()).thenReturn(mock(Binder.class));
+ when(mReceiver2.asBinder()).thenReturn(mock(Binder.class));
}
private void resetStatusBar() {
diff --git a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
index 03d9ad5..30ff119 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
@@ -34,6 +34,7 @@
import android.app.usage.UsageStatsManagerInternal;
import android.content.Context;
import android.content.LocusId;
+import android.util.ArrayMap;
import androidx.test.InstrumentationRegistry;
@@ -196,39 +197,42 @@
}
@Test
- public void testQueryAppLaunchCount() {
-
- UsageStats packageStats1 = createUsageStats(PKG_NAME_1, 2);
- UsageStats packageStats2 = createUsageStats(PKG_NAME_1, 3);
- UsageStats packageStats3 = createUsageStats(PKG_NAME_2, 1);
+ public void testQueryAppUsageStats() {
+ UsageStats packageStats1 = createUsageStats(PKG_NAME_1, 2, createDummyChooserCounts());
+ UsageStats packageStats2 = createUsageStats(PKG_NAME_1, 3, null);
+ UsageStats packageStats3 = createUsageStats(PKG_NAME_2, 1, createDummyChooserCounts());
when(mUsageStatsManagerInternal.queryUsageStatsForUser(anyInt(), anyInt(), anyLong(),
anyLong(), anyBoolean())).thenReturn(
List.of(packageStats1, packageStats2, packageStats3));
- Map<String, Integer> appLaunchCounts = mHelper.queryAppLaunchCount(USER_ID_PRIMARY, 90_000L,
- 200_000L, Set.of(PKG_NAME_1, PKG_NAME_2));
+ Map<String, AppUsageStatsData> appLaunchChooserCountCounts =
+ mHelper.queryAppUsageStats(USER_ID_PRIMARY, 90_000L,
+ 200_000L, Set.of(PKG_NAME_1, PKG_NAME_2));
- assertEquals(2, appLaunchCounts.size());
- assertEquals(5, (long) appLaunchCounts.get(PKG_NAME_1));
- assertEquals(1, (long) appLaunchCounts.get(PKG_NAME_2));
+ assertEquals(2, appLaunchChooserCountCounts.size());
+ assertEquals(4, (long) appLaunchChooserCountCounts.get(PKG_NAME_1).getChosenCount());
+ assertEquals(5, (long) appLaunchChooserCountCounts.get(PKG_NAME_1).getLaunchCount());
+ assertEquals(4, (long) appLaunchChooserCountCounts.get(PKG_NAME_2).getChosenCount());
+ assertEquals(1, (long) appLaunchChooserCountCounts.get(PKG_NAME_2).getLaunchCount());
}
@Test
- public void testQueryAppLaunchCount_packageNameFiltered() {
-
- UsageStats packageStats1 = createUsageStats(PKG_NAME_1, 2);
- UsageStats packageStats2 = createUsageStats(PKG_NAME_1, 3);
- UsageStats packageStats3 = createUsageStats(PKG_NAME_2, 1);
+ public void testQueryAppUsageStats_packageNameFiltered() {
+ UsageStats packageStats1 = createUsageStats(PKG_NAME_1, 2, createDummyChooserCounts());
+ UsageStats packageStats2 = createUsageStats(PKG_NAME_1, 3, createDummyChooserCounts());
+ UsageStats packageStats3 = createUsageStats(PKG_NAME_2, 1, null);
when(mUsageStatsManagerInternal.queryUsageStatsForUser(anyInt(), anyInt(), anyLong(),
anyLong(), anyBoolean())).thenReturn(
List.of(packageStats1, packageStats2, packageStats3));
- Map<String, Integer> appLaunchCounts = mHelper.queryAppLaunchCount(USER_ID_PRIMARY, 90_000L,
- 200_000L,
- Set.of(PKG_NAME_1));
+ Map<String, AppUsageStatsData> appLaunchChooserCountCounts =
+ mHelper.queryAppUsageStats(USER_ID_PRIMARY, 90_000L,
+ 200_000L,
+ Set.of(PKG_NAME_1));
- assertEquals(1, appLaunchCounts.size());
- assertEquals(5, (long) appLaunchCounts.get(PKG_NAME_1));
+ assertEquals(1, appLaunchChooserCountCounts.size());
+ assertEquals(8, (long) appLaunchChooserCountCounts.get(PKG_NAME_1).getChosenCount());
+ assertEquals(5, (long) appLaunchChooserCountCounts.get(PKG_NAME_1).getLaunchCount());
}
private void addUsageEvents(UsageEvents.Event... events) {
@@ -237,13 +241,27 @@
anyInt())).thenReturn(usageEvents);
}
- private static UsageStats createUsageStats(String packageName, int launchCount) {
+ private static UsageStats createUsageStats(String packageName, int launchCount,
+ ArrayMap<String, ArrayMap<String, Integer>> chooserCounts) {
UsageStats packageStats = new UsageStats();
packageStats.mPackageName = packageName;
packageStats.mAppLaunchCount = launchCount;
+ packageStats.mChooserCounts = chooserCounts;
return packageStats;
}
+ private static ArrayMap<String, ArrayMap<String, Integer>> createDummyChooserCounts() {
+ ArrayMap<String, ArrayMap<String, Integer>> chooserCounts = new ArrayMap<>();
+ ArrayMap<String, Integer> counts1 = new ArrayMap<>();
+ counts1.put("text", 2);
+ counts1.put("image", 1);
+ chooserCounts.put("intent1", counts1);
+ ArrayMap<String, Integer> counts2 = new ArrayMap<>();
+ counts2.put("video", 1);
+ chooserCounts.put("intent2", counts2);
+ return chooserCounts;
+ }
+
private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
LocalServices.removeServiceForTest(clazz);
LocalServices.addService(clazz, mock);
diff --git a/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java b/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java
index 9fc1776..45fff48 100644
--- a/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java
@@ -31,6 +31,7 @@
import android.os.UserHandle;
import android.util.Range;
+import com.android.server.people.data.AppUsageStatsData;
import com.android.server.people.data.DataManager;
import com.android.server.people.data.Event;
import com.android.server.people.data.EventHistory;
@@ -257,6 +258,39 @@
}
@Test
+ public void testComputeScoreForAppShare_promoteFrequentlyChosenApps() {
+ when(mEventHistory1.getEventIndex(anySet())).thenReturn(mEventIndex1);
+ when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2);
+ when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3);
+ when(mEventHistory4.getEventIndex(anySet())).thenReturn(mEventIndex4);
+ when(mEventHistory5.getEventIndex(anySet())).thenReturn(mEventIndex5);
+ when(mEventHistory1.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex6);
+ when(mEventHistory2.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex7);
+ when(mEventHistory3.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex8);
+ when(mEventHistory4.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex9);
+ when(mEventHistory5.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex10);
+ when(mDataManager.queryAppUsageStats(anyInt(), anyLong(), anyLong(), anySet()))
+ .thenReturn(
+ Map.of(PACKAGE_1, new AppUsageStatsData(1, 0),
+ PACKAGE_2, new AppUsageStatsData(2, 0),
+ PACKAGE_3, new AppUsageStatsData(3, 0)));
+
+ SharesheetModelScorer.computeScoreForAppShare(
+ List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
+ mShareTarget6),
+ Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+
+ verify(mDataManager, times(1)).queryAppUsageStats(anyInt(), anyLong(), anyLong(),
+ anySet());
+ assertEquals(0.9f, mShareTarget5.getScore(), DELTA);
+ assertEquals(0.6f, mShareTarget3.getScore(), DELTA);
+ assertEquals(0.3f, mShareTarget1.getScore(), DELTA);
+ assertEquals(0f, mShareTarget2.getScore(), DELTA);
+ assertEquals(0f, mShareTarget4.getScore(), DELTA);
+ assertEquals(0f, mShareTarget6.getScore(), DELTA);
+ }
+
+ @Test
public void testComputeScoreForAppShare_promoteFrequentlyUsedApps() {
when(mEventHistory1.getEventIndex(anySet())).thenReturn(mEventIndex1);
when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2);
@@ -268,22 +302,22 @@
when(mEventHistory3.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex8);
when(mEventHistory4.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex9);
when(mEventHistory5.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex10);
- when(mDataManager.queryAppLaunchCount(anyInt(), anyLong(), anyLong(), anySet()))
+ when(mDataManager.queryAppUsageStats(anyInt(), anyLong(), anyLong(), anySet()))
.thenReturn(
- Map.of(PACKAGE_1, 1,
- PACKAGE_2, 2,
- PACKAGE_3, 3));
+ Map.of(PACKAGE_1, new AppUsageStatsData(0, 1),
+ PACKAGE_2, new AppUsageStatsData(0, 2),
+ PACKAGE_3, new AppUsageStatsData(1, 0)));
SharesheetModelScorer.computeScoreForAppShare(
List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
mShareTarget6),
Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
- verify(mDataManager, times(1)).queryAppLaunchCount(anyInt(), anyLong(), anyLong(),
+ verify(mDataManager, times(1)).queryAppUsageStats(anyInt(), anyLong(), anyLong(),
anySet());
- assertEquals(0.3f, mShareTarget5.getScore(), DELTA);
+ assertEquals(0.9f, mShareTarget5.getScore(), DELTA);
assertEquals(0.27f, mShareTarget3.getScore(), DELTA);
- assertEquals(0.243f, mShareTarget1.getScore(), DELTA);
+ assertEquals(0.135f, mShareTarget1.getScore(), DELTA);
assertEquals(0f, mShareTarget2.getScore(), DELTA);
assertEquals(0f, mShareTarget4.getScore(), DELTA);
assertEquals(0f, mShareTarget6.getScore(), DELTA);
@@ -306,18 +340,19 @@
when(mEventIndex3.getMostRecentActiveTimeSlot()).thenReturn(FIVE_DAYS_AGO);
when(mEventIndex4.getMostRecentActiveTimeSlot()).thenReturn(EIGHT_DAYS_AGO);
when(mEventIndex5.getMostRecentActiveTimeSlot()).thenReturn(null);
- when(mDataManager.queryAppLaunchCount(anyInt(), anyLong(), anyLong(), anySet()))
+ when(mDataManager.queryAppUsageStats(anyInt(), anyLong(), anyLong(), anySet()))
.thenReturn(
- Map.of(PACKAGE_1, 1,
- PACKAGE_2, 2,
- PACKAGE_3, 3));
+ Map.of(PACKAGE_1, new AppUsageStatsData(0, 1),
+ PACKAGE_2, new AppUsageStatsData(0, 2),
+ PACKAGE_3, new AppUsageStatsData(1, 0)));
SharesheetModelScorer.computeScoreForAppShare(
List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
mShareTarget6),
Event.TYPE_SHARE_TEXT, 4, NOW, mDataManager, USER_ID);
- verify(mDataManager, never()).queryAppLaunchCount(anyInt(), anyLong(), anyLong(), anySet());
+ verify(mDataManager, never()).queryAppUsageStats(anyInt(), anyLong(), anyLong(),
+ anySet());
assertEquals(0.4f, mShareTarget1.getScore(), DELTA);
assertEquals(0.35f, mShareTarget2.getScore(), DELTA);
assertEquals(0.33f, mShareTarget3.getScore(), DELTA);
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 6a88298..e4acdfe 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -720,6 +720,8 @@
protected static final long INTERVAL = 10000;
+ // This doesn't need to match the max shortcuts limit in the framework, and tests should either
+ // use this or set their own limit for testing, without assuming any particular max value.
protected static final int MAX_SHORTCUTS = 10;
protected static final int MAX_UPDATES_PER_INTERVAL = 3;
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 320dacf..db02524 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -234,7 +234,7 @@
assertEquals(ShortcutService.DEFAULT_RESET_INTERVAL_SEC * 1000,
mService.getResetIntervalForTest());
- assertEquals(ShortcutService.DEFAULT_MAX_SHORTCUTS_PER_APP,
+ assertEquals(ShortcutService.DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY,
mService.getMaxShortcutsForTest());
assertEquals(ShortcutService.DEFAULT_MAX_UPDATES_PER_INTERVAL,
@@ -393,6 +393,8 @@
}
public void testPushDynamicShortcut() {
+ // Change the max number of shortcuts.
+ mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5");
setCaller(CALLING_PACKAGE_1, USER_0);
@@ -400,12 +402,11 @@
final ShortcutInfo s2 = makeShortcut("s2");
final ShortcutInfo s3 = makeShortcut("s3");
final ShortcutInfo s4 = makeShortcut("s4");
-
- final ShortcutInfo s10 = makeShortcut("s10");
- final ShortcutInfo s11 = makeShortcut("s11");
- final ShortcutInfo s12 = makeShortcut("s12");
- final ShortcutInfo s13 = makeShortcut("s13");
- final ShortcutInfo s14 = makeShortcut("s14");
+ final ShortcutInfo s5 = makeShortcut("s5");
+ final ShortcutInfo s6 = makeShortcut("s6");
+ final ShortcutInfo s7 = makeShortcut("s7");
+ final ShortcutInfo s8 = makeShortcut("s8");
+ final ShortcutInfo s9 = makeShortcut("s9");
// Test push as first shortcut
mManager.pushDynamicShortcut(s1);
@@ -440,63 +441,61 @@
mInjectedCurrentTimeMillis += INTERVAL; // reset
// Test push as last
- assertTrue(mManager.addDynamicShortcuts(makeShortcuts("s5", "s6", "s7", "s8", "s9")));
- mManager.pushDynamicShortcut(s10);
+ mManager.pushDynamicShortcut(s5);
assertShortcutIds(assertAllNotKeyFieldsOnly(mManager.getDynamicShortcuts()),
- "s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10");
- assertEquals(0, getCallerShortcut("s10").getRank());
- assertEquals(1, getCallerShortcut("s5").getRank());
- assertEquals(6, getCallerShortcut("s3").getRank());
- assertEquals(7, getCallerShortcut("s1").getRank());
- assertEquals(8, getCallerShortcut("s2").getRank());
- assertEquals(9, getCallerShortcut("s4").getRank());
+ "s1", "s2", "s3", "s4", "s5");
+ assertEquals(0, getCallerShortcut("s5").getRank());
+ assertEquals(1, getCallerShortcut("s3").getRank());
+ assertEquals(2, getCallerShortcut("s1").getRank());
+ assertEquals(3, getCallerShortcut("s2").getRank());
+ assertEquals(4, getCallerShortcut("s4").getRank());
// Push when max has already reached
- mManager.pushDynamicShortcut(s11);
+ mManager.pushDynamicShortcut(s6);
assertShortcutIds(assertAllNotKeyFieldsOnly(mManager.getDynamicShortcuts()),
- "s1", "s2", "s3", "s5", "s6", "s7", "s8", "s9", "s10", "s11");
- assertEquals(0, getCallerShortcut("s11").getRank());
- assertEquals(1, getCallerShortcut("s10").getRank());
- assertEquals(9, getCallerShortcut("s2").getRank());
+ "s1", "s2", "s3", "s5", "s6");
+ assertEquals(0, getCallerShortcut("s6").getRank());
+ assertEquals(1, getCallerShortcut("s5").getRank());
+ assertEquals(4, getCallerShortcut("s2").getRank());
mInjectedCurrentTimeMillis += INTERVAL; // reset
// Push with different activity
- s12.setActivity(makeComponent(ShortcutActivity2.class));
- mManager.pushDynamicShortcut(s12);
+ s7.setActivity(makeComponent(ShortcutActivity2.class));
+ mManager.pushDynamicShortcut(s7);
assertEquals(makeComponent(ShortcutActivity2.class),
- getCallerShortcut("s12").getActivity());
- assertEquals(0, getCallerShortcut("s12").getRank());
+ getCallerShortcut("s7").getActivity());
+ assertEquals(0, getCallerShortcut("s7").getRank());
// Push to update shortcut with different activity
final ShortcutInfo s1_2 = makeShortcut("s1");
s1_2.setActivity(makeComponent(ShortcutActivity2.class));
s1_2.setRank(1);
mManager.pushDynamicShortcut(s1_2);
- assertEquals(0, getCallerShortcut("s12").getRank());
+ assertEquals(0, getCallerShortcut("s7").getRank());
assertEquals(1, getCallerShortcut("s1").getRank());
- assertEquals(0, getCallerShortcut("s11").getRank());
- assertEquals(1, getCallerShortcut("s10").getRank());
- assertEquals(7, getCallerShortcut("s3").getRank());
- assertEquals(8, getCallerShortcut("s2").getRank());
+ assertEquals(0, getCallerShortcut("s6").getRank());
+ assertEquals(1, getCallerShortcut("s5").getRank());
+ assertEquals(2, getCallerShortcut("s3").getRank());
+ assertEquals(3, getCallerShortcut("s2").getRank());
mInjectedCurrentTimeMillis += INTERVAL; // reset
// Test push when dropped shortcut is cached
- s13.setLongLived();
- s13.setRank(100);
- mManager.pushDynamicShortcut(s13);
- assertEquals(9, getCallerShortcut("s13").getRank());
+ s8.setLongLived();
+ s8.setRank(100);
+ mManager.pushDynamicShortcut(s8);
+ assertEquals(4, getCallerShortcut("s8").getRank());
runWithCaller(LAUNCHER_1, USER_0, () -> {
- mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s13"), HANDLE_USER_0);
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s8"), HANDLE_USER_0);
});
- mManager.pushDynamicShortcut(s14);
+ mManager.pushDynamicShortcut(s9);
assertShortcutIds(assertAllNotKeyFieldsOnly(mManager.getDynamicShortcuts()),
- "s1", "s2", "s3", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "s12", "s14");
+ "s1", "s2", "s3", "s5", "s6", "s7", "s9");
// Verify s13 stayed as cached
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
- "s13");
+ "s8");
}
public void testUnlimitedCalls() {
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest11.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest11.java
index 50d290a..6219665 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest11.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest11.java
@@ -30,6 +30,8 @@
import android.content.pm.ShortcutInfo;
import android.os.test.TestLooper;
+import com.android.server.pm.ShortcutService.ConfigConstants;
+
import org.mockito.ArgumentCaptor;
import java.util.List;
@@ -372,9 +374,11 @@
}
public void testShortcutChangeCallback_pushDynamicShortcut_existingId() {
+ // Change the max number of shortcuts.
+ mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3");
+
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts((makeShortcuts("s1", "s2", "s3", "s4", "s5",
- "s6", "s7", "s8", "s9", "s10"))));
+ assertTrue(mManager.setDynamicShortcuts((makeShortcuts("s1", "s2", "s3"))));
});
ShortcutChangeCallback callback = mock(ShortcutChangeCallback.class);
@@ -384,7 +388,7 @@
});
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- mManager.pushDynamicShortcut(makeShortcut("s5"));
+ mManager.pushDynamicShortcut(makeShortcut("s2"));
});
mTestLooper.dispatchAll();
@@ -396,13 +400,15 @@
assertWith(shortcuts.getValue())
.areAllWithKeyFieldsOnly()
- .haveIds("s5");
+ .haveIds("s2");
}
public void testShortcutChangeCallback_pushDynamicShortcut_causeDeletion() {
+ // Change the max number of shortcuts.
+ mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3");
+
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts((makeShortcuts("s1", "s2", "s3", "s4", "s5",
- "s6", "s7", "s8", "s9", "s10"))));
+ assertTrue(mManager.setDynamicShortcuts((makeShortcuts("s1", "s2", "s3"))));
});
ShortcutChangeCallback callback = mock(ShortcutChangeCallback.class);
@@ -412,7 +418,7 @@
});
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- mManager.pushDynamicShortcut(makeShortcut("s11"));
+ mManager.pushDynamicShortcut(makeShortcut("s4"));
});
mTestLooper.dispatchAll();
@@ -427,31 +433,33 @@
assertWith(changedShortcuts.getValue())
.areAllWithKeyFieldsOnly()
- .haveIds("s11");
+ .haveIds("s4");
assertWith(removedShortcuts.getValue())
.areAllWithKeyFieldsOnly()
- .haveIds("s10");
+ .haveIds("s3");
}
public void testShortcutChangeCallback_pushDynamicShortcut_causeDeletionButCached() {
+ // Change the max number of shortcuts.
+ mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3");
+
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts((makeShortcuts("s1", "s2", "s3", "s4", "s5",
- "s6", "s7", "s8", "s9"))));
- ShortcutInfo s10 = makeLongLivedShortcut("s10");
- s10.setRank(10);
- mManager.pushDynamicShortcut(s10); // Add a long lived shortcut to the end of the list.
+ assertTrue(mManager.setDynamicShortcuts((makeShortcuts("s1", "s2"))));
+ ShortcutInfo s3 = makeLongLivedShortcut("s3");
+ s3.setRank(3);
+ mManager.pushDynamicShortcut(s3); // Add a long lived shortcut to the end of the list.
});
ShortcutChangeCallback callback = mock(ShortcutChangeCallback.class);
runWithCaller(LAUNCHER_1, USER_0, () -> {
- mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s10"), HANDLE_USER_0);
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0);
mLauncherApps.registerShortcutChangeCallback(callback, QUERY_MATCH_ALL,
mTestLooper.getNewExecutor());
});
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- mManager.pushDynamicShortcut(makeShortcut("s11"));
+ mManager.pushDynamicShortcut(makeShortcut("s4"));
});
mTestLooper.dispatchAll();
@@ -463,7 +471,7 @@
assertWith(shortcuts.getValue())
.areAllWithKeyFieldsOnly()
- .haveIds("s10", "s11");
+ .haveIds("s3", "s4");
}
public void testShortcutChangeCallback_disableShortcuts() {
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
index 086c845..0f028f0 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
@@ -23,7 +23,6 @@
import android.content.pm.FeatureInfo
import android.content.pm.InstrumentationInfo
import android.content.pm.PackageInfo
-import android.content.pm.PackageManager
import android.content.pm.PackageParser
import android.content.pm.PackageUserState
import android.content.pm.PermissionInfo
@@ -38,7 +37,6 @@
import com.android.server.pm.pkg.PackageStateUnserialized
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever
-import org.junit.After
import org.junit.BeforeClass
import org.mockito.Mockito
import org.mockito.Mockito.anyInt
@@ -49,7 +47,7 @@
companion object {
- private const val VERIFY_ALL_APKS = true
+ private const val VERIFY_ALL_APKS = false
/** For auditing memory usage differences */
private const val DUMP_HPROF_TO_EXTERNAL = false
@@ -93,21 +91,25 @@
lateinit var newPackages: List<AndroidPackage>
- private val thrownInSetUp = mutableListOf<Throwable>()
-
@Suppress("ConstantConditionIf")
@JvmStatic
@BeforeClass
fun setUpPackages() {
this.oldPackages = apks.mapNotNull {
- tryOrNull {
+ try {
packageParser.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false)
+ } catch (ignored: Exception) {
+ // Parsing issues will be caught by SystemPartitionParseTest
+ null
}
}
this.newPackages = apks.mapNotNull {
- tryOrNull {
+ try {
packageParser2.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false)
+ } catch (ignored: Exception) {
+ // Parsing issues will be caught by SystemPartitionParseTest
+ null
}
}
@@ -144,41 +146,6 @@
this.pkg = aPkg
whenever(pkgState) { PackageStateUnserialized() }
}
-
- private fun <T> tryOrNull(block: () -> T) = try {
- block()
- } catch (e: PackageParser.PackageParserException) {
- if (e.error != PackageManager.INSTALL_PARSE_FAILED_SKIPPED) {
- thrownInSetUp.add(e)
- }
- null
- } catch (t: Throwable) {
- thrownInSetUp.add(t)
- null
- }
- }
-
- @After
- fun verifySetUpPackages() {
- if (thrownInSetUp.isEmpty()) return
- val exception = AssertionError("setUpPackages failed with ${thrownInSetUp.size} errors:\n" +
- thrownInSetUp.joinToString(separator = "\n") { it.message.orEmpty() })
-
- /*
- Testing infrastructure doesn't currently support errors thrown in @AfterClass,
- so instead it's thrown here. But to avoid throwing a massive repeated stack for every
- test method, only throw on the first method run in the class, clearing the list so that
- subsequent methods can run without failing. Doing this in @After lets true method
- failures propagate, as those should throw before this does.
-
- This will cause the failure to be attached to a different method depending on run order,
- which could make comparisons difficult. So if a failure points here, it's worth
- checking failures for all methods in all subclasses.
-
- TODO: When infrastructure supports @AfterClass errors, move this
- */
- thrownInSetUp.clear()
- throw exception
}
// The following methods dump an exact set of fields from the object to compare, because
@@ -285,7 +252,8 @@
secondaryCpuAbi=${this.secondaryCpuAbi}
secondaryNativeLibraryDir=${this.secondaryNativeLibraryDir}
sourceDir=${this.sourceDir}
- splitDependencies=${this.splitDependencies.sequence().map { it.first to it.second?.contentToString() }.joinToString()}
+ splitDependencies=${this.splitDependencies.sequence()
+ .map { it.first to it.second?.contentToString() }.joinToString()}
splitNames=${this.splitNames?.contentToString()}
splitPublicSourceDirs=${this.splitPublicSourceDirs?.contentToString()}
splitSourceDirs=${this.splitSourceDirs?.contentToString()}
@@ -348,7 +316,9 @@
initOrder=${this.initOrder}
isSyncable=${this.isSyncable}
multiprocess=${this.multiprocess}
- pathPermissions=${this.pathPermissions?.joinToString { "readPermission=${it.readPermission}\nwritePermission=${it.writePermission}" }}
+ pathPermissions=${this.pathPermissions?.joinToString {
+ "readPermission=${it.readPermission}\nwritePermission=${it.writePermission}"
+ }}
readPermission=${this.readPermission}
uriPermissionPatterns=${this.uriPermissionPatterns?.contentToString()}
writePermission=${this.writePermission}
@@ -370,7 +340,9 @@
compileSdkVersionCodename=${this.compileSdkVersionCodename}
configPreferences=${this.configPreferences?.joinToString { it.dumpToString() }}
coreApp=${this.coreApp}
- featureGroups=${this.featureGroups?.joinToString { it.features?.joinToString { featureInfo -> featureInfo.dumpToString() }.orEmpty() }}
+ featureGroups=${this.featureGroups?.joinToString {
+ it.features?.joinToString { featureInfo -> featureInfo.dumpToString() }.orEmpty()
+ }}
firstInstallTime=${this.firstInstallTime}
gids=${gids?.contentToString()}
installLocation=${this.installLocation}
@@ -396,7 +368,8 @@
sharedUserId=${this.sharedUserId}
sharedUserLabel=${this.sharedUserLabel}
signatures=${this.signatures?.joinToString { it.toCharsString() }}
- signingInfo=${this.signingInfo?.signingCertificateHistory?.joinToString { it.toCharsString() }.orEmpty()}
+ signingInfo=${this.signingInfo?.signingCertificateHistory
+ ?.joinToString { it.toCharsString() }.orEmpty()}
splitNames=${this.splitNames?.contentToString()}
splitRevisionCodes=${this.splitRevisionCodes?.contentToString()}
targetOverlayableName=${this.targetOverlayableName}
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
new file mode 100644
index 0000000..605841d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2020 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.pm.parsing
+
+import android.content.pm.PackageManager
+import android.content.pm.PackageParser
+import android.platform.test.annotations.Postsubmit
+import com.android.server.pm.PackageManagerService
+import org.junit.Test
+
+/**
+ * This test parses all the system APKs on the device image to ensure that they succeed.
+ *
+ * Any invalid APKs should be removed from the device or marked as skipped through any mechanism
+ * for ignoring packages.
+ *
+ * This test must run on deferred postsubmit. Targeted presubmit will not catch errors fast enough,
+ * and the low failure rate does not warrant global presubmit.
+ */
+@Postsubmit
+class SystemPartitionParseTest {
+
+ private val APKS = PackageManagerService.SYSTEM_PARTITIONS
+ .flatMap { listOfNotNull(it.appFolder, it.privAppFolder, it.overlayFolder) }
+ .flatMap {
+ it.walkTopDown()
+ .filter { it.name.endsWith(".apk") }
+ .toList()
+ }
+ .distinct()
+
+ private val parser = PackageParser2.forParsingFileWithDefaults()
+
+ @Test
+ fun verify() {
+ val exceptions = APKS
+ .map {
+ runCatching {
+ parser.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false)
+ }
+ }
+ .mapNotNull { it.exceptionOrNull() }
+ .filterNot { (it as? PackageParser.PackageParserException)?.error ==
+ PackageManager.INSTALL_PARSE_FAILED_SKIPPED }
+
+ if (exceptions.isEmpty()) return
+
+ throw AssertionError("verify failed with ${exceptions.size} errors:\n" +
+ exceptions.joinToString(separator = "\n") { it.message.orEmpty() })
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 4da5adf..063568d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1405,14 +1405,14 @@
mActivity.setVisibility(true);
display.rotateInDifferentOrientationIfNeeded(mActivity);
- display.mFixedRotationLaunchingApp = mActivity;
+ display.setFixedRotationLaunchingAppUnchecked(mActivity);
displayRotation.updateRotationUnchecked(true /* forceUpdate */);
assertTrue(displayRotation.isRotatingSeamlessly());
// The launching rotated app should not be cleared when waiting for remote rotation.
display.continueUpdateOrientationForDiffOrienLaunchingApp();
- assertNotNull(display.mFixedRotationLaunchingApp);
+ assertNotNull(display.getFixedRotationLaunchingApp());
// Simulate the rotation has been updated to previous one, e.g. sensor updates before the
// remote rotation is completed.
@@ -1438,10 +1438,10 @@
displayRotation.updateRotationUnchecked(true /* forceUpdate */);
doReturn(false).when(displayRotation).isWaitingForRemoteRotation();
clearInvocations(mActivity);
- display.mFixedRotationLaunchingApp = mActivity;
+ display.setFixedRotationLaunchingAppUnchecked(mActivity);
display.sendNewConfiguration();
- assertNull(display.mFixedRotationLaunchingApp);
+ assertNull(display.getFixedRotationLaunchingApp());
assertFalse(mActivity.hasFixedRotationTransform());
}
@@ -1497,7 +1497,7 @@
// rotation should be applied when creating snapshot surface if the display rotation may be
// changed according to the activity orientation.
assertTrue(mActivity.hasFixedRotationTransform());
- assertEquals(mActivity, mActivity.mDisplayContent.mFixedRotationLaunchingApp);
+ assertEquals(mActivity, mActivity.mDisplayContent.getFixedRotationLaunchingApp());
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 4b43ceb..bd616a3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -140,6 +140,12 @@
public void onDisplayRemoved(int displayId) {
removed.add(displayId);
}
+
+ @Override
+ public void onFixedRotationStarted(int displayId, int newRotation) {}
+
+ @Override
+ public void onFixedRotationFinished(int displayId) {}
};
mService.mWindowManager.registerDisplayWindowListener(listener);
// Check that existing displays call added
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 7b23bfb..ac95a81 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -57,7 +57,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
@@ -1060,8 +1059,6 @@
@Test
public void testApplyTopFixedRotationTransform() {
mWm.mIsFixedRotationTransformEnabled = true;
- mDisplayContent.getDisplayPolicy().addWindowLw(mStatusBarWindow, mStatusBarWindow.mAttrs);
- mDisplayContent.getDisplayPolicy().addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs);
final Configuration config90 = new Configuration();
mDisplayContent.computeScreenConfiguration(config90, ROTATION_90);
@@ -1082,12 +1079,6 @@
ROTATION_0 /* oldRotation */, ROTATION_90 /* newRotation */,
false /* forceUpdate */));
- assertNotNull(mDisplayContent.mFixedRotationAnimationController);
- assertTrue(mStatusBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS,
- ANIMATION_TYPE_FIXED_TRANSFORM));
- assertTrue(mNavBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS,
- ANIMATION_TYPE_FIXED_TRANSFORM));
-
final Rect outFrame = new Rect();
final Rect outInsets = new Rect();
final Rect outStableInsets = new Rect();
@@ -1140,9 +1131,6 @@
assertFalse(app.hasFixedRotationTransform());
assertFalse(app2.hasFixedRotationTransform());
assertEquals(config90.orientation, mDisplayContent.getConfiguration().orientation);
-
- mDisplayContent.finishFixedRotationAnimation();
- assertNull(mDisplayContent.mFixedRotationAnimationController);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 209db62..f330f0f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -343,7 +343,7 @@
initializeRecentsAnimationController(mController, homeActivity);
- assertEquals(homeActivity, mDefaultDisplay.mFixedRotationLaunchingApp);
+ assertEquals(homeActivity, mDefaultDisplay.getFixedRotationLaunchingApp());
// Check that the home app is in portrait
assertEquals(Configuration.ORIENTATION_PORTRAIT,
@@ -353,7 +353,7 @@
// top rotated record should be cleared.
mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
assertFalse(homeActivity.hasFixedRotationTransform());
- assertNull(mDefaultDisplay.mFixedRotationLaunchingApp);
+ assertNull(mDefaultDisplay.getFixedRotationLaunchingApp());
}
@Test
@@ -367,7 +367,7 @@
(mDefaultDisplay.getRotation() + 1) % 4);
assertTrue(activity.hasFixedRotationTransform());
- assertEquals(activity, mDefaultDisplay.mFixedRotationLaunchingApp);
+ assertEquals(activity, mDefaultDisplay.getFixedRotationLaunchingApp());
// Before the transition is done, the recents animation is triggered.
initializeRecentsAnimationController(mController, homeActivity);
@@ -377,7 +377,7 @@
mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
// The rotation transform should be cleared after updating orientation with display.
assertFalse(activity.hasFixedRotationTransform());
- assertNull(mDefaultDisplay.mFixedRotationLaunchingApp);
+ assertNull(mDefaultDisplay.getFixedRotationLaunchingApp());
}
@Test
@@ -436,7 +436,7 @@
// The transform state should keep because we expect to listen the signal from the
// transition executed by moving the task to front.
assertTrue(homeActivity.hasFixedRotationTransform());
- assertEquals(homeActivity, mDefaultDisplay.mFixedRotationLaunchingApp);
+ assertEquals(homeActivity, mDefaultDisplay.getFixedRotationLaunchingApp());
mDefaultDisplay.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(
homeActivity.token);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index fdc5c7b..71dabc5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -758,6 +758,28 @@
}
@Test
+ public void testBLASTCallbackNoDoubleAdd() {
+ final ActivityStack stackController1 = createStack();
+ final Task task = createTask(stackController1);
+ final ITaskOrganizer organizer = registerMockOrganizer();
+ final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window");
+ makeWindowVisible(w);
+
+ BLASTSyncEngine bse = new BLASTSyncEngine();
+
+ BLASTSyncEngine.TransactionReadyListener transactionListener =
+ mock(BLASTSyncEngine.TransactionReadyListener.class);
+
+ int id = bse.startSyncSet(transactionListener);
+ assertTrue(bse.addToSyncSet(id, w));
+ assertFalse(bse.addToSyncSet(id, w));
+
+ // Clean-up
+ bse.setReady(id);
+ }
+
+
+ @Test
public void testBLASTCallbackWithInvisibleWindow() {
final ActivityStack stackController1 = createStack();
final Task task = createTask(stackController1);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 0346329..8ce5daa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -270,6 +270,26 @@
}
@Test
+ public void testCanWindowWithEmbeddedDisplayBeImeTarget() {
+ final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
+ final WindowState imeWindow = createWindow(null, TYPE_INPUT_METHOD, "imeWindow");
+
+ imeWindow.setHasSurface(true);
+ appWindow.setHasSurface(true);
+
+ appWindow.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
+ assertFalse(appWindow.canBeImeTarget());
+
+ DisplayContent secondDisplay = createNewDisplay();
+ final WindowState embeddedWindow = createWindow(null, TYPE_APPLICATION, secondDisplay,
+ "embeddedWindow");
+ appWindow.addEmbeddedDisplayContent(secondDisplay);
+ embeddedWindow.setHasSurface(true);
+ embeddedWindow.mAttrs.flags &= ~FLAG_NOT_FOCUSABLE;
+ assertTrue(appWindow.canBeImeTarget());
+ }
+
+ @Test
public void testGetWindow() {
final WindowState root = createWindow(null, TYPE_APPLICATION, "root");
final WindowState mediaChild = createWindow(root, TYPE_APPLICATION_MEDIA, "mediaChild");
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 4246aef..f5ed64e 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -7395,6 +7395,29 @@
}
/**
+ * Unregister a IImsServiceFeatureCallback previously associated with an ImsFeature through
+ * {@link #getImsMmTelFeatureAndListen(int, IImsServiceFeatureCallback)} or
+ * {@link #getImsRcsFeatureAndListen(int, IImsServiceFeatureCallback)}.
+ * @param slotIndex The SIM slot associated with the callback.
+ * @param featureType The {@link android.telephony.ims.feature.ImsFeature.FeatureType}
+ * associated with the callback.
+ * @param callback The callback to be unregistered.
+ * @hide
+ */
+ public void unregisterImsFeatureCallback(int slotIndex, int featureType,
+ IImsServiceFeatureCallback callback) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ telephony.unregisterImsFeatureCallback(slotIndex, featureType, callback);
+ }
+ } catch (RemoteException e) {
+ Rlog.e(TAG, "unregisterImsFeatureCallback, RemoteException: "
+ + e.getMessage());
+ }
+ }
+
+ /**
* @return the {@IImsRegistration} interface that corresponds with the slot index and feature.
* @param slotIndex The SIM slot corresponding to the ImsService ImsRegistration is active for.
* @param feature An integer indicating the feature that we wish to get the ImsRegistration for.
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index f5cd68f..3690200 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -851,6 +851,14 @@
IImsRcsFeature getRcsFeatureAndListen(int slotId, in IImsServiceFeatureCallback callback);
/**
+ * Unregister a callback that was previously registered through
+ * {@link #getMmTelFeatureAndListen} or {@link #getRcsFeatureAndListen}. This should always be
+ * called when the callback is no longer being used.
+ */
+ void unregisterImsFeatureCallback(int slotId, int featureType,
+ in IImsServiceFeatureCallback callback);
+
+ /**
* Returns the IImsRegistration associated with the slot and feature specified.
*/
IImsRegistration getImsRegistration(int slotId, int feature);
diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
index a35fb40..0ffafd4 100644
--- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -92,6 +92,9 @@
break;
case TRANSPORT_VPN:
mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_VPN);
+ // VPNs deduce the SUSPENDED capability from their underlying networks and there
+ // is no public API to let VPN services set it.
+ mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_SUSPENDED);
mScore = ConnectivityConstants.VPN_DEFAULT_SCORE;
break;
default:
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index d2b26d3..83ca9b2 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -5387,8 +5387,6 @@
// Even though the VPN is unvalidated, it becomes the default network for our app.
callback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent);
- // TODO: this looks like a spurious callback.
- callback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, vpnNetworkAgent);
callback.assertNoCallback();
assertTrue(vpnNetworkAgent.getScore() > mEthernetNetworkAgent.getScore());
@@ -5418,6 +5416,47 @@
}
@Test
+ public void testVpnStartsWithUnderlyingCaps() throws Exception {
+ final int uid = Process.myUid();
+
+ final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback();
+ final NetworkRequest vpnNetworkRequest = new NetworkRequest.Builder()
+ .removeCapability(NET_CAPABILITY_NOT_VPN)
+ .addTransportType(TRANSPORT_VPN)
+ .build();
+ mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback);
+ vpnNetworkCallback.assertNoCallback();
+
+ // Connect cell. It will become the default network, and in the absence of setting
+ // underlying networks explicitly it will become the sole underlying network for the vpn.
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
+ mCellNetworkAgent.connect(true);
+
+ final TestNetworkAgentWrapper vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN);
+ final ArraySet<UidRange> ranges = new ArraySet<>();
+ ranges.add(new UidRange(uid, uid));
+ mMockVpn.setNetworkAgent(vpnNetworkAgent);
+ mMockVpn.connect();
+ mMockVpn.setUids(ranges);
+ vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */,
+ false /* isStrictMode */);
+
+ vpnNetworkCallback.expectAvailableCallbacks(vpnNetworkAgent.getNetwork(),
+ false /* suspended */, false /* validated */, false /* blocked */, TIMEOUT_MS);
+ vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent.getNetwork(), TIMEOUT_MS,
+ nc -> nc.hasCapability(NET_CAPABILITY_VALIDATED));
+
+ final NetworkCapabilities nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork());
+ assertTrue(nc.hasTransport(TRANSPORT_VPN));
+ assertTrue(nc.hasTransport(TRANSPORT_CELLULAR));
+ assertFalse(nc.hasTransport(TRANSPORT_WIFI));
+ assertTrue(nc.hasCapability(NET_CAPABILITY_VALIDATED));
+ assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED));
+ assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+ }
+
+ @Test
public void testVpnSetUnderlyingNetworks() throws Exception {
final int uid = Process.myUid();
@@ -5447,9 +5486,12 @@
assertFalse(nc.hasTransport(TRANSPORT_WIFI));
// For safety reasons a VPN without underlying networks is considered metered.
assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED));
+ // A VPN without underlying networks is not suspended.
+ assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
// Connect cell and use it as an underlying network.
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
mCellNetworkAgent.connect(true);
mService.setUnderlyingNetworksForVpn(
@@ -5458,10 +5500,12 @@
vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
(caps) -> caps.hasTransport(TRANSPORT_VPN)
&& caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
- && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
+ && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
+ && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+ mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
mWiFiNetworkAgent.connect(true);
mService.setUnderlyingNetworksForVpn(
@@ -5470,7 +5514,8 @@
vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
(caps) -> caps.hasTransport(TRANSPORT_VPN)
&& caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
- && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
+ && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
+ && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
// Don't disconnect, but note the VPN is not using wifi any more.
mService.setUnderlyingNetworksForVpn(
@@ -5479,16 +5524,36 @@
vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
(caps) -> caps.hasTransport(TRANSPORT_VPN)
&& caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
- && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
+ && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
+ && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
- // Use Wifi but not cell. Note the VPN is now unmetered.
+ // Remove NOT_SUSPENDED from the only network and observe VPN is now suspended.
+ mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_SUSPENDED);
+ vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ (caps) -> caps.hasTransport(TRANSPORT_VPN)
+ && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
+ && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
+ && !caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+ vpnNetworkCallback.expectCallback(CallbackEntry.SUSPENDED, vpnNetworkAgent);
+
+ // Add NOT_SUSPENDED again and observe VPN is no longer suspended.
+ mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
+ vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ (caps) -> caps.hasTransport(TRANSPORT_VPN)
+ && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
+ && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
+ && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+ vpnNetworkCallback.expectCallback(CallbackEntry.RESUMED, vpnNetworkAgent);
+
+ // Use Wifi but not cell. Note the VPN is now unmetered and not suspended.
mService.setUnderlyingNetworksForVpn(
new Network[] { mWiFiNetworkAgent.getNetwork() });
vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
(caps) -> caps.hasTransport(TRANSPORT_VPN)
&& !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
- && caps.hasCapability(NET_CAPABILITY_NOT_METERED));
+ && caps.hasCapability(NET_CAPABILITY_NOT_METERED)
+ && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
// Use both again.
mService.setUnderlyingNetworksForVpn(
@@ -5497,7 +5562,37 @@
vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
(caps) -> caps.hasTransport(TRANSPORT_VPN)
&& caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
- && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
+ && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
+ && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+
+ // Cell is suspended again. As WiFi is not, this should not cause a callback.
+ mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_SUSPENDED);
+ vpnNetworkCallback.assertNoCallback();
+
+ // Stop using WiFi. The VPN is suspended again.
+ mService.setUnderlyingNetworksForVpn(
+ new Network[] { mCellNetworkAgent.getNetwork() });
+ vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ (caps) -> caps.hasTransport(TRANSPORT_VPN)
+ && caps.hasTransport(TRANSPORT_CELLULAR)
+ && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
+ && !caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+ // While the SUSPENDED callback should in theory be sent here, it is not. This is
+ // a bug in ConnectivityService, but as the SUSPENDED and RESUMED callbacks have never
+ // been public and are deprecated and slated for removal, there is no sense in spending
+ // resources fixing this bug now.
+
+ // Use both again.
+ mService.setUnderlyingNetworksForVpn(
+ new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
+
+ vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ (caps) -> caps.hasTransport(TRANSPORT_VPN)
+ && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
+ && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
+ && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+ // As above, the RESUMED callback not being sent here is a bug, but not a bug that's
+ // worth anybody's time to fix.
// Disconnect cell. Receive update without even removing the dead network from the
// underlying networks – it's dead anyway. Not metered any more.
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 1994d1f..f8d8a56 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -25,6 +25,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
@@ -606,6 +607,7 @@
.addCapability(NET_CAPABILITY_NOT_METERED)
.addCapability(NET_CAPABILITY_NOT_ROAMING)
.addCapability(NET_CAPABILITY_NOT_CONGESTED)
+ .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
.setLinkUpstreamBandwidthKbps(20));
setMockedNetworks(networks);
@@ -621,6 +623,7 @@
assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_METERED));
assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING));
assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_CONGESTED));
+ assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
Vpn.applyUnderlyingCapabilities(
mConnectivityManager,
@@ -635,6 +638,7 @@
assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_METERED));
assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING));
assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_CONGESTED));
+ assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
Vpn.applyUnderlyingCapabilities(
mConnectivityManager, new Network[] {wifi}, caps, false /* isAlwaysMetered */);
@@ -646,6 +650,7 @@
assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_METERED));
assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING));
assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_CONGESTED));
+ assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
Vpn.applyUnderlyingCapabilities(
mConnectivityManager, new Network[] {wifi}, caps, true /* isAlwaysMetered */);
@@ -657,6 +662,7 @@
assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_METERED));
assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING));
assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_CONGESTED));
+ assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
Vpn.applyUnderlyingCapabilities(
mConnectivityManager,
@@ -671,6 +677,7 @@
assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_METERED));
assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING));
assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_CONGESTED));
+ assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
}
/**