Merge "Give ServiceWatcher a proper lifecycle"
diff --git a/Android.bp b/Android.bp
index 6b39b51..73bc382 100644
--- a/Android.bp
+++ b/Android.bp
@@ -620,6 +620,7 @@
"av-types-aidl-java",
"mediatranscoding_aidl_interface-java",
"soundtrigger_middleware-aidl-java",
+ "modules-utils-os",
],
}
@@ -1259,7 +1260,6 @@
filegroup {
name: "framework-telephony-common-shared-srcs",
srcs: [
- "core/java/android/os/BasicShellCommandHandler.java",
"core/java/android/os/RegistrantList.java",
"core/java/android/os/Registrant.java",
"core/java/android/util/IndentingPrintWriter.java",
@@ -1342,7 +1342,6 @@
name: "framework-wifi-service-shared-srcs",
srcs: [
"core/java/android/net/InterfaceConfiguration.java",
- "core/java/android/os/BasicShellCommandHandler.java",
"core/java/android/util/BackupUtils.java",
"core/java/android/util/Rational.java",
"core/java/com/android/internal/util/FastXmlSerializer.java",
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index 1e72062..cc20213 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -20,10 +20,11 @@
import android.app.AppGlobals;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.os.BasicShellCommandHandler;
import android.os.Binder;
import android.os.UserHandle;
+import com.android.modules.utils.BasicShellCommandHandler;
+
import java.io.PrintWriter;
public final class JobSchedulerShellCommand extends BasicShellCommandHandler {
diff --git a/api/current.txt b/api/current.txt
index 344e93b..2ebb75d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -7403,6 +7403,12 @@
field public static final int ERROR_UNKNOWN = 1; // 0x1
}
+ public final class UnsafeStateException extends java.lang.IllegalStateException implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.UnsafeStateException> CREATOR;
+ }
+
}
package android.app.assist {
@@ -43126,10 +43132,11 @@
method public String getKeystoreAlias();
method public int getOrigin();
method public int getPurposes();
+ method public int getSecurityLevel();
method @NonNull public String[] getSignaturePaddings();
method public int getUserAuthenticationType();
method public int getUserAuthenticationValidityDurationSeconds();
- method public boolean isInsideSecureHardware();
+ method @Deprecated public boolean isInsideSecureHardware();
method public boolean isInvalidatedByBiometricEnrollment();
method public boolean isTrustedUserPresenceRequired();
method public boolean isUserAuthenticationRequired();
diff --git a/api/system-current.txt b/api/system-current.txt
index 0eaebce..73602ae 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4190,6 +4190,7 @@
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setLocationEnabledForUser(boolean, @NonNull android.os.UserHandle);
method @Deprecated @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setProviderEnabledForUser(@NonNull String, boolean, @NonNull android.os.UserHandle);
method @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean unregisterGnssBatchedLocationCallback(@NonNull android.location.BatchedLocationCallback);
+ field public static final String FUSED_PROVIDER = "fused";
}
public final class LocationRequest implements android.os.Parcelable {
@@ -5091,6 +5092,7 @@
method public long getAvSyncTime(int);
method @Nullable public android.media.tv.tuner.DemuxCapabilities getDemuxCapabilities();
method @Nullable public android.media.tv.tuner.frontend.FrontendInfo getFrontendInfo();
+ method @Nullable public java.util.List<android.media.tv.tuner.frontend.FrontendInfo> getFrontendInfoList();
method @Nullable public android.media.tv.tuner.frontend.FrontendStatus getFrontendStatus(@NonNull int[]);
method public int linkFrontendToCiCam(int);
method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER) public android.media.tv.tuner.Descrambler openDescrambler();
@@ -5307,6 +5309,7 @@
public class Filter implements java.lang.AutoCloseable {
method public void close();
method public int configure(@NonNull android.media.tv.tuner.filter.FilterConfiguration);
+ method public int configureScramblingStatusEvent(int);
method public int flush();
method public int getId();
method public long getId64Bit();
@@ -5314,6 +5317,9 @@
method public int setDataSource(@Nullable android.media.tv.tuner.filter.Filter);
method public int start();
method public int stop();
+ field public static final int SCRAMBLING_STATUS_NOT_SCRAMBLED = 2; // 0x2
+ field public static final int SCRAMBLING_STATUS_SCRAMBLED = 4; // 0x4
+ field public static final int SCRAMBLING_STATUS_UNKNOWN = 1; // 0x1
field public static final int STATUS_DATA_READY = 1; // 0x1
field public static final int STATUS_HIGH_WATER = 4; // 0x4
field public static final int STATUS_LOW_WATER = 2; // 0x2
@@ -5414,7 +5420,7 @@
public class MmtpRecordEvent extends android.media.tv.tuner.filter.FilterEvent {
method public long getDataLength();
- method public int getFirstMbInSlice();
+ method public int getFirstMacroblockInSlice();
method public int getMpuSequenceNumber();
method public long getPts();
method public int getScHevcIndexMask();
@@ -5492,6 +5498,10 @@
method @NonNull public android.media.tv.tuner.filter.RecordSettings.Builder setTsIndexMask(int);
}
+ public final class ScramblingStatusEvent extends android.media.tv.tuner.filter.FilterEvent {
+ method public int getScramblingStatus();
+ }
+
public class SectionEvent extends android.media.tv.tuner.filter.FilterEvent {
method public int getDataLength();
method public int getSectionNumber();
@@ -5590,7 +5600,7 @@
public class TsRecordEvent extends android.media.tv.tuner.filter.FilterEvent {
method public long getDataLength();
- method public int getFirstMbInSlice();
+ method public int getFirstMacroblockInSlice();
method public int getPacketId();
method public long getPts();
method public int getScIndexMask();
@@ -9919,8 +9929,13 @@
ctor public DeviceIdAttestationException(@Nullable String, @Nullable Throwable);
}
+ public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec {
+ method public int getNamespace();
+ }
+
public static final class KeyGenParameterSpec.Builder {
- method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUid(int);
+ method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setNamespace(int);
+ method @Deprecated @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUid(int);
}
}
@@ -11202,6 +11217,17 @@
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallQuality> CREATOR;
}
+ public final class CarrierBandwidth implements android.os.Parcelable {
+ ctor public CarrierBandwidth(int, int, int, int);
+ method public int describeContents();
+ method public int getPrimaryDownlinkCapacityKbps();
+ method public int getPrimaryUplinkCapacityKbps();
+ method public int getSecondaryDownlinkCapacityKbps();
+ method public int getSecondaryUplinkCapacityKbps();
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CarrierBandwidth> CREATOR;
+ field public static final int INVALID = -1; // 0xffffffff
+ }
+
public class CarrierConfigManager {
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultCarrierServicePackageName();
method @NonNull public static android.os.PersistableBundle getDefaultConfig();
@@ -11499,6 +11525,18 @@
field public static final int PHYSICAL_CELL_ID_UNKNOWN = -1; // 0xffffffff
}
+ public final class PinResult implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getAttemptsRemaining();
+ method public int getResult();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PinResult> CREATOR;
+ field public static final int PIN_RESULT_TYPE_ABORTED = 3; // 0x3
+ field public static final int PIN_RESULT_TYPE_FAILURE = 2; // 0x2
+ field public static final int PIN_RESULT_TYPE_INCORRECT = 1; // 0x1
+ field public static final int PIN_RESULT_TYPE_SUCCESS = 0; // 0x0
+ }
+
public final class PreciseCallState implements android.os.Parcelable {
ctor public PreciseCallState(int, int, int, int, int);
method public int describeContents();
@@ -11842,6 +11880,7 @@
public class TelephonyManager {
method @Deprecated @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void call(String, String);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult changeIccLockPin(@NonNull String, @NonNull String);
method public int checkCarrierPrivilegesForPackage(String);
method public int checkCarrierPrivilegesForPackageAnyPhone(String);
method public void dial(String);
@@ -11855,6 +11894,7 @@
method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.ComponentName getAndUpdateDefaultRespondViaMessageApplication();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getCallForwarding(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CallForwardingInfoCallback);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getCallWaitingStatus(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.CarrierBandwidth getCarrierBandwidth();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int);
method public java.util.List<java.lang.String> getCarrierPackageNamesForIntent(android.content.Intent);
method public java.util.List<java.lang.String> getCarrierPackageNamesForIntentAndPhone(android.content.Intent, int);
@@ -11946,6 +11986,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult setIccLockEnabled(boolean, @NonNull String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMobileDataPolicyEnabledStatus(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean);
method public int setNrDualConnectivityState(int);
@@ -11961,10 +12002,12 @@
method @Deprecated public void setVisualVoicemailEnabled(android.telecom.PhoneAccountHandle, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVoiceActivationState(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void shutdownAllRadios();
+ method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult supplyIccLockPin(@NonNull String);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult supplyIccLockPuk(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean supplyPin(String);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPinReportResult(String);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPinReportResult(String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean supplyPuk(String, String);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPukReportResult(String, String);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPukReportResult(String, String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean switchSlots(int[]);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void toggleRadioOnOff();
method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void updateOtaEmergencyNumberDbFilePath(@NonNull android.os.ParcelFileDescriptor);
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 012450d..d225f966 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -294,6 +294,7 @@
"tests/e2e/Anomaly_duration_sum_e2e_test.cpp",
"tests/e2e/Attribution_e2e_test.cpp",
"tests/e2e/ConfigTtl_e2e_test.cpp",
+ "tests/e2e/ConfigUpdate_e2e_test.cpp",
"tests/e2e/CountMetric_e2e_test.cpp",
"tests/e2e/DurationMetric_e2e_test.cpp",
"tests/e2e/GaugeMetric_e2e_pull_test.cpp",
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 7bee4e2..ae1ef21 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -520,10 +520,10 @@
}
void StatsLogProcessor::OnConfigUpdated(const int64_t timestampNs, const ConfigKey& key,
- const StatsdConfig& config) {
+ const StatsdConfig& config, bool modularUpdate) {
std::lock_guard<std::mutex> lock(mMetricsMutex);
WriteDataToDiskLocked(key, timestampNs, CONFIG_UPDATED, NO_TIME_CONSTRAINTS);
- OnConfigUpdatedLocked(timestampNs, key, config);
+ OnConfigUpdatedLocked(timestampNs, key, config, modularUpdate);
}
void StatsLogProcessor::OnConfigUpdatedLocked(const int64_t timestampNs, const ConfigKey& key,
@@ -720,7 +720,8 @@
for (const auto& key : configs) {
StatsdConfig config;
if (StorageManager::readConfigFromDisk(key, &config)) {
- OnConfigUpdatedLocked(timestampNs, key, config);
+ // Force a full update when resetting a config.
+ OnConfigUpdatedLocked(timestampNs, key, config, /*modularUpdate=*/false);
StatsdStats::getInstance().noteConfigReset(key);
} else {
ALOGE("Failed to read backup config from disk for : %s", key.ToString().c_str());
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 383dbd9..2af277a 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -48,7 +48,7 @@
void OnLogEvent(LogEvent* event);
void OnConfigUpdated(const int64_t timestampNs, const ConfigKey& key,
- const StatsdConfig& config);
+ const StatsdConfig& config, bool modularUpdate = false);
void OnConfigRemoved(const ConfigKey& key);
size_t GetMetricsSize(const ConfigKey& key) const;
@@ -188,7 +188,7 @@
void resetIfConfigTtlExpiredLocked(const int64_t timestampNs);
void OnConfigUpdatedLocked(const int64_t currentTimestampNs, const ConfigKey& key,
- const StatsdConfig& config, bool modularUpdate = false);
+ const StatsdConfig& config, bool modularUpdate);
void GetActiveConfigsLocked(const int uid, vector<int64_t>& outActiveConfigs);
@@ -338,6 +338,10 @@
FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation);
FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations);
+ FRIEND_TEST(ConfigUpdateE2eTest, TestHashStrings);
+ FRIEND_TEST(ConfigUpdateE2eTest, TestUidMapVersionStringInstaller);
+ FRIEND_TEST(ConfigUpdateE2eTest, TestConfigTtl);
+
FRIEND_TEST(CountMetricE2eTest, TestInitialConditionChanges);
FRIEND_TEST(CountMetricE2eTest, TestSlicedState);
FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap);
diff --git a/cmds/statsd/src/config/ConfigListener.h b/cmds/statsd/src/config/ConfigListener.h
index dcd5e52..3d30137 100644
--- a/cmds/statsd/src/config/ConfigListener.h
+++ b/cmds/statsd/src/config/ConfigListener.h
@@ -39,7 +39,7 @@
* A configuration was added or updated.
*/
virtual void OnConfigUpdated(const int64_t timestampNs, const ConfigKey& key,
- const StatsdConfig& config) = 0;
+ const StatsdConfig& config, bool modularUpdate = false) = 0;
/**
* A configuration was removed.
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index acc12aa..3b6e10b 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -188,6 +188,23 @@
mAlertTrackerMap = newAlertTrackerMap;
mAllPeriodicAlarmTrackers = newPeriodicAlarmTrackers;
+ mTtlNs = config.has_ttl_in_seconds() ? config.ttl_in_seconds() * NS_PER_SEC : -1;
+ refreshTtl(currentTimeNs);
+
+ mHashStringsInReport = config.hash_strings_in_metric_report();
+ mVersionStringsInReport = config.version_strings_in_metric_report();
+ mInstallerInReport = config.installer_in_metric_report();
+ mWhitelistedAtomIds.clear();
+ mWhitelistedAtomIds.insert(config.whitelisted_atom_ids().begin(),
+ config.whitelisted_atom_ids().end());
+ mShouldPersistHistory = config.persist_locally();
+
+ // Store the sub-configs used.
+ mAnnotations.clear();
+ for (const auto& annotation : config.annotation()) {
+ mAnnotations.emplace_back(annotation.field_int64(), annotation.field_int32());
+ }
+
mAllowedUid.clear();
mAllowedPkg.clear();
mDefaultPullUids.clear();
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 23048ae..98d4bff 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -172,7 +172,7 @@
bool mVersionStringsInReport = false;
bool mInstallerInReport = false;
- const int64_t mTtlNs;
+ int64_t mTtlNs;
int64_t mTtlEndNs;
int64_t mLastReportTimeNs;
@@ -193,7 +193,7 @@
// To guard access to mAllowedLogSources
mutable std::mutex mAllowedLogSourcesMutex;
- const std::set<int32_t> mWhitelistedAtomIds;
+ std::set<int32_t> mWhitelistedAtomIds;
// We can pull any atom from these uids.
std::set<int32_t> mDefaultPullUids;
@@ -211,8 +211,7 @@
// Contains the annotations passed in with StatsdConfig.
std::list<std::pair<const int64_t, const int32_t>> mAnnotations;
- const bool mShouldPersistHistory;
-
+ bool mShouldPersistHistory;
// All event tags that are interesting to my metrics.
std::set<int> mTagIds;
@@ -327,6 +326,7 @@
FRIEND_TEST(AlarmE2eTest, TestMultipleAlarms);
FRIEND_TEST(ConfigTtlE2eTest, TestCountMetric);
+ FRIEND_TEST(ConfigUpdateE2eTest, TestConfigTtl);
FRIEND_TEST(MetricActivationE2eTest, TestCountMetric);
FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation);
FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations);
diff --git a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
index 6372361..af9606b 100644
--- a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
+++ b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
@@ -869,6 +869,14 @@
newMetricProducers.push_back(producer.value());
}
+ for (int i = 0; i < config.no_report_metric_size(); ++i) {
+ const int64_t noReportMetric = config.no_report_metric(i);
+ if (newMetricProducerMap.find(noReportMetric) == newMetricProducerMap.end()) {
+ ALOGW("no_report_metric %" PRId64 " not exist", noReportMetric);
+ return false;
+ }
+ noReportMetricIds.insert(noReportMetric);
+ }
const set<int> atomsAllowedFromAnyUid(config.whitelisted_atom_ids().begin(),
config.whitelisted_atom_ids().end());
for (int i = 0; i < allMetricsCount; i++) {
diff --git a/cmds/statsd/tests/ConfigManager_test.cpp b/cmds/statsd/tests/ConfigManager_test.cpp
index 9455304..1d83716 100644
--- a/cmds/statsd/tests/ConfigManager_test.cpp
+++ b/cmds/statsd/tests/ConfigManager_test.cpp
@@ -44,8 +44,8 @@
*/
class MockListener : public ConfigListener {
public:
- MOCK_METHOD3(OnConfigUpdated, void(const int64_t timestampNs, const ConfigKey& key,
- const StatsdConfig& config));
+ MOCK_METHOD4(OnConfigUpdated, void(const int64_t timestampNs, const ConfigKey& key,
+ const StatsdConfig& config, bool modularUpdate));
MOCK_METHOD1(OnConfigRemoved, void(const ConfigKey& key));
};
@@ -89,26 +89,26 @@
manager->StartupForTest();
// Add another one
- EXPECT_CALL(*(listener.get()), OnConfigUpdated(_, ConfigKeyEq(1, StringToId("zzz")),
- StatsdConfigEq(91)))
+ EXPECT_CALL(*(listener.get()),
+ OnConfigUpdated(_, ConfigKeyEq(1, StringToId("zzz")), StatsdConfigEq(91), _))
.RetiresOnSaturation();
manager->UpdateConfig(ConfigKey(1, StringToId("zzz")), config91);
// Update It
- EXPECT_CALL(*(listener.get()), OnConfigUpdated(_, ConfigKeyEq(1, StringToId("zzz")),
- StatsdConfigEq(92)))
+ EXPECT_CALL(*(listener.get()),
+ OnConfigUpdated(_, ConfigKeyEq(1, StringToId("zzz")), StatsdConfigEq(92), _))
.RetiresOnSaturation();
manager->UpdateConfig(ConfigKey(1, StringToId("zzz")), config92);
// Add one with the same uid but a different name
- EXPECT_CALL(*(listener.get()), OnConfigUpdated(_, ConfigKeyEq(1, StringToId("yyy")),
- StatsdConfigEq(93)))
+ EXPECT_CALL(*(listener.get()),
+ OnConfigUpdated(_, ConfigKeyEq(1, StringToId("yyy")), StatsdConfigEq(93), _))
.RetiresOnSaturation();
manager->UpdateConfig(ConfigKey(1, StringToId("yyy")), config93);
// Add one with the same name but a different uid
- EXPECT_CALL(*(listener.get()), OnConfigUpdated(_, ConfigKeyEq(2, StringToId("zzz")),
- StatsdConfigEq(94)))
+ EXPECT_CALL(*(listener.get()),
+ OnConfigUpdated(_, ConfigKeyEq(2, StringToId("zzz")), StatsdConfigEq(94), _))
.RetiresOnSaturation();
manager->UpdateConfig(ConfigKey(2, StringToId("zzz")), config94);
@@ -143,7 +143,7 @@
StatsdConfig config;
- EXPECT_CALL(*(listener.get()), OnConfigUpdated(_, _, _)).Times(5);
+ EXPECT_CALL(*(listener.get()), OnConfigUpdated(_, _, _, _)).Times(5);
EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("xxx"))));
EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("yyy"))));
EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, StringToId("zzz"))));
diff --git a/cmds/statsd/tests/e2e/ConfigUpdate_e2e_test.cpp b/cmds/statsd/tests/e2e/ConfigUpdate_e2e_test.cpp
new file mode 100644
index 0000000..e01a0b6
--- /dev/null
+++ b/cmds/statsd/tests/e2e/ConfigUpdate_e2e_test.cpp
@@ -0,0 +1,307 @@
+// 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.
+
+#include <gtest/gtest.h>
+
+#include <thread> // std::this_thread::sleep_for
+
+#include "android-base/stringprintf.h"
+#include "src/StatsLogProcessor.h"
+#include "src/storage/StorageManager.h"
+#include "tests/statsd_test_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#ifdef __ANDROID__
+#define STATS_DATA_DIR "/data/misc/stats-data"
+using android::base::StringPrintf;
+
+namespace {
+
+StatsdConfig CreateSimpleConfig() {
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_STATSD");
+ config.set_hash_strings_in_metric_report(false);
+
+ *config.add_atom_matcher() = CreateBatteryStateUsbMatcher();
+ // Simple count metric so the config isn't empty.
+ CountMetric* countMetric1 = config.add_count_metric();
+ countMetric1->set_id(StringToId("Count1"));
+ countMetric1->set_what(config.atom_matcher(0).id());
+ countMetric1->set_bucket(FIVE_MINUTES);
+ return config;
+}
+} // namespace
+
+// Setup for parameterized tests.
+class ConfigUpdateE2eTest : public TestWithParam<bool> {};
+
+INSTANTIATE_TEST_SUITE_P(ConfigUpdateE2eTest, ConfigUpdateE2eTest, testing::Bool());
+
+TEST_P(ConfigUpdateE2eTest, TestUidMapVersionStringInstaller) {
+ sp<UidMap> uidMap = new UidMap();
+ vector<int32_t> uids({1000});
+ vector<int64_t> versions({1});
+ vector<String16> apps({String16("app1")});
+ vector<String16> versionStrings({String16("v1")});
+ vector<String16> installers({String16("installer1")});
+ uidMap->updateMap(1, uids, versions, versionStrings, apps, installers);
+
+ StatsdConfig config = CreateSimpleConfig();
+ config.set_version_strings_in_metric_report(true);
+ config.set_installer_in_metric_report(false);
+ int64_t baseTimeNs = getElapsedRealtimeNs();
+
+ ConfigKey cfgKey(0, 12345);
+ sp<StatsLogProcessor> processor =
+ CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey, nullptr, 0, uidMap);
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+ EXPECT_TRUE(metricsManager->isConfigValid());
+
+ // Now update.
+ config.set_version_strings_in_metric_report(false);
+ config.set_installer_in_metric_report(true);
+ processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, /*modularUpdate=*/GetParam());
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ EXPECT_EQ(metricsManager == processor->mMetricsManagers.begin()->second, GetParam());
+ EXPECT_TRUE(metricsManager->isConfigValid());
+
+ ConfigMetricsReportList reports;
+ vector<uint8_t> buffer;
+ processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ // First report is written to disk when the update happens.
+ ASSERT_EQ(reports.reports_size(), 2);
+ UidMapping uidMapping = reports.reports(1).uid_map();
+ ASSERT_EQ(uidMapping.snapshots_size(), 1);
+ ASSERT_EQ(uidMapping.snapshots(0).package_info_size(), 1);
+ EXPECT_FALSE(uidMapping.snapshots(0).package_info(0).has_version_string());
+ EXPECT_EQ(uidMapping.snapshots(0).package_info(0).installer(), "installer1");
+}
+
+TEST_P(ConfigUpdateE2eTest, TestHashStrings) {
+ sp<UidMap> uidMap = new UidMap();
+ vector<int32_t> uids({1000});
+ vector<int64_t> versions({1});
+ vector<String16> apps({String16("app1")});
+ vector<String16> versionStrings({String16("v1")});
+ vector<String16> installers({String16("installer1")});
+ uidMap->updateMap(1, uids, versions, versionStrings, apps, installers);
+
+ StatsdConfig config = CreateSimpleConfig();
+ config.set_version_strings_in_metric_report(true);
+ config.set_hash_strings_in_metric_report(true);
+ int64_t baseTimeNs = getElapsedRealtimeNs();
+
+ ConfigKey cfgKey(0, 12345);
+ sp<StatsLogProcessor> processor =
+ CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey, nullptr, 0, uidMap);
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+ EXPECT_TRUE(metricsManager->isConfigValid());
+
+ // Now update.
+ config.set_hash_strings_in_metric_report(false);
+ processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, /*modularUpdate=*/GetParam());
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ EXPECT_EQ(metricsManager == processor->mMetricsManagers.begin()->second, GetParam());
+ EXPECT_TRUE(metricsManager->isConfigValid());
+
+ ConfigMetricsReportList reports;
+ vector<uint8_t> buffer;
+ processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ // First report is written to disk when the update happens.
+ ASSERT_EQ(reports.reports_size(), 2);
+ UidMapping uidMapping = reports.reports(1).uid_map();
+ ASSERT_EQ(uidMapping.snapshots_size(), 1);
+ ASSERT_EQ(uidMapping.snapshots(0).package_info_size(), 1);
+ EXPECT_TRUE(uidMapping.snapshots(0).package_info(0).has_version_string());
+ EXPECT_FALSE(uidMapping.snapshots(0).package_info(0).has_version_string_hash());
+}
+
+TEST_P(ConfigUpdateE2eTest, TestAnnotations) {
+ StatsdConfig config = CreateSimpleConfig();
+ StatsdConfig_Annotation* annotation = config.add_annotation();
+ annotation->set_field_int64(11);
+ annotation->set_field_int32(1);
+ int64_t baseTimeNs = getElapsedRealtimeNs();
+ ConfigKey cfgKey(0, 12345);
+ sp<StatsLogProcessor> processor =
+ CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey);
+
+ // Now update
+ config.clear_annotation();
+ annotation = config.add_annotation();
+ annotation->set_field_int64(22);
+ annotation->set_field_int32(2);
+ processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, /*modularUpdate=*/GetParam());
+
+ ConfigMetricsReportList reports;
+ vector<uint8_t> buffer;
+ processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ // First report is written to disk when the update happens.
+ ASSERT_EQ(reports.reports_size(), 2);
+ ConfigMetricsReport report = reports.reports(1);
+ EXPECT_EQ(report.annotation_size(), 1);
+ EXPECT_EQ(report.annotation(0).field_int64(), 22);
+ EXPECT_EQ(report.annotation(0).field_int32(), 2);
+}
+
+TEST_P(ConfigUpdateE2eTest, TestPersistLocally) {
+ StatsdConfig config = CreateSimpleConfig();
+ config.set_persist_locally(false);
+ int64_t baseTimeNs = getElapsedRealtimeNs();
+ ConfigKey cfgKey(0, 12345);
+ sp<StatsLogProcessor> processor =
+ CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey);
+ ConfigMetricsReportList reports;
+ vector<uint8_t> buffer;
+ processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ ASSERT_EQ(reports.reports_size(), 1);
+ // Number of reports should still be 1 since persist_locally is false.
+ reports.Clear();
+ buffer.clear();
+ processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ ASSERT_EQ(reports.reports_size(), 1);
+
+ // Now update.
+ config.set_persist_locally(true);
+ processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, /*modularUpdate=*/GetParam());
+
+ // Should get 2: 1 in memory + 1 on disk. Both should be saved on disk.
+ reports.Clear();
+ buffer.clear();
+ processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ ASSERT_EQ(reports.reports_size(), 2);
+ // Should get 3, 2 on disk + 1 in memory.
+ reports.Clear();
+ buffer.clear();
+ processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ ASSERT_EQ(reports.reports_size(), 3);
+ string suffix = StringPrintf("%d_%lld", cfgKey.GetUid(), (long long)cfgKey.GetId());
+ StorageManager::deleteSuffixedFiles(STATS_DATA_DIR, suffix.c_str());
+ string historySuffix =
+ StringPrintf("%d_%lld_history", cfgKey.GetUid(), (long long)cfgKey.GetId());
+ StorageManager::deleteSuffixedFiles(STATS_DATA_DIR, historySuffix.c_str());
+}
+
+TEST_P(ConfigUpdateE2eTest, TestNoReportMetrics) {
+ StatsdConfig config = CreateSimpleConfig();
+ // Second simple count metric.
+ CountMetric* countMetric = config.add_count_metric();
+ countMetric->set_id(StringToId("Count2"));
+ countMetric->set_what(config.atom_matcher(0).id());
+ countMetric->set_bucket(FIVE_MINUTES);
+ config.add_no_report_metric(config.count_metric(0).id());
+ int64_t baseTimeNs = getElapsedRealtimeNs();
+ ConfigKey cfgKey(0, 12345);
+ sp<StatsLogProcessor> processor =
+ CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey);
+
+ // Now update.
+ config.clear_no_report_metric();
+ config.add_no_report_metric(config.count_metric(1).id());
+ processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, /*modularUpdate=*/GetParam());
+
+ ConfigMetricsReportList reports;
+ vector<uint8_t> buffer;
+ processor->onDumpReport(cfgKey, baseTimeNs + 1001, false, true, ADB_DUMP, FAST, &buffer);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ // First report is written to disk when the update happens.
+ ASSERT_EQ(reports.reports_size(), 2);
+ // First report (before update) has the first count metric.
+ ASSERT_EQ(reports.reports(0).metrics_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics(0).metric_id(), config.count_metric(1).id());
+ // Second report (after update) has the first count metric.
+ ASSERT_EQ(reports.reports(1).metrics_size(), 1);
+ EXPECT_EQ(reports.reports(1).metrics(0).metric_id(), config.count_metric(0).id());
+}
+
+TEST_P(ConfigUpdateE2eTest, TestAtomsAllowedFromAnyUid) {
+ StatsdConfig config = CreateSimpleConfig();
+ int64_t baseTimeNs = getElapsedRealtimeNs();
+ ConfigKey cfgKey(0, 12345);
+ sp<StatsLogProcessor> processor =
+ CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey);
+ // Uses AID_ROOT, which isn't in allowed log sources.
+ unique_ptr<LogEvent> event = CreateBatteryStateChangedEvent(
+ baseTimeNs + 2, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB);
+ processor->OnLogEvent(event.get());
+ ConfigMetricsReportList reports;
+ vector<uint8_t> buffer;
+ processor->onDumpReport(cfgKey, baseTimeNs + 1001, true, true, ADB_DUMP, FAST, &buffer);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ ASSERT_EQ(reports.reports_size(), 1);
+ // Check the metric and make sure it has 0 count.
+ ASSERT_EQ(reports.reports(0).metrics_size(), 1);
+ EXPECT_FALSE(reports.reports(0).metrics(0).has_count_metrics());
+
+ // Now update. Allow plugged state to be logged from any uid, so the atom will be counted.
+ config.add_whitelisted_atom_ids(util::PLUGGED_STATE_CHANGED);
+ processor->OnConfigUpdated(baseTimeNs + 1000, cfgKey, config, /*modularUpdate=*/GetParam());
+ unique_ptr<LogEvent> event2 = CreateBatteryStateChangedEvent(
+ baseTimeNs + 2000, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB);
+ processor->OnLogEvent(event.get());
+ reports.Clear();
+ buffer.clear();
+ processor->onDumpReport(cfgKey, baseTimeNs + 3000, true, true, ADB_DUMP, FAST, &buffer);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ ASSERT_EQ(reports.reports_size(), 2);
+ // Check the metric and make sure it has 0 count.
+ ASSERT_EQ(reports.reports(1).metrics_size(), 1);
+ EXPECT_TRUE(reports.reports(1).metrics(0).has_count_metrics());
+ ASSERT_EQ(reports.reports(1).metrics(0).count_metrics().data_size(), 1);
+ ASSERT_EQ(reports.reports(1).metrics(0).count_metrics().data(0).bucket_info_size(), 1);
+ EXPECT_EQ(reports.reports(1).metrics(0).count_metrics().data(0).bucket_info(0).count(), 1);
+}
+
+TEST_P(ConfigUpdateE2eTest, TestConfigTtl) {
+ StatsdConfig config = CreateSimpleConfig();
+ config.set_ttl_in_seconds(1);
+ int64_t baseTimeNs = getElapsedRealtimeNs();
+ ConfigKey cfgKey(0, 12345);
+ sp<StatsLogProcessor> processor =
+ CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey);
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+ EXPECT_EQ(metricsManager->getTtlEndNs(), baseTimeNs + NS_PER_SEC);
+
+ config.set_ttl_in_seconds(5);
+ processor->OnConfigUpdated(baseTimeNs + 2 * NS_PER_SEC, cfgKey, config,
+ /*modularUpdate=*/GetParam());
+ metricsManager = processor->mMetricsManagers.begin()->second;
+ EXPECT_EQ(metricsManager->getTtlEndNs(), baseTimeNs + 7 * NS_PER_SEC);
+
+ // Clear the data stored on disk as a result of the update.
+ vector<uint8_t> buffer;
+ processor->onDumpReport(cfgKey, baseTimeNs + 3 * NS_PER_SEC, false, true, ADB_DUMP, FAST,
+ &buffer);
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/config/preloaded-classes b/config/preloaded-classes
index f56656b..ecf11c2 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -4532,7 +4532,6 @@
android.os.BadParcelableException
android.os.BaseBundle$NoImagePreloadHolder
android.os.BaseBundle
-android.os.BasicShellCommandHandler
android.os.BatteryManager
android.os.BatteryManagerInternal
android.os.BatteryProperty$1
@@ -9558,6 +9557,7 @@
com.android.internal.widget.VerifyCredentialResponse
com.android.internal.widget.ViewClippingUtil$ClippingParameters
com.android.internal.widget.ViewClippingUtil
+com.android.module.utils.BasicShellCommandHandler
com.android.okhttp.Address
com.android.okhttp.AndroidShimResponseCache
com.android.okhttp.Authenticator
diff --git a/core/api/current.txt b/core/api/current.txt
index 9562715..31264b9 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -7403,6 +7403,12 @@
field public static final int ERROR_UNKNOWN = 1; // 0x1
}
+ public final class UnsafeStateException extends java.lang.IllegalStateException implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.UnsafeStateException> CREATOR;
+ }
+
}
package android.app.assist {
@@ -41238,10 +41244,11 @@
method public String getKeystoreAlias();
method public int getOrigin();
method public int getPurposes();
+ method public int getSecurityLevel();
method @NonNull public String[] getSignaturePaddings();
method public int getUserAuthenticationType();
method public int getUserAuthenticationValidityDurationSeconds();
- method public boolean isInsideSecureHardware();
+ method @Deprecated public boolean isInsideSecureHardware();
method public boolean isInvalidatedByBiometricEnrollment();
method public boolean isTrustedUserPresenceRequired();
method public boolean isUserAuthenticationRequired();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index ed3b7de..37a5389 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -4130,6 +4130,7 @@
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setLocationEnabledForUser(boolean, @NonNull android.os.UserHandle);
method @Deprecated @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setProviderEnabledForUser(@NonNull String, boolean, @NonNull android.os.UserHandle);
method @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean unregisterGnssBatchedLocationCallback(@NonNull android.location.BatchedLocationCallback);
+ field public static final String FUSED_PROVIDER = "fused";
}
public final class LocationRequest implements android.os.Parcelable {
@@ -5031,6 +5032,7 @@
method public long getAvSyncTime(int);
method @Nullable public android.media.tv.tuner.DemuxCapabilities getDemuxCapabilities();
method @Nullable public android.media.tv.tuner.frontend.FrontendInfo getFrontendInfo();
+ method @Nullable public java.util.List<android.media.tv.tuner.frontend.FrontendInfo> getFrontendInfoList();
method @Nullable public android.media.tv.tuner.frontend.FrontendStatus getFrontendStatus(@NonNull int[]);
method public int linkFrontendToCiCam(int);
method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER) public android.media.tv.tuner.Descrambler openDescrambler();
@@ -5247,6 +5249,7 @@
public class Filter implements java.lang.AutoCloseable {
method public void close();
method public int configure(@NonNull android.media.tv.tuner.filter.FilterConfiguration);
+ method public int configureScramblingStatusEvent(int);
method public int flush();
method public int getId();
method public long getId64Bit();
@@ -5254,6 +5257,9 @@
method public int setDataSource(@Nullable android.media.tv.tuner.filter.Filter);
method public int start();
method public int stop();
+ field public static final int SCRAMBLING_STATUS_NOT_SCRAMBLED = 2; // 0x2
+ field public static final int SCRAMBLING_STATUS_SCRAMBLED = 4; // 0x4
+ field public static final int SCRAMBLING_STATUS_UNKNOWN = 1; // 0x1
field public static final int STATUS_DATA_READY = 1; // 0x1
field public static final int STATUS_HIGH_WATER = 4; // 0x4
field public static final int STATUS_LOW_WATER = 2; // 0x2
@@ -5354,7 +5360,7 @@
public class MmtpRecordEvent extends android.media.tv.tuner.filter.FilterEvent {
method public long getDataLength();
- method public int getFirstMbInSlice();
+ method public int getFirstMacroblockInSlice();
method public int getMpuSequenceNumber();
method public long getPts();
method public int getScHevcIndexMask();
@@ -5432,6 +5438,10 @@
method @NonNull public android.media.tv.tuner.filter.RecordSettings.Builder setTsIndexMask(int);
}
+ public final class ScramblingStatusEvent extends android.media.tv.tuner.filter.FilterEvent {
+ method public int getScramblingStatus();
+ }
+
public class SectionEvent extends android.media.tv.tuner.filter.FilterEvent {
method public int getDataLength();
method public int getSectionNumber();
@@ -5530,7 +5540,7 @@
public class TsRecordEvent extends android.media.tv.tuner.filter.FilterEvent {
method public long getDataLength();
- method public int getFirstMbInSlice();
+ method public int getFirstMacroblockInSlice();
method public int getPacketId();
method public long getPts();
method public int getScIndexMask();
@@ -8760,8 +8770,13 @@
ctor public DeviceIdAttestationException(@Nullable String, @Nullable Throwable);
}
+ public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec {
+ method public int getNamespace();
+ }
+
public static final class KeyGenParameterSpec.Builder {
- method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUid(int);
+ method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setNamespace(int);
+ method @Deprecated @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUid(int);
}
}
@@ -10043,6 +10058,17 @@
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallQuality> CREATOR;
}
+ public final class CarrierBandwidth implements android.os.Parcelable {
+ ctor public CarrierBandwidth(int, int, int, int);
+ method public int describeContents();
+ method public int getPrimaryDownlinkCapacityKbps();
+ method public int getPrimaryUplinkCapacityKbps();
+ method public int getSecondaryDownlinkCapacityKbps();
+ method public int getSecondaryUplinkCapacityKbps();
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CarrierBandwidth> CREATOR;
+ field public static final int INVALID = -1; // 0xffffffff
+ }
+
public class CarrierConfigManager {
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultCarrierServicePackageName();
method @NonNull public static android.os.PersistableBundle getDefaultConfig();
@@ -10340,6 +10366,18 @@
field public static final int PHYSICAL_CELL_ID_UNKNOWN = -1; // 0xffffffff
}
+ public final class PinResult implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getAttemptsRemaining();
+ method public int getResult();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PinResult> CREATOR;
+ field public static final int PIN_RESULT_TYPE_ABORTED = 3; // 0x3
+ field public static final int PIN_RESULT_TYPE_FAILURE = 2; // 0x2
+ field public static final int PIN_RESULT_TYPE_INCORRECT = 1; // 0x1
+ field public static final int PIN_RESULT_TYPE_SUCCESS = 0; // 0x0
+ }
+
public final class PreciseCallState implements android.os.Parcelable {
ctor public PreciseCallState(int, int, int, int, int);
method public int describeContents();
@@ -10683,6 +10721,7 @@
public class TelephonyManager {
method @Deprecated @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void call(String, String);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult changeIccLockPin(@NonNull String, @NonNull String);
method public int checkCarrierPrivilegesForPackage(String);
method public int checkCarrierPrivilegesForPackageAnyPhone(String);
method public void dial(String);
@@ -10696,6 +10735,7 @@
method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.ComponentName getAndUpdateDefaultRespondViaMessageApplication();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getCallForwarding(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CallForwardingInfoCallback);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getCallWaitingStatus(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.CarrierBandwidth getCarrierBandwidth();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int);
method public java.util.List<java.lang.String> getCarrierPackageNamesForIntent(android.content.Intent);
method public java.util.List<java.lang.String> getCarrierPackageNamesForIntentAndPhone(android.content.Intent, int);
@@ -10787,6 +10827,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult setIccLockEnabled(boolean, @NonNull String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMobileDataPolicyEnabledStatus(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean);
method public int setNrDualConnectivityState(int);
@@ -10802,10 +10843,12 @@
method @Deprecated public void setVisualVoicemailEnabled(android.telecom.PhoneAccountHandle, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVoiceActivationState(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void shutdownAllRadios();
+ method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult supplyIccLockPin(@NonNull String);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult supplyIccLockPuk(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean supplyPin(String);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPinReportResult(String);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPinReportResult(String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean supplyPuk(String, String);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPukReportResult(String, String);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPukReportResult(String, String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean switchSlots(int[]);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void toggleRadioOnOff();
method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void updateOtaEmergencyNumberDbFilePath(@NonNull android.os.ParcelFileDescriptor);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 7a74ad1..ef4dfdf 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -369,12 +369,17 @@
method public boolean isCurrentInputMethodSetByOwner();
method public boolean isFactoryResetProtectionPolicySupported();
field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
+ field public static final int OPERATION_LOCK_NOW = 1; // 0x1
}
public static final class SecurityLog.SecurityEvent implements android.os.Parcelable {
ctor public SecurityLog.SecurityEvent(long, byte[]);
}
+ public final class UnsafeStateException extends java.lang.IllegalStateException implements android.os.Parcelable {
+ method public int getOperation();
+ }
+
}
package android.app.blob {
diff --git a/core/java/android/accounts/GrantCredentialsPermissionActivity.java b/core/java/android/accounts/GrantCredentialsPermissionActivity.java
index 32b61b5..5dc6e60 100644
--- a/core/java/android/accounts/GrantCredentialsPermissionActivity.java
+++ b/core/java/android/accounts/GrantCredentialsPermissionActivity.java
@@ -16,16 +16,23 @@
package android.accounts;
import android.app.Activity;
-import android.content.res.Resources;
-import android.os.Bundle;
-import android.widget.TextView;
-import android.widget.LinearLayout;
-import android.view.View;
-import android.view.LayoutInflater;
+import android.app.ActivityTaskManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
import com.android.internal.R;
import java.io.IOException;
@@ -42,6 +49,7 @@
private Account mAccount;
private String mAuthTokenType;
private int mUid;
+ private int mCallingUid;
private Bundle mResultBundle = null;
protected LayoutInflater mInflater;
@@ -77,6 +85,20 @@
return;
}
+ try {
+ IBinder activityToken = getActivityToken();
+ mCallingUid = ActivityTaskManager.getService().getLaunchedFromUid(activityToken);
+ } catch (RemoteException re) {
+ // Couldn't figure out caller details
+ Log.w(getClass().getSimpleName(), "Unable to get caller identity \n" + re);
+ }
+
+ if (!UserHandle.isSameApp(mCallingUid, Process.SYSTEM_UID) && mCallingUid != mUid) {
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ return;
+ }
+
String accountTypeLabel;
try {
accountTypeLabel = getAccountLabel(mAccount);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 1d644c4..01f3932 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -122,6 +122,7 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
+// TODO(b/172376923) - add CarDevicePolicyManager examples below (or remove reference to it).
/**
* Public interface for managing policies enforced on a device. Most clients of this class must be
* registered with the system as a <a href="{@docRoot}guide/topics/admin/device-admin.html">device
@@ -130,6 +131,13 @@
* for that method specifies that it is restricted to either device or profile owners. Any
* application calling an api may only pass as an argument a device administrator component it
* owns. Otherwise, a {@link SecurityException} will be thrown.
+ *
+ * <p><b>Note: </b>on
+ * {@link android.content.pm.PackageManager#FEATURE_AUTOMOTIVE automotive builds}, some methods can
+ * throw an {@link UnsafeStateException} exception (for example, if the vehicle is moving), so
+ * callers running on automotive builds should wrap every method call under the methods provided by
+ * {@code android.car.admin.CarDevicePolicyManager}.
+ *
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>
@@ -1972,6 +1980,7 @@
public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11;
/**
+ * TODO (b/137101239): clean up split system user codes
* Result code for {@link #checkProvisioningPreCondition}.
*
* <p>Returned for {@link #ACTION_PROVISION_MANAGED_USER} and
@@ -1995,6 +2004,7 @@
public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13;
/**
+ * TODO (b/137101239): clean up split system user codes
* Result code for {@link #checkProvisioningPreCondition}.
*
* <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} when the device the user is a
@@ -2443,6 +2453,19 @@
@Retention(RetentionPolicy.SOURCE)
public @interface PersonalAppsSuspensionReason {}
+ /** @hide */
+ @TestApi
+ public static final int OPERATION_LOCK_NOW = 1;
+
+ // TODO(b/172376923) - add all operations
+ /** @hide */
+ @IntDef(prefix = "OPERATION_", value = {
+ OPERATION_LOCK_NOW,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public static @interface DevicePolicyOperation {
+ }
+
/**
* Return true if the given administrator component is currently active (enabled) in the system.
*
diff --git a/core/java/android/app/admin/DevicePolicySafetyChecker.java b/core/java/android/app/admin/DevicePolicySafetyChecker.java
new file mode 100644
index 0000000..1f8a933
--- /dev/null
+++ b/core/java/android/app/admin/DevicePolicySafetyChecker.java
@@ -0,0 +1,42 @@
+/*
+ * 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 android.app.admin;
+
+import android.annotation.NonNull;
+import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
+
+/**
+ * Interface responsible to check if a {@link DevicePolicyManager} API can be safely executed.
+ *
+ * @hide
+ */
+public interface DevicePolicySafetyChecker {
+
+ /**
+ * Returns whether the given {@code operation} can be safely executed at the moment.
+ */
+ default boolean isDevicePolicyOperationSafe(@DevicePolicyOperation int operation) {
+ return true;
+ }
+
+ /**
+ * Returns a new exception for when the given {@code operation} cannot be safely executed.
+ */
+ @NonNull
+ default UnsafeStateException newUnsafeStateException(@DevicePolicyOperation int operation) {
+ return new UnsafeStateException(operation);
+ }
+}
diff --git a/core/java/android/app/admin/UnsafeStateException.java b/core/java/android/app/admin/UnsafeStateException.java
new file mode 100644
index 0000000..9dcaae4
--- /dev/null
+++ b/core/java/android/app/admin/UnsafeStateException.java
@@ -0,0 +1,74 @@
+/*
+ * 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 android.app.admin;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Exception thrown when a {@link DevicePolicyManager} operation failed because it was not safe
+ * to be executed at that moment.
+ *
+ * <p>For example, it can be thrown on
+ * {@link android.content.pm.PackageManager#FEATURE_AUTOMOTIVE automotive devices} when the vehicle
+ * is moving.
+ */
+@SuppressWarnings("serial")
+public final class UnsafeStateException extends IllegalStateException implements Parcelable {
+
+ private final @DevicePolicyOperation int mOperation;
+
+ /** @hide */
+ public UnsafeStateException(@DevicePolicyOperation int operation) {
+ super();
+
+ mOperation = operation;
+ }
+
+ /** @hide */
+ @TestApi
+ public @DevicePolicyOperation int getOperation() {
+ return mOperation;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mOperation);
+ }
+
+ @NonNull
+ public static final Creator<UnsafeStateException> CREATOR =
+ new Creator<UnsafeStateException>() {
+
+ @Override
+ public UnsafeStateException createFromParcel(Parcel source) {
+ return new UnsafeStateException(source.readInt());
+ }
+
+ @Override
+ public UnsafeStateException[] newArray(int size) {
+ return new UnsafeStateException[size];
+ }
+ };
+}
diff --git a/core/java/android/app/backup/OWNERS b/core/java/android/app/backup/OWNERS
index 7e7710b..0f88811 100644
--- a/core/java/android/app/backup/OWNERS
+++ b/core/java/android/app/backup/OWNERS
@@ -1,4 +1,4 @@
# Bug component: 656484
-include platform/frameworks/base/services/backup:/OWNERS
+include platform/frameworks/base:/services/backup/OWNERS
diff --git a/core/java/android/hardware/iris/IIrisService.aidl b/core/java/android/hardware/iris/IIrisService.aidl
index b9eca3b..3d26318 100644
--- a/core/java/android/hardware/iris/IIrisService.aidl
+++ b/core/java/android/hardware/iris/IIrisService.aidl
@@ -22,5 +22,5 @@
*/
interface IIrisService {
// Give IrisService its ID. See AuthService.java
- void initializeConfiguration(int sensorId);
+ void initializeConfiguration(int sensorId, int strength);
}
diff --git a/core/java/android/os/BasicShellCommandHandler.java b/core/java/android/os/BasicShellCommandHandler.java
deleted file mode 100644
index 366da3d..0000000
--- a/core/java/android/os/BasicShellCommandHandler.java
+++ /dev/null
@@ -1,342 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-import android.util.Log;
-
-import java.io.BufferedInputStream;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-
-/**
- * Helper for implementing {@link Binder#onShellCommand Binder.onShellCommand}. This is meant to
- * be copied into mainline modules, so this class must not use any hidden APIs.
- *
- * @hide
- */
-public abstract class BasicShellCommandHandler {
- static final String TAG = "ShellCommand";
- static final boolean DEBUG = false;
-
- private Binder mTarget;
- private FileDescriptor mIn;
- private FileDescriptor mOut;
- private FileDescriptor mErr;
- private String[] mArgs;
-
- private String mCmd;
- private int mArgPos;
- private String mCurArgData;
-
- private FileInputStream mFileIn;
- private FileOutputStream mFileOut;
- private FileOutputStream mFileErr;
-
- private PrintWriter mOutPrintWriter;
- private PrintWriter mErrPrintWriter;
- private InputStream mInputStream;
-
- public void init(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, int firstArgPos) {
- mTarget = target;
- mIn = in;
- mOut = out;
- mErr = err;
- mArgs = args;
- mCmd = null;
- mArgPos = firstArgPos;
- mCurArgData = null;
- mFileIn = null;
- mFileOut = null;
- mFileErr = null;
- mOutPrintWriter = null;
- mErrPrintWriter = null;
- mInputStream = null;
- }
-
- public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args) {
- String cmd;
- int start;
- if (args != null && args.length > 0) {
- cmd = args[0];
- start = 1;
- } else {
- cmd = null;
- start = 0;
- }
- init(target, in, out, err, args, start);
- mCmd = cmd;
-
- if (DEBUG) {
- RuntimeException here = new RuntimeException("here");
- here.fillInStackTrace();
- Log.d(TAG, "Starting command " + mCmd + " on " + mTarget, here);
- Log.d(TAG, "Calling uid=" + Binder.getCallingUid()
- + " pid=" + Binder.getCallingPid());
- }
- int res = -1;
- try {
- res = onCommand(mCmd);
- if (DEBUG) Log.d(TAG, "Executed command " + mCmd + " on " + mTarget);
- } catch (Throwable e) {
- // Unlike usual calls, in this case if an exception gets thrown
- // back to us we want to print it back in to the dump data, since
- // that is where the caller expects all interesting information to
- // go.
- PrintWriter eout = getErrPrintWriter();
- eout.println();
- eout.println("Exception occurred while executing '" + mCmd + "':");
- e.printStackTrace(eout);
- } finally {
- if (DEBUG) Log.d(TAG, "Flushing output streams on " + mTarget);
- if (mOutPrintWriter != null) {
- mOutPrintWriter.flush();
- }
- if (mErrPrintWriter != null) {
- mErrPrintWriter.flush();
- }
- if (DEBUG) Log.d(TAG, "Sending command result on " + mTarget);
- }
- if (DEBUG) Log.d(TAG, "Finished command " + mCmd + " on " + mTarget);
- return res;
- }
-
- /**
- * Return the raw FileDescriptor for the output stream.
- */
- public FileDescriptor getOutFileDescriptor() {
- return mOut;
- }
-
- /**
- * Return direct raw access (not buffered) to the command's output data stream.
- */
- public OutputStream getRawOutputStream() {
- if (mFileOut == null) {
- mFileOut = new FileOutputStream(mOut);
- }
- return mFileOut;
- }
-
- /**
- * Return a PrintWriter for formatting output to {@link #getRawOutputStream()}.
- */
- public PrintWriter getOutPrintWriter() {
- if (mOutPrintWriter == null) {
- mOutPrintWriter = new PrintWriter(getRawOutputStream());
- }
- return mOutPrintWriter;
- }
-
- /**
- * Return the raw FileDescriptor for the error stream.
- */
- public FileDescriptor getErrFileDescriptor() {
- return mErr;
- }
-
- /**
- * Return direct raw access (not buffered) to the command's error output data stream.
- */
- public OutputStream getRawErrorStream() {
- if (mFileErr == null) {
- mFileErr = new FileOutputStream(mErr);
- }
- return mFileErr;
- }
-
- /**
- * Return a PrintWriter for formatting output to {@link #getRawErrorStream()}.
- */
- public PrintWriter getErrPrintWriter() {
- if (mErr == null) {
- return getOutPrintWriter();
- }
- if (mErrPrintWriter == null) {
- mErrPrintWriter = new PrintWriter(getRawErrorStream());
- }
- return mErrPrintWriter;
- }
-
- /**
- * Return the raw FileDescriptor for the input stream.
- */
- public FileDescriptor getInFileDescriptor() {
- return mIn;
- }
-
- /**
- * Return direct raw access (not buffered) to the command's input data stream.
- */
- public InputStream getRawInputStream() {
- if (mFileIn == null) {
- mFileIn = new FileInputStream(mIn);
- }
- return mFileIn;
- }
-
- /**
- * Return buffered access to the command's {@link #getRawInputStream()}.
- */
- public InputStream getBufferedInputStream() {
- if (mInputStream == null) {
- mInputStream = new BufferedInputStream(getRawInputStream());
- }
- return mInputStream;
- }
-
- /**
- * Return the next option on the command line -- that is an argument that
- * starts with '-'. If the next argument is not an option, null is returned.
- */
- public String getNextOption() {
- if (mCurArgData != null) {
- String prev = mArgs[mArgPos - 1];
- throw new IllegalArgumentException("No argument expected after \"" + prev + "\"");
- }
- if (mArgPos >= mArgs.length) {
- return null;
- }
- String arg = mArgs[mArgPos];
- if (!arg.startsWith("-")) {
- return null;
- }
- mArgPos++;
- if (arg.equals("--")) {
- return null;
- }
- if (arg.length() > 1 && arg.charAt(1) != '-') {
- if (arg.length() > 2) {
- mCurArgData = arg.substring(2);
- return arg.substring(0, 2);
- } else {
- mCurArgData = null;
- return arg;
- }
- }
- mCurArgData = null;
- return arg;
- }
-
- /**
- * Return the next argument on the command line, whatever it is; if there are
- * no arguments left, return null.
- */
- public String getNextArg() {
- if (mCurArgData != null) {
- String arg = mCurArgData;
- mCurArgData = null;
- return arg;
- } else if (mArgPos < mArgs.length) {
- return mArgs[mArgPos++];
- } else {
- return null;
- }
- }
-
- public String peekNextArg() {
- if (mCurArgData != null) {
- return mCurArgData;
- } else if (mArgPos < mArgs.length) {
- return mArgs[mArgPos];
- } else {
- return null;
- }
- }
-
- /**
- * @return all the remaining arguments in the command without moving the current position.
- */
- public String[] peekRemainingArgs() {
- int remaining = getRemainingArgsCount();
- String[] args = new String[remaining];
- for (int pos = mArgPos; pos < mArgs.length; pos++) {
- args[pos - mArgPos] = mArgs[pos];
- }
- return args;
- }
-
- /**
- * Returns number of arguments that haven't been processed yet.
- */
- public int getRemainingArgsCount() {
- if (mArgPos >= mArgs.length) {
- return 0;
- }
- return mArgs.length - mArgPos;
- }
-
- /**
- * Return the next argument on the command line, whatever it is; if there are
- * no arguments left, throws an IllegalArgumentException to report this to the user.
- */
- public String getNextArgRequired() {
- String arg = getNextArg();
- if (arg == null) {
- String prev = mArgs[mArgPos - 1];
- throw new IllegalArgumentException("Argument expected after \"" + prev + "\"");
- }
- return arg;
- }
-
- public int handleDefaultCommands(String cmd) {
- if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) {
- onHelp();
- } else {
- getOutPrintWriter().println("Unknown command: " + cmd);
- }
- return -1;
- }
-
- public Binder getTarget() {
- return mTarget;
- }
-
- public String[] getAllArgs() {
- return mArgs;
- }
-
- /**
- * Implement parsing and execution of a command. If it isn't a command you understand,
- * call {@link #handleDefaultCommands(String)} and return its result as a last resort.
- * Use {@link #getNextOption()}, {@link #getNextArg()}, and {@link #getNextArgRequired()}
- * to process additional command line arguments. Command output can be written to
- * {@link #getOutPrintWriter()} and errors to {@link #getErrPrintWriter()}.
- *
- * <p class="caution">Note that no permission checking has been done before entering this
- * function, so you need to be sure to do your own security verification for any commands you
- * are executing. The easiest way to do this is to have the ShellCommand contain
- * only a reference to your service's aidl interface, and do all of your command
- * implementations on top of that -- that way you can rely entirely on your executing security
- * code behind that interface.</p>
- *
- * @param cmd The first command line argument representing the name of the command to execute.
- * @return Return the command result; generally 0 or positive indicates success and
- * negative values indicate error.
- */
- public abstract int onCommand(String cmd);
-
- /**
- * Implement this to print help text about your command to {@link #getOutPrintWriter()}.
- */
- public abstract void onHelp();
-}
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index 3358ce1..a2173a6 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -19,15 +19,9 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.util.Slog;
-import com.android.internal.util.FastPrintWriter;
+import com.android.modules.utils.BasicShellCommandHandler;
-import java.io.BufferedInputStream;
import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintWriter;
/**
* Helper for implementing {@link Binder#onShellCommand Binder.onShellCommand}.
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index b94031a..46a5caf 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -40,7 +40,9 @@
import android.media.AudioFormat;
import android.media.permission.Identity;
import android.os.AsyncTask;
+import android.os.Binder;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.util.Slog;
@@ -246,6 +248,7 @@
private final Callback mExternalCallback;
private final Object mLock = new Object();
private final Handler mHandler;
+ private final IBinder mBinder = new Binder();
private int mAvailability = STATE_NOT_READY;
@@ -450,7 +453,7 @@
Identity identity = new Identity();
identity.packageName = ActivityThread.currentOpPackageName();
mSoundTriggerSession = mModelManagementService.createSoundTriggerSessionAsOriginator(
- identity);
+ identity, mBinder);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
diff --git a/core/java/android/uwb/RangingParams.java b/core/java/android/uwb/RangingParams.java
deleted file mode 100644
index f23d9ed..0000000
--- a/core/java/android/uwb/RangingParams.java
+++ /dev/null
@@ -1,477 +0,0 @@
-/*
- * Copyright 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 android.uwb;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.PersistableBundle;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.time.Duration;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-
-/**
- * An object used when requesting to open a new {@link RangingSession}.
- * <p>Use {@link RangingParams.Builder} to create an instance of this class.
- *
- * @hide
- */
-public final class RangingParams implements Parcelable {
- private final boolean mIsInitiator;
- private final boolean mIsController;
- private final Duration mSamplePeriod;
- private final UwbAddress mLocalDeviceAddress;
- private final List<UwbAddress> mRemoteDeviceAddresses;
- private final int mChannelNumber;
- private final int mTransmitPreambleCodeIndex;
- private final int mReceivePreambleCodeIndex;
- private final int mStsPhyPacketType;
- private final PersistableBundle mSpecificationParameters;
-
- private RangingParams(boolean isInitiator, boolean isController,
- @NonNull Duration samplingPeriod, @NonNull UwbAddress localDeviceAddress,
- @NonNull List<UwbAddress> remoteDeviceAddresses, int channelNumber,
- int transmitPreambleCodeIndex, int receivePreambleCodeIndex,
- @StsPhyPacketType int stsPhyPacketType,
- @NonNull PersistableBundle specificationParameters) {
- mIsInitiator = isInitiator;
- mIsController = isController;
- mSamplePeriod = samplingPeriod;
- mLocalDeviceAddress = localDeviceAddress;
- mRemoteDeviceAddresses = remoteDeviceAddresses;
- mChannelNumber = channelNumber;
- mTransmitPreambleCodeIndex = transmitPreambleCodeIndex;
- mReceivePreambleCodeIndex = receivePreambleCodeIndex;
- mStsPhyPacketType = stsPhyPacketType;
- mSpecificationParameters = specificationParameters;
- }
-
- /**
- * Get if the local device is the initiator
- *
- * @return true if the device is the initiator
- */
- public boolean isInitiator() {
- return mIsInitiator;
- }
-
- /**
- * Get if the local device is the controller
- *
- * @return true if the device is the controller
- */
- public boolean isController() {
- return mIsController;
- }
-
- /**
- * The desired amount of time between two adjacent samples of measurement
- *
- * @return the ranging sample period
- */
- @NonNull
- public Duration getSamplingPeriod() {
- return mSamplePeriod;
- }
-
- /**
- * Local device's {@link UwbAddress}
- *
- * <p>Simultaneous {@link RangingSession}s on the same device can have different results for
- * {@link #getLocalDeviceAddress()}.
- *
- * @return the local device's {@link UwbAddress}
- */
- @NonNull
- public UwbAddress getLocalDeviceAddress() {
- return mLocalDeviceAddress;
- }
-
- /**
- * Gets a list of all remote device's {@link UwbAddress}
- *
- * @return a {@link List} of {@link UwbAddress} representing the remote devices
- */
- @NonNull
- public List<UwbAddress> getRemoteDeviceAddresses() {
- return mRemoteDeviceAddresses;
- }
-
- /**
- * Channel number used between this device pair as defined by 802.15.4z
- *
- * Range: -1, 0-15
- *
- * @return the channel to use
- */
- public int getChannelNumber() {
- return mChannelNumber;
- }
-
- /**
- * Preamble index used between this device pair as defined by 802.15.4z
- *
- * Range: 0, 0-32
- *
- * @return the preamble index to use for transmitting
- */
- public int getTxPreambleIndex() {
- return mTransmitPreambleCodeIndex;
- }
-
- /**
- * preamble index used between this device pair as defined by 802.15.4z
- *
- * Range: 0, 13-16, 21-32
- *
- * @return the preamble index to use for receiving
- */
- public int getRxPreambleIndex() {
- return mReceivePreambleCodeIndex;
- }
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(value = {
- STS_PHY_PACKET_TYPE_SP0,
- STS_PHY_PACKET_TYPE_SP1,
- STS_PHY_PACKET_TYPE_SP2,
- STS_PHY_PACKET_TYPE_SP3})
- public @interface StsPhyPacketType {}
-
- /**
- * PHY packet type SP0 when STS is used as defined by 802.15.4z
- */
- public static final int STS_PHY_PACKET_TYPE_SP0 = 0;
-
- /**
- * PHY packet type SP1 when STS is used as defined by 802.15.4z
- */
- public static final int STS_PHY_PACKET_TYPE_SP1 = 1;
-
- /**
- * PHY packet type SP2 when STS is used as defined by 802.15.4z
- */
- public static final int STS_PHY_PACKET_TYPE_SP2 = 2;
-
- /**
- * PHY packet type SP3 when STS is used as defined by 802.15.4z
- */
- public static final int STS_PHY_PACKET_TYPE_SP3 = 3;
-
- /**
- * Get the type of PHY packet when STS is used as defined by 802.15.4z
- *
- * @return the {@link StsPhyPacketType} to use
- */
- @StsPhyPacketType
- public int getStsPhyPacketType() {
- return mStsPhyPacketType;
- }
-
- /**
- * Parameters for a specific UWB protocol constructed using a support library.
- *
- * <p>Android reserves the '^android.*' namespace
- *
- * @return a {@link PersistableBundle} copy of protocol specific parameters
- */
- public @Nullable PersistableBundle getSpecificationParameters() {
- return new PersistableBundle(mSpecificationParameters);
- }
-
- /**
- * @hide
- */
- @Override
- public boolean equals(@Nullable Object obj) {
- if (this == obj) {
- return true;
- }
-
- if (obj instanceof RangingParams) {
- RangingParams other = (RangingParams) obj;
-
- return mIsInitiator == other.mIsInitiator
- && mIsController == other.mIsController
- && mSamplePeriod.equals(other.mSamplePeriod)
- && mLocalDeviceAddress.equals(other.mLocalDeviceAddress)
- && mRemoteDeviceAddresses.equals(other.mRemoteDeviceAddresses)
- && mChannelNumber == other.mChannelNumber
- && mTransmitPreambleCodeIndex == other.mTransmitPreambleCodeIndex
- && mReceivePreambleCodeIndex == other.mReceivePreambleCodeIndex
- && mStsPhyPacketType == other.mStsPhyPacketType
- && mSpecificationParameters.size() == other.mSpecificationParameters.size()
- && mSpecificationParameters.kindofEquals(other.mSpecificationParameters);
- }
- return false;
- }
-
- /**
- * @hide
- */
- @Override
- public int hashCode() {
- return Objects.hash(mIsInitiator, mIsController, mSamplePeriod, mLocalDeviceAddress,
- mRemoteDeviceAddresses, mChannelNumber, mTransmitPreambleCodeIndex,
- mReceivePreambleCodeIndex, mStsPhyPacketType, mSpecificationParameters);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeBoolean(mIsInitiator);
- dest.writeBoolean(mIsController);
- dest.writeLong(mSamplePeriod.getSeconds());
- dest.writeInt(mSamplePeriod.getNano());
- dest.writeParcelable(mLocalDeviceAddress, flags);
-
- UwbAddress[] remoteAddresses = new UwbAddress[mRemoteDeviceAddresses.size()];
- mRemoteDeviceAddresses.toArray(remoteAddresses);
- dest.writeParcelableArray(remoteAddresses, flags);
-
- dest.writeInt(mChannelNumber);
- dest.writeInt(mTransmitPreambleCodeIndex);
- dest.writeInt(mReceivePreambleCodeIndex);
- dest.writeInt(mStsPhyPacketType);
- dest.writePersistableBundle(mSpecificationParameters);
- }
-
- public static final @android.annotation.NonNull Creator<RangingParams> CREATOR =
- new Creator<RangingParams>() {
- @Override
- public RangingParams createFromParcel(Parcel in) {
- Builder builder = new Builder();
- builder.setIsInitiator(in.readBoolean());
- builder.setIsController(in.readBoolean());
- builder.setSamplePeriod(Duration.ofSeconds(in.readLong(), in.readInt()));
- builder.setLocalDeviceAddress(
- in.readParcelable(UwbAddress.class.getClassLoader()));
-
- UwbAddress[] remoteAddresses =
- in.readParcelableArray(null, UwbAddress.class);
- for (UwbAddress remoteAddress : remoteAddresses) {
- builder.addRemoteDeviceAddress(remoteAddress);
- }
-
- builder.setChannelNumber(in.readInt());
- builder.setTransmitPreambleCodeIndex(in.readInt());
- builder.setReceivePreambleCodeIndex(in.readInt());
- builder.setStsPhPacketType(in.readInt());
- builder.setSpecificationParameters(in.readPersistableBundle());
-
- return builder.build();
- }
-
- @Override
- public RangingParams[] newArray(int size) {
- return new RangingParams[size];
- }
- };
-
- /**
- * Builder class for {@link RangingParams}.
- */
- public static final class Builder {
- private boolean mIsInitiator = false;
- private boolean mIsController = false;
- private Duration mSamplePeriod = null;
- private UwbAddress mLocalDeviceAddress = null;
- private List<UwbAddress> mRemoteDeviceAddresses = new ArrayList<>();
- private int mChannelNumber = 0;
- private int mTransmitPreambleCodeIndex = 0;
- private int mReceivePreambleCodeIndex = 0;
- private int mStsPhyPacketType = STS_PHY_PACKET_TYPE_SP0;
- private PersistableBundle mSpecificationParameters = new PersistableBundle();
-
- /**
- * Set whether the device is the initiator or responder as defined by IEEE 802.15.4z
- *
- * @param isInitiator whether the device is the initiator (true) or responder (false)
- */
- public Builder setIsInitiator(boolean isInitiator) {
- mIsInitiator = isInitiator;
- return this;
- }
-
- /**
- * Set whether the local device is the controller or controlee as defined by IEEE 802.15.4z
- *
- * @param isController whether the device is the controller (true) or controlee (false)
- */
- public Builder setIsController(boolean isController) {
- mIsController = isController;
- return this;
- }
-
- /**
- * Set the time between ranging samples
- *
- * @param samplePeriod the time between ranging samples
- */
- public Builder setSamplePeriod(@NonNull Duration samplePeriod) {
- mSamplePeriod = samplePeriod;
- return this;
- }
-
- /**
- * Set the local device address
- *
- * @param localDeviceAddress the local device's address for the {@link RangingSession}
- */
- public Builder setLocalDeviceAddress(@NonNull UwbAddress localDeviceAddress) {
- mLocalDeviceAddress = localDeviceAddress;
- return this;
- }
-
- /**
- * Add a remote device's address to the ranging session
- *
- * @param remoteDeviceAddress a remote device's address for the {@link RangingSession}
- * @throws IllegalArgumentException if {@code remoteDeviceAddress} is already present.
- */
- public Builder addRemoteDeviceAddress(@NonNull UwbAddress remoteDeviceAddress) {
- if (mRemoteDeviceAddresses.contains(remoteDeviceAddress)) {
- throw new IllegalArgumentException(
- "Remote device address already added: " + remoteDeviceAddress.toString());
- }
- mRemoteDeviceAddresses.add(remoteDeviceAddress);
- return this;
- }
-
- /**
- * Set the IEEE 802.15.4z channel to use for the {@link RangingSession}
- * <p>Valid values are in the range [-1, 15]
- *
- * @param channelNumber the channel to use for the {@link RangingSession}
- * @throws IllegalArgumentException if {@code channelNumber} is invalid.
- */
- public Builder setChannelNumber(int channelNumber) {
- if (channelNumber < -1 || channelNumber > 15) {
- throw new IllegalArgumentException("Invalid channel number");
- }
- mChannelNumber = channelNumber;
- return this;
- }
-
- private static final Set<Integer> VALID_TX_PREAMBLE_CODES = new HashSet<Integer>(
- Arrays.asList(0, 13, 14, 15, 16, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32));
-
- /**
- * Set the IEEE 802.15.4z preamble code index to use when transmitting
- *
- * <p>Valid values are in the ranges: [0], [13-16], [21-32]
- *
- * @param transmitPreambleCodeIndex preamble code index to use for transmitting
- * @throws IllegalArgumentException if {@code transmitPreambleCodeIndex} is invalid.
- */
- public Builder setTransmitPreambleCodeIndex(int transmitPreambleCodeIndex) {
- if (!VALID_TX_PREAMBLE_CODES.contains(transmitPreambleCodeIndex)) {
- throw new IllegalArgumentException(
- "Invalid transmit preamble: " + transmitPreambleCodeIndex);
- }
- mTransmitPreambleCodeIndex = transmitPreambleCodeIndex;
- return this;
- }
-
- private static final Set<Integer> VALID_RX_PREAMBLE_CODES = new HashSet<Integer>(
- Arrays.asList(0, 16, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32));
-
- /**
- * Set the IEEE 802.15.4z preamble code index to use when receiving
- *
- * Valid values are in the ranges: [0], [16-32]
- *
- * @param receivePreambleCodeIndex preamble code index to use for receiving
- * @throws IllegalArgumentException if {@code receivePreambleCodeIndex} is invalid.
- */
- public Builder setReceivePreambleCodeIndex(int receivePreambleCodeIndex) {
- if (!VALID_RX_PREAMBLE_CODES.contains(receivePreambleCodeIndex)) {
- throw new IllegalArgumentException(
- "Invalid receive preamble: " + receivePreambleCodeIndex);
- }
- mReceivePreambleCodeIndex = receivePreambleCodeIndex;
- return this;
- }
-
- /**
- * Set the IEEE 802.15.4z PHY packet type when STS is used
- *
- * @param stsPhyPacketType PHY packet type when STS is used
- * @throws IllegalArgumentException if {@code stsPhyPacketType} is invalid.
- */
- public Builder setStsPhPacketType(@StsPhyPacketType int stsPhyPacketType) {
- if (stsPhyPacketType != STS_PHY_PACKET_TYPE_SP0
- && stsPhyPacketType != STS_PHY_PACKET_TYPE_SP1
- && stsPhyPacketType != STS_PHY_PACKET_TYPE_SP2
- && stsPhyPacketType != STS_PHY_PACKET_TYPE_SP3) {
- throw new IllegalArgumentException("unknown StsPhyPacketType: " + stsPhyPacketType);
- }
-
- mStsPhyPacketType = stsPhyPacketType;
- return this;
- }
-
- /**
- * Set the specification parameters
- *
- * <p>Creates a copy of the parameters
- *
- * @param parameters specification parameters built from support library
- */
- public Builder setSpecificationParameters(@NonNull PersistableBundle parameters) {
- mSpecificationParameters = new PersistableBundle(parameters);
- return this;
- }
-
- /**
- * Build the {@link RangingParams} object.
- *
- * @throws IllegalStateException if required parameters are missing
- */
- public RangingParams build() {
- if (mSamplePeriod == null) {
- throw new IllegalStateException("No sample period provided");
- }
-
- if (mLocalDeviceAddress == null) {
- throw new IllegalStateException("Local device address not provided");
- }
-
- if (mRemoteDeviceAddresses.size() == 0) {
- throw new IllegalStateException("No remote device address(es) provided");
- }
-
- return new RangingParams(mIsInitiator, mIsController, mSamplePeriod,
- mLocalDeviceAddress, mRemoteDeviceAddresses, mChannelNumber,
- mTransmitPreambleCodeIndex, mReceivePreambleCodeIndex, mStsPhyPacketType,
- mSpecificationParameters);
- }
- }
-}
diff --git a/core/java/android/uwb/RangingSession.java b/core/java/android/uwb/RangingSession.java
index f4033fe..8639269 100644
--- a/core/java/android/uwb/RangingSession.java
+++ b/core/java/android/uwb/RangingSession.java
@@ -30,7 +30,7 @@
* {@link RangingSession}.
*
* <p>To get an instance of {@link RangingSession}, first use
- * {@link UwbManager#openRangingSession(RangingParams, Executor, Callback)} to request to open a
+ * {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)} to request to open a
* session. Once the session is opened, a {@link RangingSession} object is provided through
* {@link RangingSession.Callback#onOpenSuccess(RangingSession, PersistableBundle)}. If opening a
* session fails, the failure is reported through {@link RangingSession.Callback#onClosed(int)} with
@@ -44,7 +44,7 @@
*/
public interface Callback {
/**
- * Invoked when {@link UwbManager#openRangingSession(RangingParams, Executor, Callback)}
+ * Invoked when {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)}
* is successful
*
* @param session the newly opened {@link RangingSession}
@@ -77,7 +77,7 @@
/**
* Indicates that the session failed to open due to erroneous parameters passed
- * to {@link UwbManager#openRangingSession(RangingParams, Executor, Callback)}
+ * to {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)}
*/
int CLOSE_REASON_LOCAL_BAD_PARAMETERS = 2;
@@ -137,8 +137,8 @@
* will still be invoked.
*
* <p>{@link Callback#onClosed(int)} will be invoked using the same callback
- * object given to {@link UwbManager#openRangingSession(RangingParams, Executor, Callback)} when
- * the {@link RangingSession} was opened. The callback will be invoked after each call to
+ * object given to {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)}
+ * when the {@link RangingSession} was opened. The callback will be invoked after each call to
* {@link #close()}, even if the {@link RangingSession} is already closed.
*/
@Override
diff --git a/core/java/android/uwb/UwbManager.java b/core/java/android/uwb/UwbManager.java
index 6bf7d6f..2f1e2de 100644
--- a/core/java/android/uwb/UwbManager.java
+++ b/core/java/android/uwb/UwbManager.java
@@ -279,7 +279,10 @@
* <p>An open {@link RangingSession} will be automatically closed if client application process
* dies.
*
- * @param params {@link RangingParams} used to initialize this {@link RangingSession}
+ * <p>A UWB support library must be used in order to construct the {@code parameter}
+ * {@link PersistableBundle}.
+ *
+ * @param parameters the parameters that define the ranging session
* @param executor {@link Executor} to run callbacks
* @param callbacks {@link RangingSession.Callback} to associate with the
* {@link RangingSession} that is being opened.
@@ -290,8 +293,9 @@
* {@link RangingSession.Callback#onOpenSuccess}.
*/
@NonNull
- public AutoCloseable openRangingSession(@NonNull RangingParams params,
- @NonNull Executor executor, @NonNull RangingSession.Callback callbacks) {
+ public AutoCloseable openRangingSession(@NonNull PersistableBundle parameters,
+ @NonNull Executor executor,
+ @NonNull RangingSession.Callback callbacks) {
throw new UnsupportedOperationException();
}
}
diff --git a/core/java/android/window/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java
index bc3e35c..6cc3cd3 100644
--- a/core/java/android/window/DisplayAreaOrganizer.java
+++ b/core/java/android/window/DisplayAreaOrganizer.java
@@ -78,6 +78,12 @@
public static final int FEATURE_FULLSCREEN_MAGNIFICATION = FEATURE_SYSTEM_FIRST + 5;
/**
+ * Display area for hiding display cutout feature
+ * @hide
+ */
+ public static final int FEATURE_HIDE_DISPLAY_CUTOUT = FEATURE_SYSTEM_FIRST + 6;
+
+ /**
* The last boundary of display area for system features
*/
public static final int FEATURE_SYSTEM_LAST = 10_000;
diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl
index f8aac42..3874de3 100644
--- a/core/java/com/android/internal/app/ISoundTriggerService.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl
@@ -38,8 +38,12 @@
*
* It is good practice to clear the binder calling identity prior to calling this, in case the
* caller is ever in the same process as the callee.
+ *
+ * The binder object being passed is used by the server to keep track of client death, in order
+ * to clean-up whenever that happens.
*/
- ISoundTriggerSession attachAsOriginator(in Identity originatorIdentity);
+ ISoundTriggerSession attachAsOriginator(in Identity originatorIdentity,
+ IBinder client);
/**
* Creates a new session.
@@ -54,7 +58,11 @@
*
* It is good practice to clear the binder calling identity prior to calling this, in case the
* caller is ever in the same process as the callee.
+ *
+ * The binder object being passed is used by the server to keep track of client death, in order
+ * to clean-up whenever that happens.
*/
ISoundTriggerSession attachAsMiddleman(in Identity middlemanIdentity,
- in Identity originatorIdentity);
+ in Identity originatorIdentity,
+ IBinder client);
}
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 81c61aa..db25514 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -216,7 +216,11 @@
* Caller must provide an identity, used for permission tracking purposes.
* The uid/pid elements of the identity will be ignored by the server and replaced with the ones
* provided by binder.
+ *
+ * The client argument is any binder owned by the client, used for tracking is death and
+ * cleaning up in this event.
*/
IVoiceInteractionSoundTriggerSession createSoundTriggerSessionAsOriginator(
- in Identity originatorIdentity);
+ in Identity originatorIdentity,
+ IBinder client);
}
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 137430b..bb7e2af 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -348,6 +348,57 @@
}
/**
+ * A helper method to translate interaction type to CUJ name.
+ *
+ * @param interactionType the interaction type defined in AtomsProto.java
+ * @return the name of the interaction type
+ */
+ public static String getNameOfInteraction(int interactionType) {
+ // There is an offset amount of 1 between cujType and interactionType.
+ return getNameOfCuj(interactionType - 1);
+ }
+
+ private static String getNameOfCuj(int cujType) {
+ switch (cujType) {
+ case CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE:
+ return "SHADE_EXPAND_COLLAPSE";
+ case CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE_LOCK:
+ return "SHADE_EXPAND_COLLAPSE_LOCK";
+ case CUJ_NOTIFICATION_SHADE_SCROLL_FLING:
+ return "SHADE_SCROLL_FLING";
+ case CUJ_NOTIFICATION_SHADE_ROW_EXPAND:
+ return "SHADE_ROW_EXPAND";
+ case CUJ_NOTIFICATION_SHADE_ROW_SWIPE:
+ return "SHADE_ROW_SWIPE";
+ case CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE:
+ return "SHADE_QS_EXPAND_COLLAPSE";
+ case CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE:
+ return "SHADE_QS_SCROLL_SWIPE";
+ case CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS:
+ return "LAUNCHER_APP_LAUNCH_FROM_RECENTS";
+ case CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON:
+ return "LAUNCHER_APP_LAUNCH_FROM_ICON";
+ case CUJ_LAUNCHER_APP_CLOSE_TO_HOME:
+ return "LAUNCHER_APP_CLOSE_TO_HOME";
+ case CUJ_LAUNCHER_APP_CLOSE_TO_PIP:
+ return "LAUNCHER_APP_CLOSE_TO_PIP";
+ case CUJ_LAUNCHER_QUICK_SWITCH:
+ return "LAUNCHER_QUICK_SWITCH";
+ case CUJ_NOTIFICATION_HEADS_UP_APPEAR:
+ return "NOTIFICATION_HEADS_UP_APPEAR";
+ case CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR:
+ return "NOTIFICATION_HEADS_UP_DISAPPEAR";
+ case CUJ_NOTIFICATION_ADD:
+ return "NOTIFICATION_ADD";
+ case CUJ_NOTIFICATION_REMOVE:
+ return "NOTIFICATION_REMOVE";
+ case CUJ_NOTIFICATION_APP_START:
+ return "NOTIFICATION_APP_START";
+ }
+ return "UNKNOWN";
+ }
+
+ /**
* A class to represent a session.
*/
public static class Session {
diff --git a/core/java/com/android/internal/os/BaseCommand.java b/core/java/com/android/internal/os/BaseCommand.java
index c110b26..c85b5d7 100644
--- a/core/java/com/android/internal/os/BaseCommand.java
+++ b/core/java/com/android/internal/os/BaseCommand.java
@@ -18,9 +18,10 @@
package com.android.internal.os;
import android.compat.annotation.UnsupportedAppUsage;
-import android.os.BasicShellCommandHandler;
import android.os.Build;
+import com.android.modules.utils.BasicShellCommandHandler;
+
import java.io.PrintStream;
public abstract class BaseCommand {
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 433a46b..3295df1 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4510,4 +4510,7 @@
activity-level letterboxing (size-compat mode). Therefore this override can control the
maximum screen area that can be occupied by the app in the letterbox mode. -->
<item name="config_taskLetterboxAspectRatio" format="float" type="dimen">0.0</item>
+
+ <!-- If true, hide the display cutout with display area -->
+ <bool name="config_hideDisplayCutoutWithDisplayArea">false</bool>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6960fb3..fba431c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4095,4 +4095,6 @@
<java-symbol type="dimen" name="controls_thumbnail_image_max_width" />
<java-symbol type="dimen" name="config_taskLetterboxAspectRatio" />
+
+ <java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
</resources>
diff --git a/core/tests/coretests/src/android/graphics/TypefaceTest.java b/core/tests/coretests/src/android/graphics/TypefaceTest.java
index cfed2ce..4393e9e7 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceTest.java
@@ -17,12 +17,14 @@
package android.graphics;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
+import android.graphics.fonts.SystemFonts;
+import android.os.SharedMemory;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
@@ -35,9 +37,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Random;
@RunWith(AndroidJUnit4.class)
@@ -54,14 +55,33 @@
Typeface.create(Typeface.MONOSPACE, 0)
};
+ private static final int[] STYLES = {
+ Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC, Typeface.BOLD_ITALIC,
+ };
+
@SmallTest
@Test
- public void testBasic() throws Exception {
- assertTrue("basic", Typeface.DEFAULT != null);
- assertTrue("basic", Typeface.DEFAULT_BOLD != null);
- assertTrue("basic", Typeface.SANS_SERIF != null);
- assertTrue("basic", Typeface.SERIF != null);
- assertTrue("basic", Typeface.MONOSPACE != null);
+ public void testBasic() {
+ assertNotEquals("basic", 0, Typeface.DEFAULT.native_instance);
+ assertNotEquals("basic", 0, Typeface.DEFAULT_BOLD.native_instance);
+ assertNotEquals("basic", 0, Typeface.SANS_SERIF.native_instance);
+ assertNotEquals("basic", 0, Typeface.SERIF.native_instance);
+ assertNotEquals("basic", 0, Typeface.MONOSPACE.native_instance);
+ assertEquals("basic", Typeface.NORMAL, Typeface.DEFAULT.getStyle());
+ assertEquals("basic", Typeface.BOLD, Typeface.DEFAULT_BOLD.getStyle());
+ assertEquals("basic", Typeface.NORMAL, Typeface.SANS_SERIF.getStyle());
+ assertEquals("basic", Typeface.NORMAL, Typeface.SERIF.getStyle());
+ assertEquals("basic", Typeface.NORMAL, Typeface.MONOSPACE.getStyle());
+ }
+
+ @SmallTest
+ @Test
+ public void testDefaults() {
+ for (int style : STYLES) {
+ String msg = "style = " + style;
+ assertNotEquals(msg, 0, Typeface.defaultFromStyle(style).native_instance);
+ assertEquals(msg, style, Typeface.defaultFromStyle(style).getStyle());
+ }
}
@SmallTest
@@ -178,21 +198,67 @@
@SmallTest
@Test
public void testSerialize() throws Exception {
- int size = Typeface.writeTypefaces(null, Arrays.asList(mFaces));
- ByteBuffer buffer = ByteBuffer.allocateDirect(size);
- Typeface.writeTypefaces(buffer, Arrays.asList(mFaces));
- List<Typeface> copiedTypefaces = Typeface.readTypefaces(buffer);
- assertNotNull(copiedTypefaces);
- assertEquals(mFaces.length, copiedTypefaces.size());
- for (int i = 0; i < mFaces.length; i++) {
- Typeface original = mFaces[i];
- Typeface copied = copiedTypefaces.get(i);
+ HashMap<String, Typeface> systemFontMap = new HashMap<>();
+ Typeface.initSystemDefaultTypefaces(systemFontMap, SystemFonts.getRawSystemFallbackMap(),
+ SystemFonts.getAliases());
+ SharedMemory sharedMemory = Typeface.serializeFontMap(systemFontMap);
+ Map<String, Typeface> copiedFontMap = Typeface.deserializeFontMap(sharedMemory);
+ assertEquals(systemFontMap.size(), copiedFontMap.size());
+ for (String key : systemFontMap.keySet()) {
+ assertTrue(copiedFontMap.containsKey(key));
+ Typeface original = systemFontMap.get(key);
+ Typeface copied = copiedFontMap.get(key);
assertEquals(original.getStyle(), copied.getStyle());
assertEquals(original.getWeight(), copied.getWeight());
assertEquals(measureText(original, "hello"), measureText(copied, "hello"), 1e-6);
}
}
+ @SmallTest
+ @Test
+ public void testSetSystemFontMap() throws Exception {
+ Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ Resources res = context.getResources();
+ Map<String, Typeface> fontMap = Map.of(
+ "sans-serif", Typeface.create(res.getFont(R.font.samplefont), Typeface.NORMAL),
+ "serif", Typeface.create(res.getFont(R.font.samplefont2), Typeface.NORMAL),
+ "monospace", Typeface.create(res.getFont(R.font.samplefont3), Typeface.NORMAL),
+ "sample", Typeface.create(res.getFont(R.font.samplefont4), Typeface.NORMAL),
+ "sample-italic", Typeface.create(res.getFont(R.font.samplefont4), Typeface.ITALIC));
+ Typeface.setSystemFontMap(fontMap);
+
+ // Test public static final fields
+ assertEquals(fontMap.get("sans-serif").native_instance, Typeface.DEFAULT.native_instance);
+ assertNotEquals(0, Typeface.DEFAULT_BOLD.native_instance);
+ assertEquals(
+ fontMap.get("sans-serif").native_instance, Typeface.SANS_SERIF.native_instance);
+ assertEquals(fontMap.get("serif").native_instance, Typeface.SERIF.native_instance);
+ assertEquals(fontMap.get("monospace").native_instance, Typeface.MONOSPACE.native_instance);
+ assertEquals(Typeface.NORMAL, Typeface.DEFAULT.getStyle());
+ assertEquals(Typeface.BOLD, Typeface.DEFAULT_BOLD.getStyle());
+ assertEquals(Typeface.NORMAL, Typeface.SANS_SERIF.getStyle());
+ assertEquals(Typeface.NORMAL, Typeface.SERIF.getStyle());
+ assertEquals(Typeface.NORMAL, Typeface.MONOSPACE.getStyle());
+
+ // Test defaults
+ assertEquals(
+ fontMap.get("sans-serif").native_instance,
+ Typeface.defaultFromStyle(Typeface.NORMAL).native_instance);
+ for (int style : STYLES) {
+ String msg = "style = " + style;
+ assertNotEquals(msg, 0, Typeface.defaultFromStyle(style).native_instance);
+ assertEquals(msg, style, Typeface.defaultFromStyle(style).getStyle());
+ }
+
+ // Test create()
+ assertEquals(
+ fontMap.get("sample").native_instance,
+ Typeface.create("sample", Typeface.NORMAL).native_instance);
+ assertEquals(
+ fontMap.get("sample-italic").native_instance,
+ Typeface.create("sample-italic", Typeface.ITALIC).native_instance);
+ }
+
private static float measureText(Typeface typeface, String text) {
Paint paint = new Paint();
paint.setTypeface(typeface);
diff --git a/core/tests/uwbtests/src/android/uwb/RangingParamsTest.java b/core/tests/uwbtests/src/android/uwb/RangingParamsTest.java
deleted file mode 100644
index 8095c99..0000000
--- a/core/tests/uwbtests/src/android/uwb/RangingParamsTest.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright 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 android.uwb;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.os.Parcel;
-import android.os.PersistableBundle;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.time.Duration;
-
-/**
- * Test of {@link RangingParams}.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class RangingParamsTest {
-
- @Test
- public void testParams_Build() {
- UwbAddress local = UwbAddress.fromBytes(new byte[] {(byte) 0xA0, (byte) 0x57});
- UwbAddress remote = UwbAddress.fromBytes(new byte[] {(byte) 0x4D, (byte) 0x8C});
- int channel = 9;
- int rxPreamble = 16;
- int txPreamble = 21;
- boolean isController = true;
- boolean isInitiator = false;
- @RangingParams.StsPhyPacketType int stsPhyType = RangingParams.STS_PHY_PACKET_TYPE_SP2;
- Duration samplePeriod = Duration.ofSeconds(1, 234);
- PersistableBundle specParams = new PersistableBundle();
- specParams.putString("protocol", "some_protocol");
-
- RangingParams params = new RangingParams.Builder()
- .setChannelNumber(channel)
- .setReceivePreambleCodeIndex(rxPreamble)
- .setTransmitPreambleCodeIndex(txPreamble)
- .setLocalDeviceAddress(local)
- .addRemoteDeviceAddress(remote)
- .setIsController(isController)
- .setIsInitiator(isInitiator)
- .setSamplePeriod(samplePeriod)
- .setStsPhPacketType(stsPhyType)
- .setSpecificationParameters(specParams)
- .build();
-
- assertEquals(params.getLocalDeviceAddress(), local);
- assertEquals(params.getRemoteDeviceAddresses().size(), 1);
- assertEquals(params.getRemoteDeviceAddresses().get(0), remote);
- assertEquals(params.getChannelNumber(), channel);
- assertEquals(params.isController(), isController);
- assertEquals(params.isInitiator(), isInitiator);
- assertEquals(params.getRxPreambleIndex(), rxPreamble);
- assertEquals(params.getTxPreambleIndex(), txPreamble);
- assertEquals(params.getStsPhyPacketType(), stsPhyType);
- assertEquals(params.getSamplingPeriod(), samplePeriod);
- assertTrue(params.getSpecificationParameters().kindofEquals(specParams));
- }
-
- @Test
- public void testParcel() {
- Parcel parcel = Parcel.obtain();
- RangingParams params = new RangingParams.Builder()
- .setChannelNumber(9)
- .setReceivePreambleCodeIndex(16)
- .setTransmitPreambleCodeIndex(21)
- .setLocalDeviceAddress(UwbTestUtils.getUwbAddress(false))
- .addRemoteDeviceAddress(UwbTestUtils.getUwbAddress(true))
- .setIsController(false)
- .setIsInitiator(true)
- .setSamplePeriod(Duration.ofSeconds(2))
- .setStsPhPacketType(RangingParams.STS_PHY_PACKET_TYPE_SP1)
- .build();
- params.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
- RangingParams fromParcel = RangingParams.CREATOR.createFromParcel(parcel);
- assertEquals(params, fromParcel);
- }
-}
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index c58e5f3..d44cb9c 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -34,8 +34,12 @@
import android.graphics.fonts.SystemFonts;
import android.os.Build;
import android.os.ParcelFileDescriptor;
+import android.os.SharedMemory;
+import android.os.Trace;
import android.provider.FontRequest;
import android.provider.FontsContract;
+import android.system.ErrnoException;
+import android.system.OsConstants;
import android.text.FontConfig;
import android.util.Base64;
import android.util.LongSparseArray;
@@ -50,6 +54,7 @@
import libcore.util.NativeAllocationRegistry;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -57,6 +62,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -79,19 +85,19 @@
Typeface.class.getClassLoader(), nativeGetReleaseFunc());
/** The default NORMAL typeface object */
- public static final Typeface DEFAULT;
+ public static final Typeface DEFAULT = new Typeface();
/**
* The default BOLD typeface object. Note: this may be not actually be
* bold, depending on what fonts are installed. Call getStyle() to know
* for sure.
*/
- public static final Typeface DEFAULT_BOLD;
+ public static final Typeface DEFAULT_BOLD = new Typeface();
/** The NORMAL style of the default sans serif typeface. */
- public static final Typeface SANS_SERIF;
+ public static final Typeface SANS_SERIF = new Typeface();
/** The NORMAL style of the default serif typeface. */
- public static final Typeface SERIF;
+ public static final Typeface SERIF = new Typeface();
/** The NORMAL style of the default monospace typeface. */
- public static final Typeface MONOSPACE;
+ public static final Typeface MONOSPACE = new Typeface();
/**
* The default {@link Typeface}s for different text styles.
@@ -133,7 +139,7 @@
* Use public API {@link #create(String, int)} to get the typeface for given familyName.
*/
@UnsupportedAppUsage(trackingBug = 123769347)
- static final Map<String, Typeface> sSystemFontMap;
+ static final Map<String, Typeface> sSystemFontMap = new HashMap<>();
// We cannot support sSystemFallbackMap since we will migrate to public FontFamily API.
/**
@@ -150,6 +156,9 @@
@UnsupportedAppUsage
public long native_instance;
+ // This destructs native_instance.
+ private Runnable mCleaner;
+
/** @hide */
@IntDef(value = {NORMAL, BOLD, ITALIC, BOLD_ITALIC})
@Retention(RetentionPolicy.SOURCE)
@@ -1086,19 +1095,41 @@
return new Typeface(nativeCreateFromArray(ptrArray, weight, italic));
}
+ /**
+ * Creates a fake Typeface object that are later modified to point to another Typeface object
+ * using {@link #reset(Typeface, int)}.
+ *
+ * <p>This constructor is only for filling 'static final' Typeface instances in Zygote process.
+ */
+ private Typeface() {
+ }
+
// don't allow clients to call this directly
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private Typeface(long ni) {
+ init(ni);
+ }
+
+ private void init(long ni) {
if (ni == 0) {
throw new RuntimeException("native typeface cannot be made");
}
-
+ if (mCleaner != null) {
+ mCleaner.run();
+ }
native_instance = ni;
- sRegistry.registerNativeAllocation(this, native_instance);
+ mCleaner = sRegistry.registerNativeAllocation(this, native_instance);
mStyle = nativeGetStyle(ni);
mWeight = nativeGetWeight(ni);
}
+ private void reset(Typeface typeface, int style) {
+ // Just create a new native instance without looking into the cache, because we are going to
+ // claim the ownership.
+ long ni = nativeCreateFromTypeface(typeface.native_instance, style);
+ init(ni);
+ }
+
private static Typeface getSystemDefaultTypeface(@NonNull String familyName) {
Typeface tf = sSystemFontMap.get(familyName);
return tf == null ? Typeface.DEFAULT : tf;
@@ -1137,23 +1168,105 @@
}
}
- static {
- final HashMap<String, Typeface> systemFontMap = new HashMap<>();
- initSystemDefaultTypefaces(systemFontMap, SystemFonts.getRawSystemFallbackMap(),
- SystemFonts.getAliases());
- sSystemFontMap = Collections.unmodifiableMap(systemFontMap);
+ /** @hide */
+ public static SharedMemory serializeFontMap(Map<String, Typeface> fontMap)
+ throws IOException, ErrnoException {
+ long[] nativePtrs = new long[fontMap.size()];
+ // The name table will not be large, so let's create a byte array in memory.
+ ByteArrayOutputStream namesBytes = new ByteArrayOutputStream();
+ int i = 0;
+ for (Map.Entry<String, Typeface> entry : fontMap.entrySet()) {
+ nativePtrs[i++] = entry.getValue().native_instance;
+ writeString(namesBytes, entry.getKey());
+ }
+ int typefacesBytesCount = nativeWriteTypefaces(null, nativePtrs);
+ // int (typefacesBytesCount), typefaces, namesBytes
+ SharedMemory sharedMemory = SharedMemory.create(
+ "fontMap", Integer.BYTES + typefacesBytesCount + namesBytes.size());
+ ByteBuffer writableBuffer = sharedMemory.mapReadWrite().order(ByteOrder.BIG_ENDIAN);
+ try {
+ writableBuffer.putInt(typefacesBytesCount);
+ int writtenBytesCount = nativeWriteTypefaces(writableBuffer.slice(), nativePtrs);
+ if (writtenBytesCount != typefacesBytesCount) {
+ throw new IOException(String.format("Unexpected bytes written: %d, expected: %d",
+ writtenBytesCount, typefacesBytesCount));
+ }
+ writableBuffer.position(writableBuffer.position() + writtenBytesCount);
+ writableBuffer.put(namesBytes.toByteArray());
+ } finally {
+ SharedMemory.unmap(writableBuffer);
+ }
+ sharedMemory.setProtect(OsConstants.PROT_READ);
+ return sharedMemory;
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public static Map<String, Typeface> deserializeFontMap(SharedMemory sharedMemory)
+ throws IOException, ErrnoException {
+ Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "deserializeFontMap");
+ try {
+ // TODO: unmap buffer when all Typefaces are gone.
+ // Currently deserializeFontMap() should be called at most once per process, so we don't
+ // need to unmap this buffer.
+ ByteBuffer buffer = sharedMemory.mapReadOnly().order(ByteOrder.BIG_ENDIAN);
+ Map<String, Typeface> fontMap = new HashMap<>();
+ int typefacesBytesCount = buffer.getInt();
+ long[] nativePtrs = nativeReadTypefaces(buffer.slice());
+ if (nativePtrs == null) {
+ throw new IOException("Could not read typefaces");
+ }
+ buffer.position(buffer.position() + typefacesBytesCount);
+ for (long nativePtr : nativePtrs) {
+ String name = readString(buffer);
+ fontMap.put(name, new Typeface(nativePtr));
+ }
+ return fontMap;
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
+ }
+ }
+
+ private static String readString(ByteBuffer buffer) {
+ int length = buffer.getInt();
+ byte[] bytes = new byte[length];
+ buffer.get(bytes);
+ return new String(bytes);
+ }
+
+ private static void writeString(ByteArrayOutputStream bos, String value) throws IOException {
+ byte[] bytes = value.getBytes();
+ writeInt(bos, bytes.length);
+ bos.write(bytes);
+ }
+
+ private static void writeInt(ByteArrayOutputStream bos, int value) {
+ // Write in the big endian order.
+ bos.write((value >> 24) & 0xFF);
+ bos.write((value >> 16) & 0xFF);
+ bos.write((value >> 8) & 0xFF);
+ bos.write(value & 0xFF);
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public static void setSystemFontMap(Map<String, Typeface> systemFontMap) {
+ sSystemFontMap.clear();
+ sSystemFontMap.putAll(systemFontMap);
// We can't assume DEFAULT_FAMILY available on Roboletric.
if (sSystemFontMap.containsKey(DEFAULT_FAMILY)) {
setDefault(sSystemFontMap.get(DEFAULT_FAMILY));
}
- // Set up defaults and typefaces exposed in public API
- DEFAULT = create((String) null, 0);
- DEFAULT_BOLD = create((String) null, Typeface.BOLD);
- SANS_SERIF = create("sans-serif", 0);
- SERIF = create("serif", 0);
- MONOSPACE = create("monospace", 0);
+ // Set up defaults and typefaces exposed in public API.
+ // We cannot use getSystemDefaultTypeface() here to initialize DEFAULT, because it returns
+ // DEFAULT.
+ DEFAULT.reset(sDefaultTypeface, Typeface.NORMAL);
+ DEFAULT_BOLD.reset(sDefaultTypeface, Typeface.BOLD);
+ SANS_SERIF.reset(getSystemDefaultTypeface("sans-serif"), Typeface.NORMAL);
+ SERIF.reset(getSystemDefaultTypeface("serif"), Typeface.NORMAL);
+ MONOSPACE.reset(getSystemDefaultTypeface("monospace"), Typeface.NORMAL);
sDefaults = new Typeface[] {
DEFAULT,
@@ -1173,6 +1286,13 @@
}
}
+ static {
+ final HashMap<String, Typeface> systemFontMap = new HashMap<>();
+ initSystemDefaultTypefaces(systemFontMap, SystemFonts.getRawSystemFallbackMap(),
+ SystemFonts.getAliases());
+ setSystemFontMap(systemFontMap);
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -1210,36 +1330,6 @@
return Arrays.binarySearch(mSupportedAxes, axis) >= 0;
}
- /**
- * Writes Typeface instances to the ByteBuffer and returns the number of bytes written.
- *
- * <p>If {@code buffer} is null, this method returns the number of bytes required to serialize
- * the typefaces, without writing anything.
- * @hide
- */
- public static int writeTypefaces(
- @Nullable ByteBuffer buffer, @NonNull List<Typeface> typefaces) {
- long[] nativePtrs = new long[typefaces.size()];
- for (int i = 0; i < nativePtrs.length; i++) {
- nativePtrs[i] = typefaces.get(i).native_instance;
- }
- return nativeWriteTypefaces(buffer, nativePtrs);
- }
-
- /**
- * Reads serialized Typeface instances from the ByteBuffer. Returns null on errors.
- * @hide
- */
- public static @Nullable List<Typeface> readTypefaces(@NonNull ByteBuffer buffer) {
- long[] nativePtrs = nativeReadTypefaces(buffer);
- if (nativePtrs == null) return null;
- List<Typeface> typefaces = new ArrayList<>(nativePtrs.length);
- for (long nativePtr : nativePtrs) {
- typefaces.add(new Typeface(nativePtr));
- }
- return typefaces;
- }
-
private static native long nativeCreateFromTypeface(long native_instance, int style);
private static native long nativeCreateFromTypefaceWithExactStyle(
long native_instance, int weight, boolean italic);
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
index 71e6559..d1b4464 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
@@ -23,6 +23,7 @@
import android.security.keymaster.ExportResult;
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterDefs;
+import android.sysprop.Keystore2Properties;
import java.io.IOException;
import java.security.KeyFactory;
@@ -111,6 +112,26 @@
putSecretKeyFactoryImpl("HmacSHA512");
}
+ private static boolean sKeystore2Enabled;
+
+ /**
+ * This function indicates whether or not Keystore 2.0 is enabled. Some parts of the
+ * Keystore SPI must behave subtly differently when Keystore 2.0 is enabled. However,
+ * the platform property that indicates that Keystore 2.0 is enabled is not readable
+ * by applications. So we set this value when {@code install()} is called because it
+ * is called by zygote, which can access Keystore2Properties.
+ *
+ * This function can be removed once the transition to Keystore 2.0 is complete.
+ * b/171305684
+ *
+ * @return true if Keystore 2.0 is enabled.
+ * @hide
+ */
+ public static boolean isKeystore2Enabled() {
+ return sKeystore2Enabled;
+ }
+
+
/**
* Installs a new instance of this provider (and the
* {@link AndroidKeyStoreBCWorkaroundProvider}).
@@ -138,6 +159,11 @@
// priority.
Security.addProvider(workaroundProvider);
}
+
+ // {@code install()} is run by zygote when this property is still accessible. We store its
+ // value so that the Keystore SPI can act accordingly without having to access an internal
+ // property.
+ sKeystore2Enabled = Keystore2Properties.keystore2_enabled().orElse(false);
}
private void putSecretKeyFactoryImpl(String algorithm) {
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
index 9707260..3694d63 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
@@ -211,7 +211,11 @@
userAuthenticationValidWhileOnBody,
trustedUserPresenceRequired,
invalidatedByBiometricEnrollment,
- userConfirmationRequired);
+ userConfirmationRequired,
+ // Keystore 1.0 does not tell us the exact security level of the key
+ // so we report an unknown but secure security level.
+ insideSecureHardware ? KeyProperties.SECURITY_LEVEL_UNKNOWN_SECURE
+ : KeyProperties.SECURITY_LEVEL_SOFTWARE);
}
private static BigInteger getGateKeeperSecureUserId() throws ProviderException {
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 688c4a7..e9aac7d 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -27,7 +27,6 @@
import android.hardware.biometrics.BiometricPrompt;
import android.os.Build;
import android.security.GateKeeper;
-import android.security.KeyStore;
import android.text.TextUtils;
import java.math.BigInteger;
@@ -246,7 +245,7 @@
private static final Date DEFAULT_CERT_NOT_AFTER = new Date(2461449600000L); // Jan 1 2048
private final String mKeystoreAlias;
- private final int mUid;
+ private final int mNamespace;
private final int mKeySize;
private final AlgorithmParameterSpec mSpec;
private final X500Principal mCertificateSubject;
@@ -286,7 +285,7 @@
*/
public KeyGenParameterSpec(
String keyStoreAlias,
- int uid,
+ int namespace,
int keySize,
AlgorithmParameterSpec spec,
X500Principal certificateSubject,
@@ -337,7 +336,7 @@
}
mKeystoreAlias = keyStoreAlias;
- mUid = uid;
+ mNamespace = namespace;
mKeySize = keySize;
mSpec = spec;
mCertificateSubject = certificateSubject;
@@ -382,11 +381,43 @@
* Returns the UID which will own the key. {@code -1} is an alias for the UID of the current
* process.
*
+ * @deprecated See deprecation message on {@link KeyGenParameterSpec.Builder#setUid(int)}.
+ * Known namespaces will be translated to their legacy UIDs. Unknown
+ * Namespaces will yield {@link IllegalStateException}.
+ *
* @hide
*/
@UnsupportedAppUsage
+ @Deprecated
public int getUid() {
- return mUid;
+ if (!AndroidKeyStoreProvider.isKeystore2Enabled()) {
+ // If Keystore2 has not been enabled we have to behave as if mNamespace is actually
+ // a UID, because we are still being used with the old Keystore SPI.
+ // TODO This if statement and body can be removed when the Keystore 2 migration is
+ // complete. b/171563717
+ return mNamespace;
+ }
+ try {
+ return KeyProperties.namespaceToLegacyUid(mNamespace);
+ } catch (IllegalArgumentException e) {
+ throw new IllegalStateException("getUid called on KeyGenParameterSpec with non legacy"
+ + " keystore namespace.", e);
+ }
+ }
+
+ /**
+ * Returns the target namespace for the key.
+ * See {@link KeyGenParameterSpec.Builder#setNamespace(int)}.
+ *
+ * @return The numeric namespace as configured in the keystore2_key_contexts files of Android's
+ * SEPolicy.
+ * TODO b/171806779 link to public Keystore 2.0 documentation.
+ * See bug for more details for now.
+ * @hide
+ */
+ @SystemApi
+ public int getNamespace() {
+ return mNamespace;
}
/**
@@ -767,7 +798,7 @@
private final String mKeystoreAlias;
private @KeyProperties.PurposeEnum int mPurposes;
- private int mUid = KeyStore.UID_SELF;
+ private int mNamespace = KeyProperties.NAMESPACE_APPLICATION;
private int mKeySize = -1;
private AlgorithmParameterSpec mSpec;
private X500Principal mCertificateSubject;
@@ -830,7 +861,7 @@
*/
public Builder(@NonNull KeyGenParameterSpec sourceSpec) {
this(sourceSpec.getKeystoreAlias(), sourceSpec.getPurposes());
- mUid = sourceSpec.getUid();
+ mNamespace = sourceSpec.getNamespace();
mKeySize = sourceSpec.getKeySize();
mSpec = sourceSpec.getAlgorithmParameterSpec();
mCertificateSubject = sourceSpec.getCertificateSubject();
@@ -873,12 +904,51 @@
*
* @param uid UID or {@code -1} for the UID of the current process.
*
+ * @deprecated Setting the UID of the target namespace is based on a hardcoded
+ * hack in the Keystore service. This is no longer supported with Keystore 2.0/Android S.
+ * Instead, dedicated non UID based namespaces can be configured in SEPolicy using
+ * the keystore2_key_contexts files. The functionality of this method will be supported
+ * by mapping knows special UIDs, such as WIFI, to the newly configured SELinux based
+ * namespaces. Unknown UIDs will yield {@link IllegalArgumentException}.
+ *
* @hide
*/
@SystemApi
@NonNull
+ @Deprecated
public Builder setUid(int uid) {
- mUid = uid;
+ if (!AndroidKeyStoreProvider.isKeystore2Enabled()) {
+ // If Keystore2 has not been enabled we have to behave as if mNamespace is actually
+ // a UID, because we are still being used with the old Keystore SPI.
+ // TODO This if statement and body can be removed when the Keystore 2 migration is
+ // complete. b/171563717
+ mNamespace = uid;
+ return this;
+ }
+ mNamespace = KeyProperties.legacyUidToNamespace(uid);
+ return this;
+ }
+
+ /**
+ * Set the designated SELinux namespace that the key shall live in. The caller must
+ * have sufficient permissions to install a key in the given namespace. Namespaces
+ * can be created using SEPolicy. The keystore2_key_contexts files map numeric
+ * namespaces to SELinux labels, and SEPolicy can be used to grant access to these
+ * namespaces to the desired target context. This is the preferred way to share
+ * keys between system and vendor components, e.g., WIFI settings and WPA supplicant.
+ *
+ * @param namespace Numeric SELinux namespace as configured in keystore2_key_contexts
+ * of Android's SEPolicy.
+ * TODO b/171806779 link to public Keystore 2.0 documentation.
+ * See bug for more details for now.
+ * @return this Builder object.
+ *
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ public Builder setNamespace(int namespace) {
+ mNamespace = namespace;
return this;
}
@@ -1489,7 +1559,7 @@
public KeyGenParameterSpec build() {
return new KeyGenParameterSpec(
mKeystoreAlias,
- mUid,
+ mNamespace,
mKeySize,
mSpec,
mCertificateSubject,
diff --git a/keystore/java/android/security/keystore/KeyInfo.java b/keystore/java/android/security/keystore/KeyInfo.java
index d891a25..7158d0c 100644
--- a/keystore/java/android/security/keystore/KeyInfo.java
+++ b/keystore/java/android/security/keystore/KeyInfo.java
@@ -84,6 +84,7 @@
private final boolean mTrustedUserPresenceRequired;
private final boolean mInvalidatedByBiometricEnrollment;
private final boolean mUserConfirmationRequired;
+ private final @KeyProperties.SecurityLevelEnum int mSecurityLevel;
/**
* @hide
@@ -107,7 +108,8 @@
boolean userAuthenticationValidWhileOnBody,
boolean trustedUserPresenceRequired,
boolean invalidatedByBiometricEnrollment,
- boolean userConfirmationRequired) {
+ boolean userConfirmationRequired,
+ @KeyProperties.SecurityLevelEnum int securityLevel) {
mKeystoreAlias = keystoreKeyAlias;
mInsideSecureHardware = insideSecureHardware;
mOrigin = origin;
@@ -131,6 +133,7 @@
mTrustedUserPresenceRequired = trustedUserPresenceRequired;
mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment;
mUserConfirmationRequired = userConfirmationRequired;
+ mSecurityLevel = securityLevel;
}
/**
@@ -144,7 +147,10 @@
* Returns {@code true} if the key resides inside secure hardware (e.g., Trusted Execution
* Environment (TEE) or Secure Element (SE)). Key material of such keys is available in
* plaintext only inside the secure hardware and is not exposed outside of it.
+ *
+ * @deprecated This method is superseded by @see getSecurityLevel.
*/
+ @Deprecated
public boolean isInsideSecureHardware() {
return mInsideSecureHardware;
}
@@ -355,4 +361,17 @@
public boolean isTrustedUserPresenceRequired() {
return mTrustedUserPresenceRequired;
}
+
+ /**
+ * Returns the security level that the key is protected by.
+ * {@code KeyProperties.SecurityLevelEnum.TRUSTED_ENVIRONMENT} and
+ * {@code KeyProperties.SecurityLevelEnum.STRONGBOX} indicate that the key material resides
+ * in secure hardware. Key material of such keys is available in
+ * plaintext only inside the secure hardware and is not exposed outside of it.
+ *
+ * <p>See {@link KeyProperties}.{@code SecurityLevelEnum} constants.
+ */
+ public @KeyProperties.SecurityLevelEnum int getSecurityLevel() {
+ return mSecurityLevel;
+ }
}
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index 9050c69..5928540 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringDef;
+import android.security.KeyStore;
import android.security.keymaster.KeymasterDefs;
import libcore.util.EmptyArray;
@@ -857,4 +858,43 @@
}
}
+ /**
+ * This value indicates the implicit keystore namespace of the calling application.
+ * It is used by default. Only select system components can choose a different namespace
+ * which it must be configured in SEPolicy.
+ * @hide
+ */
+ public static final int NAMESPACE_APPLICATION = -1;
+
+ /**
+ * For legacy support, translate namespaces into known UIDs.
+ * @hide
+ */
+ public static int namespaceToLegacyUid(int namespace) {
+ switch (namespace) {
+ case NAMESPACE_APPLICATION:
+ return KeyStore.UID_SELF;
+ // TODO Translate WIFI and VPN UIDs once the namespaces are defined.
+ // b/171305388 and b/171305607
+ default:
+ throw new IllegalArgumentException("No UID corresponding to namespace "
+ + namespace);
+ }
+ }
+
+ /**
+ * For legacy support, translate namespaces into known UIDs.
+ * @hide
+ */
+ public static int legacyUidToNamespace(int uid) {
+ switch (uid) {
+ case KeyStore.UID_SELF:
+ return NAMESPACE_APPLICATION;
+ // TODO Translate WIFI and VPN UIDs once the namespaces are defined.
+ // b/171305388 and b/171305607
+ default:
+ throw new IllegalArgumentException("No namespace corresponding to uid "
+ + uid);
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS
index 4390004..36da7aa 100644
--- a/libs/WindowManager/Shell/OWNERS
+++ b/libs/WindowManager/Shell/OWNERS
@@ -1,4 +1,5 @@
# sysui owners
hwwang@google.com
mrenouf@google.com
-winsonc@google.com
\ No newline at end of file
+winsonc@google.com
+madym@google.com
diff --git a/libs/WindowManager/Shell/res/layout/bubble_menu_view.xml b/libs/WindowManager/Shell/res/layout/bubble_menu_view.xml
index 0c1d1a5..d19b653 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_menu_view.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_menu_view.xml
@@ -16,14 +16,16 @@
-->
<com.android.wm.shell.bubbles.BubbleMenuView
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:background="#66000000"
android:visibility="gone"
- android:id="@+id/bubble_menu_container">
+ android:id="@+id/bubble_menu_container"
+ tools:ignore="MissingClass">
<FrameLayout
- android:layout_height="@dimen/individual_bubble_size"
+ android:layout_height="@dimen/bubble_menu_item_height"
android:layout_width="wrap_content"
android:background="#FFFFFF"
android:id="@+id/bubble_menu_view">
diff --git a/libs/WindowManager/Shell/res/layout/bubble_overflow_button.xml b/libs/WindowManager/Shell/res/layout/bubble_overflow_button.xml
index 61000fe..e392cdc2 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_overflow_button.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_overflow_button.xml
@@ -17,6 +17,6 @@
<com.android.wm.shell.bubbles.BadgedImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/bubble_overflow_button"
- android:layout_width="@dimen/individual_bubble_size"
- android:layout_height="@dimen/individual_bubble_size"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:src="@drawable/bubble_ic_overflow_button"/>
diff --git a/libs/WindowManager/Shell/res/layout/bubble_view.xml b/libs/WindowManager/Shell/res/layout/bubble_view.xml
index a28bd678..2b4b9e9 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_view.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_view.xml
@@ -17,5 +17,5 @@
<com.android.wm.shell.bubbles.BadgedImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/bubble_view"
- android:layout_width="@dimen/individual_bubble_size"
- android:layout_height="@dimen/individual_bubble_size"/>
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 8a60aaf..25d034c 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -99,12 +99,12 @@
<dimen name="bubble_flyout_avatar_message_space">6dp</dimen>
<!-- Padding between status bar and bubbles when displayed in expanded state -->
<dimen name="bubble_padding_top">16dp</dimen>
+ <!-- Max amount of space between bubbles when expanded. -->
+ <dimen name="bubble_max_spacing">16dp</dimen>
<!-- Size of individual bubbles. -->
<dimen name="individual_bubble_size">60dp</dimen>
<!-- Size of bubble bitmap. -->
<dimen name="bubble_bitmap_size">52dp</dimen>
- <!-- Size of bubble icon bitmap. -->
- <dimen name="bubble_overflow_icon_bitmap_size">24dp</dimen>
<!-- Extra padding added to the touchable rect for bubbles so they are easier to grab. -->
<dimen name="bubble_touch_padding">12dp</dimen>
<!-- Size of the circle around the bubbles when they're in the dismiss target. -->
@@ -136,6 +136,8 @@
<dimen name="bubble_dismiss_slop">16dp</dimen>
<!-- Height of button allowing users to adjust settings for bubbles. -->
<dimen name="bubble_manage_button_height">48dp</dimen>
+ <!-- Height of an item in the bubble manage menu. -->
+ <dimen name="bubble_menu_item_height">60dp</dimen>
<!-- Max width of the message bubble-->
<dimen name="bubble_message_max_width">144dp</dimen>
<!-- Min width of the message bubble -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellDump.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellDump.java
index 4ba84223..bb9accd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellDump.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellDump.java
@@ -16,8 +16,7 @@
package com.android.wm.shell;
-import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.splitscreen.SplitScreen;
@@ -33,16 +32,19 @@
private final Optional<SplitScreen> mSplitScreenOptional;
private final Optional<Pip> mPipOptional;
private final Optional<OneHanded> mOneHandedOptional;
+ private final Optional<HideDisplayCutout> mHideDisplayCutout;
private final ShellTaskOrganizer mShellTaskOrganizer;
public ShellDump(ShellTaskOrganizer shellTaskOrganizer,
Optional<SplitScreen> splitScreenOptional,
Optional<Pip> pipOptional,
- Optional<OneHanded> oneHandedOptional) {
+ Optional<OneHanded> oneHandedOptional,
+ Optional<HideDisplayCutout> hideDisplayCutout) {
mShellTaskOrganizer = shellTaskOrganizer;
mSplitScreenOptional = splitScreenOptional;
mPipOptional = pipOptional;
mOneHandedOptional = oneHandedOptional;
+ mHideDisplayCutout = hideDisplayCutout;
}
public void dump(PrintWriter pw) {
@@ -52,5 +54,6 @@
mPipOptional.ifPresent(pip -> pip.dump(pw));
mSplitScreenOptional.ifPresent(splitScreen -> splitScreen.dump(pw));
mOneHandedOptional.ifPresent(oneHanded -> oneHanded.dump(pw));
+ mHideDisplayCutout.ifPresent(hideDisplayCutout -> hideDisplayCutout.dump(pw));
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
index 4d06c03..32c6e36 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
@@ -31,7 +31,6 @@
import android.widget.ImageView;
import com.android.launcher3.icons.DotRenderer;
-import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
import java.util.EnumSet;
@@ -75,9 +74,8 @@
private boolean mDotIsAnimating = false;
private BubbleViewProvider mBubble;
+ private BubblePositioner mPositioner;
- private int mBubbleBitmapSize;
- private int mBubbleSize;
private DotRenderer mDotRenderer;
private DotRenderer.DrawParams mDrawParams;
private boolean mOnLeft;
@@ -101,18 +99,21 @@
public BadgedImageView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mBubbleBitmapSize = getResources().getDimensionPixelSize(R.dimen.bubble_bitmap_size);
- mBubbleSize = getResources().getDimensionPixelSize(R.dimen.individual_bubble_size);
mDrawParams = new DotRenderer.DrawParams();
- Path iconPath = PathParser.createPathFromPathData(
- getResources().getString(com.android.internal.R.string.config_icon_mask));
- mDotRenderer = new DotRenderer(mBubbleBitmapSize, iconPath, DEFAULT_PATH_SIZE);
-
setFocusable(true);
setClickable(true);
}
+ public void initialize(BubblePositioner positioner) {
+ mPositioner = positioner;
+
+ Path iconPath = PathParser.createPathFromPathData(
+ getResources().getString(com.android.internal.R.string.config_icon_mask));
+ mDotRenderer = new DotRenderer(mPositioner.getBubbleBitmapSize(),
+ iconPath, DEFAULT_PATH_SIZE);
+ }
+
public void showDotAndBadge(boolean onLeft) {
removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.BEHIND_STACK);
animateDotBadgePositions(onLeft);
@@ -186,7 +187,8 @@
* @param iconPath The new icon path to use when calculating dot position.
*/
void drawDot(Path iconPath) {
- mDotRenderer = new DotRenderer(mBubbleBitmapSize, iconPath, DEFAULT_PATH_SIZE);
+ mDotRenderer = new DotRenderer(mPositioner.getBubbleBitmapSize(),
+ iconPath, DEFAULT_PATH_SIZE);
invalidate();
}
@@ -310,13 +312,13 @@
bubbleCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
bubbleCanvas.setBitmap(bubble);
-
- final int badgeSize = (int) (ICON_BADGE_SCALE * mBubbleSize);
+ final int bubbleSize = bubble.getWidth();
+ final int badgeSize = (int) (ICON_BADGE_SCALE * bubbleSize);
if (mOnLeft) {
- badge.setBounds(0, mBubbleSize - badgeSize, badgeSize, mBubbleSize);
+ badge.setBounds(0, bubbleSize - badgeSize, badgeSize, bubbleSize);
} else {
- badge.setBounds(mBubbleSize - badgeSize, mBubbleSize - badgeSize,
- mBubbleSize, mBubbleSize);
+ badge.setBounds(bubbleSize - badgeSize, bubbleSize - badgeSize,
+ bubbleSize, bubbleSize);
}
badge.draw(bubbleCanvas);
bubbleCanvas.setBitmap(null);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 93ed395..122f917 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -310,7 +310,7 @@
*
* @param callback the callback to notify one the bubble is ready to be displayed.
* @param context the context for the bubble.
- * @param controller
+ * @param controller the bubble controller.
* @param stackView the stackView the bubble is eventually added to.
* @param iconFactory the iconfactory use to create badged images for the bubble.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 05acb55..eb3a3a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -95,12 +95,6 @@
private BubblePositioner mBubblePositioner;
private SysuiProxy mSysuiProxy;
- /**
- * The relative position of the stack when we removed it and nulled it out. If the stack is
- * re-created, it will re-appear at this position.
- */
- @Nullable private BubbleStackView.RelativeStackPosition mPositionFromRemovedStack;
-
// Tracks the id of the current (foreground) user.
private int mCurrentUserId;
// Saves notification keys of active bubbles when users are switched.
@@ -176,12 +170,12 @@
Handler mainHandler,
ShellTaskOrganizer organizer) {
BubbleLogger logger = new BubbleLogger(uiEventLogger);
- return new BubbleController(context,
- new BubbleData(context, logger), synchronizer,
- floatingContentCoordinator, new BubbleDataRepository(context, launcherApps),
- statusBarService, windowManager,
- windowManagerShellWrapper, launcherApps, logger, mainHandler, organizer,
- new BubblePositioner(context, windowManager));
+ BubblePositioner positioner = new BubblePositioner(context, windowManager);
+ BubbleData data = new BubbleData(context, logger, positioner);
+ return new BubbleController(context, data, synchronizer, floatingContentCoordinator,
+ new BubbleDataRepository(context, launcherApps),
+ statusBarService, windowManager, windowManagerShellWrapper, launcherApps,
+ logger, mainHandler, organizer, positioner);
}
/**
@@ -207,6 +201,7 @@
mLogger = bubbleLogger;
mMainHandler = mainHandler;
+ mBubblePositioner = positioner;
mBubbleData = data;
mBubbleData.setListener(mBubbleDataListener);
mBubbleData.setSuppressionChangedListener(bubble -> {
@@ -249,7 +244,6 @@
mBubbleIconFactory = new BubbleIconFactory(context);
mTaskOrganizer = organizer;
- mBubblePositioner = positioner;
launcherApps.registerCallback(new LauncherApps.Callback() {
@Override
@@ -388,8 +382,6 @@
if (mStackView == null) {
mStackView = new BubbleStackView(
mContext, this, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator);
- mStackView.setStackStartPosition(mPositionFromRemovedStack);
- mStackView.addView(mBubbleScrim);
mStackView.onOrientationChanged();
if (mExpandListener != null) {
mStackView.setExpandListener(mExpandListener);
@@ -429,7 +421,11 @@
try {
mAddedToWindowManager = true;
+ mBubbleData.getOverflow().initialize(this);
+ mStackView.addView(mBubbleScrim);
mWindowManager.addView(mStackView, mWmLayoutParams);
+ // Position info is dependent on us being attached to a window
+ mBubblePositioner.update(mOrientation);
} catch (IllegalStateException e) {
// This means the stack has already been added. This shouldn't happen...
e.printStackTrace();
@@ -449,10 +445,9 @@
try {
mAddedToWindowManager = false;
if (mStackView != null) {
- mPositionFromRemovedStack = mStackView.getRelativeStackPosition();
mWindowManager.removeView(mStackView);
mStackView.removeView(mBubbleScrim);
- mStackView = null;
+ mBubbleData.getOverflow().cleanUpExpandedState();
} else {
Log.w(TAG, "StackView added to WindowManager, but was null when removing!");
}
@@ -544,7 +539,7 @@
}
if (newConfig.fontScale != mFontScale) {
mFontScale = newConfig.fontScale;
- mStackView.updateFlyout(mFontScale);
+ mStackView.updateFontScale(mFontScale);
}
if (newConfig.getLayoutDirection() != mLayoutDirection) {
mLayoutDirection = newConfig.getLayoutDirection();
@@ -584,7 +579,7 @@
if (mStackView == null) {
return false;
}
- return mBubbleData.hasBubbles();
+ return mBubbleData.hasBubbles() || mBubbleData.isShowingOverflow();
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index b6a97e2..e24ff06 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -69,7 +69,7 @@
boolean selectionChanged;
boolean orderChanged;
boolean expanded;
- @Nullable Bubble selectedBubble;
+ @Nullable BubbleViewProvider selectedBubble;
@Nullable Bubble addedBubble;
@Nullable Bubble updatedBubble;
@Nullable Bubble addedOverflowBubble;
@@ -116,13 +116,15 @@
}
private final Context mContext;
+ private final BubblePositioner mPositioner;
/** Bubbles that are actively in the stack. */
private final List<Bubble> mBubbles;
/** Bubbles that aged out to overflow. */
private final List<Bubble> mOverflowBubbles;
/** Bubbles that are being loaded but haven't been added to the stack just yet. */
private final HashMap<String, Bubble> mPendingBubbles;
- private Bubble mSelectedBubble;
+ private BubbleViewProvider mSelectedBubble;
+ private final BubbleOverflow mOverflow;
private boolean mShowingOverflow;
private boolean mExpanded;
private final int mMaxBubbles;
@@ -153,9 +155,11 @@
*/
private HashMap<String, String> mSuppressedGroupKeys = new HashMap<>();
- public BubbleData(Context context, BubbleLogger bubbleLogger) {
+ public BubbleData(Context context, BubbleLogger bubbleLogger, BubblePositioner positioner) {
mContext = context;
mLogger = bubbleLogger;
+ mPositioner = positioner;
+ mOverflow = new BubbleOverflow(context, positioner);
mBubbles = new ArrayList<>();
mOverflowBubbles = new ArrayList<>();
mPendingBubbles = new HashMap<>();
@@ -178,6 +182,10 @@
return !mBubbles.isEmpty();
}
+ public boolean hasOverflowBubbles() {
+ return !mOverflowBubbles.isEmpty();
+ }
+
public boolean isExpanded() {
return mExpanded;
}
@@ -195,10 +203,14 @@
}
@Nullable
- public Bubble getSelectedBubble() {
+ public BubbleViewProvider getSelectedBubble() {
return mSelectedBubble;
}
+ public BubbleOverflow getOverflow() {
+ return mOverflow;
+ }
+
/** Return a read-only current active bubble lists. */
public List<Bubble> getActiveBubbles() {
return Collections.unmodifiableList(mBubbles);
@@ -212,7 +224,7 @@
dispatchPendingChanges();
}
- public void setSelectedBubble(Bubble bubble) {
+ public void setSelectedBubble(BubbleViewProvider bubble) {
if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "setSelectedBubble: " + bubble);
}
@@ -224,6 +236,10 @@
mShowingOverflow = showingOverflow;
}
+ boolean isShowingOverflow() {
+ return mShowingOverflow && (isExpanded() || mPositioner.showingInTaskbar());
+ }
+
/**
* Constructs a new bubble or returns an existing one. Does not add new bubbles to
* bubble data, must go through {@link #notificationEntryUpdated(Bubble, boolean, boolean)}
@@ -264,8 +280,8 @@
/**
* When this method is called it is expected that all info in the bubble has completed loading.
- * @see Bubble#inflate(BubbleViewInfoTask.Callback, Context,
- * BubbleStackView, BubbleIconFactory, boolean).
+ * @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, BubbleController, BubbleStackView,
+ * BubbleIconFactory, boolean)
*/
void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
if (DEBUG_BUBBLE_DATA) {
@@ -484,10 +500,17 @@
Bubble bubbleToRemove = mBubbles.get(indexToRemove);
bubbleToRemove.stopInflation();
if (mBubbles.size() == 1) {
- // Going to become empty, handle specially.
- setExpandedInternal(false);
- // Don't use setSelectedBubbleInternal because we don't want to trigger an applyUpdate
- mSelectedBubble = null;
+ if (hasOverflowBubbles() && (mPositioner.showingInTaskbar() || isExpanded())) {
+ // No more active bubbles but we have stuff in the overflow -- select that view
+ // if we're already expanded or always showing.
+ setShowingOverflow(true);
+ setSelectedBubbleInternal(mOverflow);
+ } else {
+ setExpandedInternal(false);
+ // Don't use setSelectedBubbleInternal because we don't want to trigger an
+ // applyUpdate
+ mSelectedBubble = null;
+ }
}
if (indexToRemove < mBubbles.size() - 1) {
// Removing anything but the last bubble means positions will change.
@@ -505,7 +528,7 @@
if (Objects.equals(mSelectedBubble, bubbleToRemove)) {
// Move selection to the new bubble at the same position.
int newIndex = Math.min(indexToRemove, mBubbles.size() - 1);
- Bubble newSelected = mBubbles.get(newIndex);
+ BubbleViewProvider newSelected = mBubbles.get(newIndex);
setSelectedBubbleInternal(newSelected);
}
maybeSendDeleteIntent(reason, bubbleToRemove);
@@ -564,7 +587,7 @@
*
* @param bubble the new selected bubble
*/
- private void setSelectedBubbleInternal(@Nullable Bubble bubble) {
+ private void setSelectedBubbleInternal(@Nullable BubbleViewProvider bubble) {
if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "setSelectedBubbleInternal: " + bubble);
}
@@ -572,14 +595,17 @@
return;
}
// Otherwise, if we are showing the overflow menu, return to the previously selected bubble.
-
- if (bubble != null && !mBubbles.contains(bubble) && !mOverflowBubbles.contains(bubble)) {
+ boolean isOverflow = bubble != null && BubbleOverflow.KEY.equals(bubble.getKey());
+ if (bubble != null
+ && !mBubbles.contains(bubble)
+ && !mOverflowBubbles.contains(bubble)
+ && !isOverflow) {
Log.e(TAG, "Cannot select bubble which doesn't exist!"
+ " (" + bubble + ") bubbles=" + mBubbles);
return;
}
- if (mExpanded && bubble != null) {
- bubble.markAsAccessedAt(mTimeSource.currentTimeMillis());
+ if (mExpanded && bubble != null && !isOverflow) {
+ ((Bubble) bubble).markAsAccessedAt(mTimeSource.currentTimeMillis());
}
mSelectedBubble = bubble;
mStateChange.selectedBubble = bubble;
@@ -629,7 +655,7 @@
return;
}
if (shouldExpand) {
- if (mBubbles.isEmpty()) {
+ if (mBubbles.isEmpty() && !mShowingOverflow) {
Log.e(TAG, "Attempt to expand stack when empty!");
return;
}
@@ -637,7 +663,9 @@
Log.e(TAG, "Attempt to expand stack without selected bubble!");
return;
}
- mSelectedBubble.markAsAccessedAt(mTimeSource.currentTimeMillis());
+ if (mSelectedBubble instanceof Bubble) {
+ ((Bubble) mSelectedBubble).markAsAccessedAt(mTimeSource.currentTimeMillis());
+ }
mStateChange.orderChanged |= repackAll();
} else if (!mBubbles.isEmpty()) {
// Apply ordering and grouping rules from expanded -> collapsed, then save
@@ -647,14 +675,18 @@
if (mShowingOverflow) {
// Show previously selected bubble instead of overflow menu on next expansion.
- setSelectedBubbleInternal(mSelectedBubble);
+ if (!mSelectedBubble.getKey().equals(mOverflow.getKey())) {
+ setSelectedBubbleInternal(mSelectedBubble);
+ } else {
+ setSelectedBubbleInternal(mBubbles.get(0));
+ }
}
if (mBubbles.indexOf(mSelectedBubble) > 0) {
// Move the selected bubble to the top while collapsed.
int index = mBubbles.indexOf(mSelectedBubble);
if (index != 0) {
- mBubbles.remove(mSelectedBubble);
- mBubbles.add(0, mSelectedBubble);
+ mBubbles.remove((Bubble) mSelectedBubble);
+ mBubbles.add(0, (Bubble) mSelectedBubble);
mStateChange.orderChanged = true;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
index 53f4e87..dc2ace9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
@@ -45,6 +45,7 @@
static final boolean DEBUG_EXPERIMENTS = true;
static final boolean DEBUG_OVERFLOW = false;
static final boolean DEBUG_USER_EDUCATION = false;
+ static final boolean DEBUG_POSITIONER = false;
private static final boolean FORCE_SHOW_USER_EDUCATION = false;
private static final String FORCE_SHOW_USER_EDUCATION_SETTING =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 74521c7..646e75a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -639,7 +639,9 @@
}
/**
- * Cleans up anything related to the task and TaskView.
+ * Cleans up anything related to the task and TaskView. If this view should be reused after this
+ * method is called, then {@link #initialize(BubbleController, BubbleStackView)} must be invoked
+ * first.
*/
public void cleanUpExpandedState() {
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
index 460e0e7..19c3cf9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
@@ -68,9 +68,8 @@
private final int mFlyoutPadding;
private final int mFlyoutSpaceFromBubble;
private final int mPointerSize;
- private final int mBubbleSize;
- private final int mBubbleBitmapSize;
- private final float mBubbleIconTopPadding;
+ private int mBubbleSize;
+ private int mBubbleBitmapSize;
private final int mFlyoutElevation;
private final int mBubbleElevation;
@@ -83,9 +82,9 @@
private final TextView mMessageText;
/** Values related to the 'new' dot which we use to figure out where to collapse the flyout. */
- private final float mNewDotRadius;
- private final float mNewDotSize;
- private final float mOriginalDotSize;
+ private float mNewDotRadius;
+ private float mNewDotSize;
+ private float mOriginalDotSize;
/**
* The paint used to draw the background, whose color changes as the flyout transitions to the
@@ -169,17 +168,9 @@
mFlyoutSpaceFromBubble = res.getDimensionPixelSize(R.dimen.bubble_flyout_space_from_bubble);
mPointerSize = res.getDimensionPixelSize(R.dimen.bubble_flyout_pointer_size);
- mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
- mBubbleBitmapSize = res.getDimensionPixelSize(R.dimen.bubble_bitmap_size);
- mBubbleIconTopPadding = (mBubbleSize - mBubbleBitmapSize) / 2f;
-
mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
mFlyoutElevation = res.getDimensionPixelSize(R.dimen.bubble_flyout_elevation);
- mOriginalDotSize = SIZE_PERCENTAGE * mBubbleBitmapSize;
- mNewDotRadius = (DOT_SCALE * mOriginalDotSize) / 2f;
- mNewDotSize = mNewDotRadius * 2f;
-
final TypedArray ta = mContext.obtainStyledAttributes(
new int[] {
android.R.attr.colorBackgroundFloating,
@@ -306,7 +297,15 @@
@Nullable Runnable onLayoutComplete,
@Nullable Runnable onHide,
float[] dotCenter,
- boolean hideDot) {
+ boolean hideDot,
+ BubblePositioner positioner) {
+
+ mBubbleBitmapSize = positioner.getBubbleBitmapSize();
+ mBubbleSize = positioner.getBubbleSize();
+
+ mOriginalDotSize = SIZE_PERCENTAGE * mBubbleBitmapSize;
+ mNewDotRadius = (DOT_SCALE * mOriginalDotSize) / 2f;
+ mNewDotSize = mNewDotRadius * 2f;
updateFlyoutMessage(flyoutMessage, parentWidth);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index 686d2d4..8ab2f63 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -29,14 +29,18 @@
import android.util.PathParser
import android.util.TypedValue
import android.view.LayoutInflater
-import android.view.View
import android.widget.FrameLayout
import com.android.wm.shell.R
+/**
+ * The icon in the bubble overflow is scaled down, this is the percent of the normal bubble bitmap
+ * size to use.
+ */
+const val ICON_BITMAP_SIZE_PERCENT = 0.46f
+
class BubbleOverflow(
private val context: Context,
- private val controller: BubbleController,
- private val stack: BubbleStackView
+ private val positioner: BubblePositioner
) : BubbleViewProvider {
private lateinit var bitmap: Bitmap
@@ -48,41 +52,42 @@
private var showDot = false
private val inflater: LayoutInflater = LayoutInflater.from(context)
- private val expandedView: BubbleExpandedView = inflater
- .inflate(R.layout.bubble_expanded_view, null /* root */, false /* attachToRoot */)
- as BubbleExpandedView
- private val overflowBtn: BadgedImageView = inflater
- .inflate(R.layout.bubble_overflow_button, null /* root */, false /* attachToRoot */)
- as BadgedImageView
+ private var expandedView: BubbleExpandedView?
+ private var overflowBtn: BadgedImageView?
+
init {
updateResources()
- with(expandedView) {
- initialize(controller, stack)
- setOverflow(true)
- applyThemeAttrs()
- }
- with(overflowBtn) {
- setContentDescription(context.resources.getString(
- R.string.bubble_overflow_button_content_description))
- updateBtnTheme()
- }
+ bitmapSize = positioner.bubbleBitmapSize
+ iconBitmapSize = (bitmapSize * ICON_BITMAP_SIZE_PERCENT).toInt()
+ expandedView = null
+ overflowBtn = null
+ }
+
+ /** Call before use and again if cleanUpExpandedState was called. */
+ fun initialize(controller: BubbleController) {
+ getExpandedView()?.initialize(controller, controller.stackView)
+ getExpandedView()?.setOverflow(true)
+ }
+
+ fun cleanUpExpandedState() {
+ expandedView?.cleanUpExpandedState()
+ expandedView = null
}
fun update() {
updateResources()
- expandedView.applyThemeAttrs()
+ getExpandedView()?.applyThemeAttrs()
// Apply inset and new style to fresh icon drawable.
- overflowBtn.setImageResource(R.drawable.bubble_ic_overflow_button)
+ getIconView()?.setImageResource(R.drawable.bubble_ic_overflow_button)
updateBtnTheme()
}
fun updateResources() {
- bitmapSize = context.resources.getDimensionPixelSize(R.dimen.bubble_bitmap_size)
- iconBitmapSize = context.resources.getDimensionPixelSize(
- R.dimen.bubble_overflow_icon_bitmap_size)
- val bubbleSize = context.resources.getDimensionPixelSize(R.dimen.individual_bubble_size)
- overflowBtn.setLayoutParams(FrameLayout.LayoutParams(bubbleSize, bubbleSize))
- expandedView.updateDimensions()
+ bitmapSize = positioner.bubbleBitmapSize
+ iconBitmapSize = (bitmapSize * 0.46f).toInt()
+ val bubbleSize = positioner.bubbleSize
+ overflowBtn?.setLayoutParams(FrameLayout.LayoutParams(bubbleSize, bubbleSize))
+ expandedView?.updateDimensions()
}
private fun updateBtnTheme() {
@@ -92,7 +97,7 @@
val typedValue = TypedValue()
context.theme.resolveAttribute(android.R.attr.colorAccent, typedValue, true)
val colorAccent = res.getColor(typedValue.resourceId)
- overflowBtn.drawable?.setTint(colorAccent)
+ overflowBtn?.drawable?.setTint(colorAccent)
dotColor = colorAccent
val iconFactory = BubbleIconFactory(context)
@@ -103,7 +108,7 @@
val bg = ColorDrawable(res.getColor(
if (nightMode) R.color.bubbles_dark else R.color.bubbles_light))
- val fg = InsetDrawable(overflowBtn.drawable,
+ val fg = InsetDrawable(overflowBtn?.drawable,
bitmapSize - iconBitmapSize /* inset */)
bitmap = iconFactory.createBadgedIconBitmap(AdaptiveIconDrawable(bg, fg),
null /* user */, true /* shrinkNonAdaptiveIcons */).icon
@@ -111,7 +116,7 @@
// Update dot path
dotPath = PathParser.createPathFromPathData(
res.getString(com.android.internal.R.string.config_icon_mask))
- val scale = iconFactory.normalizer.getScale(overflowBtn.getDrawable(),
+ val scale = iconFactory.normalizer.getScale(getIconView()!!.getDrawable(),
null /* outBounds */, null /* path */, null /* outMaskShape */)
val radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f
val matrix = Matrix()
@@ -120,20 +125,26 @@
dotPath.transform(matrix)
// Attach BubbleOverflow to BadgedImageView
- overflowBtn.setRenderedBubble(this)
- overflowBtn.removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE)
+ overflowBtn?.setRenderedBubble(this)
+ overflowBtn?.removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE)
}
fun setVisible(visible: Int) {
- overflowBtn.visibility = visible
+ overflowBtn?.visibility = visible
}
fun setShowDot(show: Boolean) {
showDot = show
- overflowBtn.updateDotVisibility(true /* animate */)
+ overflowBtn?.updateDotVisibility(true /* animate */)
}
override fun getExpandedView(): BubbleExpandedView? {
+ if (expandedView == null) {
+ expandedView = inflater.inflate(R.layout.bubble_expanded_view,
+ null /* root */, false /* attachToRoot */) as BubbleExpandedView
+ expandedView?.applyThemeAttrs()
+ updateResources()
+ }
return expandedView
}
@@ -158,10 +169,20 @@
}
override fun setContentVisibility(visible: Boolean) {
- expandedView.setContentVisibility(visible)
+ expandedView?.setContentVisibility(visible)
}
- override fun getIconView(): View? {
+ override fun getIconView(): BadgedImageView? {
+ if (overflowBtn == null) {
+ overflowBtn = inflater.inflate(R.layout.bubble_overflow_button,
+ null /* root */, false /* attachToRoot */) as BadgedImageView
+ overflowBtn?.initialize(positioner)
+ overflowBtn?.setContentDescription(context.resources.getString(
+ R.string.bubble_overflow_button_content_description))
+ val bubbleSize = positioner.bubbleSize
+ overflowBtn?.setLayoutParams(FrameLayout.LayoutParams(bubbleSize, bubbleSize))
+ updateBtnTheme()
+ }
return overflowBtn
}
@@ -170,7 +191,7 @@
}
override fun getTaskId(): Int {
- return if (expandedView != null) expandedView.getTaskId() else INVALID_TASK_ID
+ return if (expandedView != null) expandedView!!.getTaskId() else INVALID_TASK_ID
}
companion object {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowActivity.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowActivity.java
index 2759b59..cfd0066 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowActivity.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowActivity.java
@@ -111,11 +111,11 @@
IBinder binder = intent.getExtras().getBinder(EXTRA_BUBBLE_CONTROLLER);
if (binder instanceof ObjectWrapper) {
mController = ((ObjectWrapper<BubbleController>) binder).get();
+ updateOverflow();
}
} else {
Log.w(TAG, "Bubble overflow activity created without bubble controller!");
}
- updateOverflow();
}
void updateOverflow() {
@@ -138,7 +138,9 @@
final int viewHeight = recyclerViewHeight / rows;
mAdapter = new BubbleOverflowAdapter(getApplicationContext(), mOverflowBubbles,
- mController::promoteBubbleFromOverflow, viewWidth, viewHeight);
+ mController::promoteBubbleFromOverflow,
+ mController.getPositioner(),
+ viewWidth, viewHeight);
mRecyclerView.setAdapter(mAdapter);
mOverflowBubbles.clear();
@@ -257,15 +259,20 @@
private Context mContext;
private Consumer<Bubble> mPromoteBubbleFromOverflow;
+ private BubblePositioner mPositioner;
private List<Bubble> mBubbles;
private int mWidth;
private int mHeight;
- public BubbleOverflowAdapter(Context context, List<Bubble> list, Consumer<Bubble> promoteBubble,
+ BubbleOverflowAdapter(Context context,
+ List<Bubble> list,
+ Consumer<Bubble> promoteBubble,
+ BubblePositioner positioner,
int width, int height) {
mContext = context;
mBubbles = list;
mPromoteBubbleFromOverflow = promoteBubble;
+ mPositioner = positioner;
mWidth = width;
mHeight = height;
}
@@ -295,7 +302,7 @@
TextView viewName = overflowView.findViewById(R.id.bubble_view_name);
viewName.setTextColor(textColor);
- return new ViewHolder(overflowView);
+ return new ViewHolder(overflowView, mPositioner);
}
@Override
@@ -348,9 +355,10 @@
public BadgedImageView iconView;
public TextView textView;
- public ViewHolder(LinearLayout v) {
+ ViewHolder(LinearLayout v, BubblePositioner positioner) {
super(v);
iconView = v.findViewById(R.id.bubble_view);
+ iconView.initialize(positioner);
textView = v.findViewById(R.id.bubble_view_name);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index eccd009..46e8e11 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -16,45 +16,99 @@
package com.android.wm.shell.bubbles;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
import android.content.Context;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Insets;
+import android.graphics.PointF;
import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.Log;
+import android.view.View;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
import androidx.annotation.VisibleForTesting;
+import com.android.wm.shell.R;
+
+import java.lang.annotation.Retention;
+
/**
* Keeps track of display size, configuration, and specific bubble sizes. One place for all
* placement and positioning calculations to refer to.
*/
public class BubblePositioner {
+ private static final String TAG = BubbleDebugConfig.TAG_WITH_CLASS_NAME
+ ? "BubblePositioner"
+ : BubbleDebugConfig.TAG_BUBBLES;
+ @Retention(SOURCE)
+ @IntDef({TASKBAR_POSITION_RIGHT, TASKBAR_POSITION_LEFT, TASKBAR_POSITION_BOTTOM})
+ @interface TaskbarPosition {}
+ public static final int TASKBAR_POSITION_RIGHT = 0;
+ public static final int TASKBAR_POSITION_LEFT = 1;
+ public static final int TASKBAR_POSITION_BOTTOM = 2;
+
+ /**
+ * The bitmap in the bubble is slightly smaller than the overall size of the bubble.
+ * This is the percentage to scale the image down based on the overall bubble size.
+ */
+ private static final float BUBBLE_BITMAP_SIZE_PERCENT = 0.86f;
+
+ private Context mContext;
private WindowManager mWindowManager;
private Rect mPositionRect;
private int mOrientation;
private Insets mInsets;
+ private int mBubbleSize;
+ private int mBubbleBitmapSize;
+
+ private PointF mPinLocation;
+ private PointF mRestingStackPosition;
+
+ private boolean mShowingInTaskbar;
+ private @TaskbarPosition int mTaskbarPosition;
+ private int mTaskbarIconSize;
+
public BubblePositioner(Context context, WindowManager windowManager) {
+ mContext = context;
mWindowManager = windowManager;
update(Configuration.ORIENTATION_UNDEFINED);
}
+ /**
+ * Updates orientation, available space, and inset information. Call this when config changes
+ * occur or when added to a window.
+ */
public void update(int orientation) {
WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
- mPositionRect = new Rect(windowMetrics.getBounds());
+ if (windowMetrics == null) {
+ return;
+ }
WindowInsets metricInsets = windowMetrics.getWindowInsets();
Insets insets = metricInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars()
| WindowInsets.Type.statusBars()
| WindowInsets.Type.displayCutout());
- update(orientation, insets, windowMetrics.getBounds());
+
+ if (BubbleDebugConfig.DEBUG_POSITIONER) {
+ Log.w(TAG, "update positioner:"
+ + " landscape= " + (orientation == Configuration.ORIENTATION_LANDSCAPE)
+ + " insets: " + insets
+ + " bounds: " + windowMetrics.getBounds()
+ + " showingInTaskbar: " + mShowingInTaskbar);
+ }
+ updateInternal(orientation, insets, windowMetrics.getBounds());
}
@VisibleForTesting
- public void update(int orientation, Insets insets, Rect bounds) {
+ public void updateInternal(int orientation, Insets insets, Rect bounds) {
mOrientation = orientation;
mInsets = insets;
@@ -63,27 +117,156 @@
mPositionRect.top += mInsets.top;
mPositionRect.right -= mInsets.right;
mPositionRect.bottom -= mInsets.bottom;
+
+ Resources res = mContext.getResources();
+ mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
+ mBubbleBitmapSize = res.getDimensionPixelSize(R.dimen.bubble_bitmap_size);
+ adjustForTaskbar();
}
/**
- * @return a rect of available screen space for displaying bubbles in the correct orientation,
- * accounting for system bars and cutouts.
+ * Updates position information to account for taskbar state.
+ *
+ * @param taskbarPosition which position the taskbar is displayed in.
+ * @param showingInTaskbar whether the taskbar is being shown.
+ */
+ public void updateForTaskbar(int iconSize,
+ @TaskbarPosition int taskbarPosition, boolean showingInTaskbar) {
+ mShowingInTaskbar = showingInTaskbar;
+ mTaskbarIconSize = iconSize;
+ mTaskbarPosition = taskbarPosition;
+ update(mOrientation);
+ }
+
+ /**
+ * Taskbar insets appear as navigationBar insets, however, unlike navigationBar this should
+ * not inset bubbles UI as bubbles floats above the taskbar. This adjust the available space
+ * and insets to account for the taskbar.
+ */
+ // TODO(b/171559950): When the insets are reported correctly we can remove this logic
+ private void adjustForTaskbar() {
+ // When bar is showing on edges... subtract that inset because we appear on top
+ if (mShowingInTaskbar && mTaskbarPosition != TASKBAR_POSITION_BOTTOM) {
+ WindowInsets metricInsets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+ Insets navBarInsets = metricInsets.getInsetsIgnoringVisibility(
+ WindowInsets.Type.navigationBars());
+ int newInsetLeft = mInsets.left;
+ int newInsetRight = mInsets.right;
+ if (mTaskbarPosition == TASKBAR_POSITION_LEFT) {
+ mPositionRect.left -= navBarInsets.left;
+ newInsetLeft -= navBarInsets.left;
+ } else if (mTaskbarPosition == TASKBAR_POSITION_RIGHT) {
+ mPositionRect.right += navBarInsets.right;
+ newInsetRight -= navBarInsets.right;
+ }
+ mInsets = Insets.of(newInsetLeft, mInsets.top, newInsetRight, mInsets.bottom);
+ }
+ }
+
+ /**
+ * @return a rect of available screen space accounting for orientation, system bars and cutouts.
+ * Does not account for IME.
*/
public Rect getAvailableRect() {
return mPositionRect;
}
/**
- * @return the current orientation.
- */
- public int getOrientation() {
- return mOrientation;
- }
-
- /**
- * @return the relevant insets (status bar, nav bar, cutouts).
+ * @return the relevant insets (status bar, nav bar, cutouts). If taskbar is showing, its
+ * inset is not included here.
*/
public Insets getInsets() {
return mInsets;
}
+
+ /**
+ * @return whether the device is in landscape orientation.
+ */
+ public boolean isLandscape() {
+ return mOrientation == Configuration.ORIENTATION_LANDSCAPE;
+ }
+
+ /**
+ * Indicates how bubbles appear when expanded.
+ *
+ * When false, bubbles display at the top of the screen with the expanded view
+ * below them. When true, bubbles display at the edges of the screen with the expanded view
+ * to the left or right side.
+ */
+ public boolean showBubblesVertically() {
+ return mOrientation == Configuration.ORIENTATION_LANDSCAPE
+ || mShowingInTaskbar;
+ }
+
+ /** Size of the bubble account for badge & dot. */
+ public int getBubbleSize() {
+ int bsize = (mShowingInTaskbar && mTaskbarIconSize > 0)
+ ? mTaskbarIconSize
+ : mBubbleSize;
+ return bsize;
+ }
+
+ /** Size of the bitmap within the bubble */
+ public int getBubbleBitmapSize() {
+ float size = (mShowingInTaskbar && mTaskbarIconSize > 0)
+ ? (mTaskbarIconSize * BUBBLE_BITMAP_SIZE_PERCENT)
+ : mBubbleBitmapSize;
+ return (int) size;
+ }
+
+ /**
+ * Sets the stack's most recent position along the edge of the screen. This is saved when the
+ * last bubble is removed, so that the stack can be restored in its previous position.
+ */
+ public void setRestingPosition(PointF position) {
+ if (mRestingStackPosition == null) {
+ mRestingStackPosition = new PointF(position);
+ } else {
+ mRestingStackPosition.set(position);
+ }
+ }
+
+ /** The position the bubble stack should rest at when collapsed. */
+ public PointF getRestingPosition() {
+ if (mPinLocation != null) {
+ return mPinLocation;
+ }
+ if (mRestingStackPosition == null) {
+ return getDefaultStartPosition();
+ }
+ return mRestingStackPosition;
+ }
+
+ /**
+ * @return the stack position to use if we don't have a saved location or if user education
+ * is being shown.
+ */
+ public PointF getDefaultStartPosition() {
+ // Start on the left if we're in LTR, right otherwise.
+ final boolean startOnLeft =
+ mContext.getResources().getConfiguration().getLayoutDirection()
+ != View.LAYOUT_DIRECTION_RTL;
+ final float startingVerticalOffset = mContext.getResources().getDimensionPixelOffset(
+ R.dimen.bubble_stack_starting_offset_y);
+ // TODO: placement bug here because mPositionRect doesn't handle the overhanging edge
+ return new BubbleStackView.RelativeStackPosition(
+ startOnLeft,
+ startingVerticalOffset / mPositionRect.height())
+ .getAbsolutePositionInRegion(new RectF(mPositionRect));
+ }
+
+ /**
+ * @return whether the bubble stack is pinned to the taskbar.
+ */
+ public boolean showingInTaskbar() {
+ return mShowingInTaskbar;
+ }
+
+ /**
+ * In some situations bubbles will be pinned to a specific onscreen location. This sets the
+ * location to anchor the stack to.
+ */
+ public void setPinnedLocation(PointF point) {
+ mPinLocation = point;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 155f342..cbe9845 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -30,7 +30,6 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.ColorMatrix;
@@ -228,7 +227,7 @@
* once it collapses.
*/
@Nullable
- private Bubble mBubbleToExpandAfterFlyoutCollapse = null;
+ private BubbleViewProvider mBubbleToExpandAfterFlyoutCollapse = null;
/** Layout change listener that moves the stack to the nearest valid position on rotation. */
private OnLayoutChangeListener mOrientationChangedListener;
@@ -572,6 +571,16 @@
mBubbleContainer.setActiveController(mStackAnimationController);
hideFlyoutImmediate();
+ if (!mPositioner.showingInTaskbar()) {
+ // Also, save the magnetized stack so we can dispatch touch events to it.
+ mMagnetizedObject = mStackAnimationController.getMagnetizedStack(
+ mMagneticTarget);
+ mMagnetizedObject.setMagnetListener(mStackMagnetListener);
+ } else {
+ // In taskbar, the stack isn't draggable so we shouldn't dispatch touch events.
+ mMagnetizedObject = null;
+ }
+
// Also, save the magnetized stack so we can dispatch touch events to it.
mMagnetizedObject = mStackAnimationController.getMagnetizedStack(mMagneticTarget);
mMagnetizedObject.setMagnetListener(mStackMagnetListener);
@@ -593,7 +602,9 @@
public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
float viewInitialY, float dx, float dy) {
// If we're expanding or collapsing, ignore all touch events.
- if (mIsExpansionAnimating) {
+ if (mIsExpansionAnimating
+ // Also ignore events if we shouldn't be draggable.
+ || (mPositioner.showingInTaskbar() && !mIsExpanded)) {
return;
}
@@ -603,7 +614,7 @@
// First, see if the magnetized object consumes the event - if so, we shouldn't move the
// bubble since it's stuck to the target.
if (!passEventToMagnetizedObject(ev)) {
- if (mBubbleData.isExpanded()) {
+ if (mBubbleData.isExpanded() || mPositioner.showingInTaskbar()) {
mExpandedAnimationController.dragBubbleOut(
v, viewInitialX + dx, viewInitialY + dy);
} else {
@@ -701,7 +712,6 @@
}
};
- @Nullable
private BubbleOverflow mBubbleOverflow;
private StackEducationView mStackEduView;
private ManageEducationView mManageEduView;
@@ -745,7 +755,7 @@
ta.recycle();
final Runnable onBubbleAnimatedOut = () -> {
- if (getBubbleCount() == 0) {
+ if (getBubbleCount() == 0 && !mBubbleData.isShowingOverflow()) {
mBubbleController.onAllBubblesAnimatedOut();
}
};
@@ -818,15 +828,16 @@
setFocusable(true);
mBubbleContainer.bringToFront();
- mBubbleOverflow = new BubbleOverflow(getContext(), bubbleController, this);
+ mBubbleOverflow = mBubbleData.getOverflow();
mBubbleContainer.addView(mBubbleOverflow.getIconView(),
mBubbleContainer.getChildCount() /* index */,
- new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT));
+ new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
+ mPositioner.getBubbleSize()));
updateOverflow();
mBubbleOverflow.getIconView().setOnClickListener((View v) -> {
- setSelectedBubble(mBubbleOverflow);
- showManageMenu(false);
+ mBubbleData.setShowingOverflow(true);
+ mBubbleData.setSelectedBubble(mBubbleOverflow);
+ mBubbleData.setExpanded(true);
});
setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
@@ -997,11 +1008,12 @@
mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container).setOnClickListener(
view -> {
showManageMenu(false /* show */);
- final Bubble bubble = mBubbleData.getSelectedBubble();
+ final BubbleViewProvider bubble = mBubbleData.getSelectedBubble();
if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
- final Intent intent = bubble.getSettingsIntent(mContext);
+ // If it's in the stack it's a proper Bubble.
+ final Intent intent = ((Bubble) bubble).getSettingsIntent(mContext);
mBubbleData.setExpanded(false);
- mContext.startActivityAsUser(intent, bubble.getUser());
+ mContext.startActivityAsUser(intent, ((Bubble) bubble).getUser());
logBubbleEvent(bubble,
FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
}
@@ -1067,7 +1079,7 @@
mStackEduView = new StackEducationView(mContext);
addView(mStackEduView);
}
- return mStackEduView.show(mStackAnimationController.getStartPosition());
+ return mStackEduView.show(mPositioner.getDefaultStartPosition());
}
private void updateUserEdu() {
@@ -1093,7 +1105,7 @@
addView(mFlyout, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
}
- void updateFlyout(float fontScale) {
+ void updateFontScale(float fontScale) {
mFlyout.updateFontSize(fontScale);
}
@@ -1130,7 +1142,9 @@
Resources res = getContext().getResources();
mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
- mRelativeStackPositionBeforeRotation = mStackAnimationController.getRelativeStackPosition();
+ mRelativeStackPositionBeforeRotation = new RelativeStackPosition(
+ mPositioner.getRestingPosition(),
+ mStackAnimationController.getAllowableStackPositionRegion());
mManageMenu.setVisibility(View.INVISIBLE);
mShowingManage = false;
@@ -1157,7 +1171,7 @@
Resources res = getContext().getResources();
mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
- mBubbleSize = getResources().getDimensionPixelSize(R.dimen.individual_bubble_size);
+ mBubbleSize = mPositioner.getBubbleSize();
for (Bubble b : mBubbleData.getBubbles()) {
if (b.getIconView() == null) {
Log.d(TAG, "Display size changed. Icon null: " + b);
@@ -1165,6 +1179,7 @@
}
b.getIconView().setLayoutParams(new LayoutParams(mBubbleSize, mBubbleSize));
}
+ mBubbleOverflow.getIconView().setLayoutParams(new LayoutParams(mBubbleSize, mBubbleSize));
mExpandedAnimationController.updateResources();
mStackAnimationController.updateResources();
mDismissView.updateResources();
@@ -1199,8 +1214,8 @@
super.onDetachedFromWindow();
getViewTreeObserver().removeOnPreDrawListener(mViewUpdater);
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
- if (mBubbleOverflow != null && mBubbleOverflow.getExpandedView() != null) {
- mBubbleOverflow.getExpandedView().cleanUpExpandedState();
+ if (mBubbleOverflow != null) {
+ mBubbleOverflow.cleanUpExpandedState();
}
}
@@ -1390,8 +1405,7 @@
if (getBubbleCount() == 0 && shouldShowStackEdu()) {
// Override the default stack position if we're showing user education.
- mStackAnimationController.setStackPosition(
- mStackAnimationController.getStartPosition());
+ mStackAnimationController.setStackPosition(mPositioner.getDefaultStartPosition());
}
if (getBubbleCount() == 0) {
@@ -1410,7 +1424,8 @@
bubble.getIconView().setOnTouchListener(mBubbleTouchListener);
mBubbleContainer.addView(bubble.getIconView(), 0,
- new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+ new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
+ mPositioner.getBubbleSize()));
animateInFlyoutForBubble(bubble);
requestUpdate();
logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
@@ -1441,10 +1456,9 @@
}
private void updateOverflowVisibility() {
- if (mBubbleOverflow == null) {
- return;
- }
- mBubbleOverflow.setVisible(mIsExpanded ? VISIBLE : GONE);
+ mBubbleOverflow.setVisible((mIsExpanded || mBubbleData.isShowingOverflow())
+ ? VISIBLE
+ : GONE);
}
// via BubbleData.Listener
@@ -1614,6 +1628,11 @@
mBubbleController.hideCurrentInputMethod();
}
+ /** Set the stack position to whatever the positioner says. */
+ void updateStackPosition() {
+ mStackAnimationController.setStackPosition(mPositioner.getRestingPosition());
+ }
+
private void beforeExpandedViewAnimation() {
mIsExpansionAnimating = true;
hideFlyoutImmediate();
@@ -1629,8 +1648,7 @@
private void animateExpansion() {
cancelDelayedExpandCollapseSwitchAnimations();
- final boolean isLandscape =
- mPositioner.getOrientation() == Configuration.ORIENTATION_LANDSCAPE;
+ final boolean showVertically = mPositioner.showBubblesVertically();
mIsExpanded = true;
if (mStackEduView != null) {
mStackEduView.hide(true /* fromExpansion */);
@@ -1650,14 +1668,19 @@
mExpandedViewContainer.setTranslationY(getExpandedViewY());
mExpandedViewContainer.setAlpha(1f);
- // X-value of the bubble we're expanding, once it's settled in its row.
+ int index;
+ if (mExpandedBubble != null && BubbleOverflow.KEY.equals(mExpandedBubble.getKey())) {
+ index = mBubbleData.getBubbles().size();
+ } else {
+ index = getBubbleIndex(mExpandedBubble);
+ }
+ // Position of the bubble we're expanding, once it's settled in its row.
final float bubbleWillBeAt =
- mExpandedAnimationController.getBubbleXOrYForOrientation(
- mBubbleData.getBubbles().indexOf(mExpandedBubble));
+ mExpandedAnimationController.getBubbleXOrYForOrientation(index);
// How far horizontally the bubble will be animating. We'll wait a bit longer for bubbles
// that are animating farther, so that the expanded view doesn't move as much.
- final float relevantStackPosition = isLandscape
+ final float relevantStackPosition = showVertically
? mStackAnimationController.getStackPosition().y
: mStackAnimationController.getStackPosition().x;
final float distanceAnimated = Math.abs(bubbleWillBeAt - relevantStackPosition);
@@ -1674,7 +1697,7 @@
}
// Set the pivot point for the scale, so the expanded view animates out from the bubble.
- if (isLandscape) {
+ if (showVertically) {
float pivotX;
float pivotY = bubbleWillBeAt + mBubbleSize / 2f;
if (mStackOnLeftOrWillBe) {
@@ -1709,7 +1732,7 @@
if (mExpandedBubble == null || mExpandedBubble.getIconView() == null) {
return;
}
- float translation = isLandscape
+ float translation = showVertically
? mExpandedBubble.getIconView().getTranslationY()
: mExpandedBubble.getIconView().getTranslationX();
mExpandedViewContainerMatrix.postTranslate(
@@ -1761,13 +1784,17 @@
// We want to visually collapse into this bubble during the animation.
final View expandingFromBubble = mExpandedBubble.getIconView();
+ int index;
+ if (mExpandedBubble != null && BubbleOverflow.KEY.equals(mExpandedBubble.getKey())) {
+ index = mBubbleData.getBubbles().size();
+ } else {
+ index = mBubbleData.getBubbles().indexOf(mExpandedBubble);
+ }
// Value the bubble is animating from (back into the stack).
final float expandingFromBubbleAt =
- mExpandedAnimationController.getBubbleXOrYForOrientation(
- mBubbleData.getBubbles().indexOf(mExpandedBubble));
- final boolean isLandscape =
- mPositioner.getOrientation() == Configuration.ORIENTATION_LANDSCAPE;
- if (isLandscape) {
+ mExpandedAnimationController.getBubbleXOrYForOrientation(index);
+ final boolean showVertically = mPositioner.showBubblesVertically();
+ if (mPositioner.showBubblesVertically()) {
float pivotX;
float pivotY = expandingFromBubbleAt + mBubbleSize / 2f;
if (mStackOnLeftOrWillBe) {
@@ -1792,7 +1819,7 @@
.addUpdateListener((target, values) -> {
if (expandingFromBubble != null) {
// Follow the bubble as it translates!
- if (isLandscape) {
+ if (showVertically) {
mExpandedViewContainerMatrix.postTranslate(
0f, expandingFromBubble.getTranslationY()
- expandingFromBubbleAt);
@@ -1849,7 +1876,7 @@
.spring(DynamicAnimation.SCALE_Y, 0f, mScaleOutSpringConfig)
.withEndActions(this::releaseAnimatingOutBubbleBuffer);
- if (mPositioner.getOrientation() == Configuration.ORIENTATION_LANDSCAPE) {
+ if (mPositioner.showBubblesVertically()) {
float translationX = mStackAnimationController.isStackOnLeftSide()
? mAnimatingOutSurfaceContainer.getTranslationX() + mBubbleSize * 2
: mAnimatingOutSurfaceContainer.getTranslationX();
@@ -1874,7 +1901,7 @@
mExpandedViewContainer.setAlpha(1f);
mExpandedViewContainer.setVisibility(View.VISIBLE);
- if (mPositioner.getOrientation() == Configuration.ORIENTATION_LANDSCAPE) {
+ if (mPositioner.showBubblesVertically()) {
float pivotX;
float pivotY = expandingFromBubbleDestination + mBubbleSize / 2f;
if (mStackOnLeftOrWillBe) {
@@ -2113,7 +2140,7 @@
}
}
- private void dismissBubbleIfExists(@Nullable Bubble bubble) {
+ private void dismissBubbleIfExists(@Nullable BubbleViewProvider bubble) {
if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
mBubbleData.dismissBubbleWithKey(bubble.getKey(), Bubbles.DISMISS_USER_GESTURE);
}
@@ -2180,7 +2207,7 @@
*/
float getExpandedViewY() {
final int top = mPositioner.getAvailableRect().top;
- if (mPositioner.getOrientation() == Configuration.ORIENTATION_LANDSCAPE) {
+ if (mPositioner.showBubblesVertically()) {
return top + mExpandedViewPadding;
} else {
return top + mBubbleSize + mBubblePaddingTop;
@@ -2278,7 +2305,8 @@
expandFlyoutAfterDelay /* onLayoutComplete */,
mAfterFlyoutHidden,
bubble.getIconView().getDotCenter(),
- !bubble.showDot());
+ !bubble.showDot(),
+ mPositioner);
}
mFlyout.bringToFront();
});
@@ -2321,7 +2349,7 @@
}
if (!mIsExpanded) {
- if (getBubbleCount() > 0) {
+ if (getBubbleCount() > 0 || mBubbleData.isShowingOverflow()) {
mBubbleContainer.getChildAt(0).getBoundsOnScreen(outRect);
// Increase the touch target size of the bubble
outRect.top -= mBubbleTouchPadding;
@@ -2550,7 +2578,7 @@
Insets insets = mPositioner.getInsets();
int leftPadding = insets.left + mExpandedViewPadding;
int rightPadding = insets.right + mExpandedViewPadding;
- if (mPositioner.getOrientation() == Configuration.ORIENTATION_LANDSCAPE) {
+ if (mPositioner.showBubblesVertically()) {
if (!mStackAnimationController.isStackOnLeftSide()) {
rightPadding += mPointerHeight + mBubbleSize;
} else {
@@ -2581,7 +2609,9 @@
bv.setZ((mMaxBubbles * mBubbleElevation) - i);
if (mIsExpanded) {
- bv.showDotAndBadge(false /* onLeft */);
+ // If we're not displaying vertically, we always show the badge on the left.
+ boolean onLeft = mPositioner.showBubblesVertically() && !mStackOnLeftOrWillBe;
+ bv.showDotAndBadge(onLeft);
} else if (i == 0) {
bv.showDotAndBadge(!mStackOnLeftOrWillBe);
} else {
@@ -2599,7 +2629,7 @@
return;
}
float bubblePosition = mExpandedAnimationController.getBubbleXOrYForOrientation(index);
- if (mPositioner.getOrientation() == Configuration.ORIENTATION_LANDSCAPE) {
+ if (mPositioner.showBubblesVertically()) {
float x = mStackOnLeftOrWillBe
? mPositioner.getAvailableRect().left
: mPositioner.getAvailableRect().right
@@ -2661,21 +2691,11 @@
.floatValue();
}
- /** Set the start position of the bubble stack. */
- public void setStackStartPosition(RelativeStackPosition position) {
- mStackAnimationController.setStackStartPosition(position);
- }
-
/** @return the position of the bubble stack. */
public PointF getStackPosition() {
return mStackAnimationController.getStackPosition();
}
- /** @return the relative position of the bubble stack. */
- public RelativeStackPosition getRelativeStackPosition() {
- return mStackAnimationController.getRelativeStackPosition();
- }
-
/**
* Logs the bubble UI event.
*
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index 0b68306..e21ba63 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -134,6 +134,7 @@
LayoutInflater inflater = LayoutInflater.from(c);
info.imageView = (BadgedImageView) inflater.inflate(
R.layout.bubble_view, stackView, false /* attachToRoot */);
+ info.imageView.initialize(controller.getPositioner());
info.expandedView = (BubbleExpandedView) inflater.inflate(
R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index 61fbf81..18aaa96 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -84,7 +84,11 @@
private float mBubbleSizePx;
/** Max number of bubbles shown in row above expanded view. */
private int mBubblesMaxRendered;
-
+ /** Max amount of space to have between bubbles when expanded. */
+ private int mBubblesMaxSpace;
+ /** Amount of space between the bubbles when expanded. */
+ private float mSpaceBetweenBubbles;
+ /** Whether the expand / collapse animation is running. */
private boolean mAnimatingExpand = false;
/**
@@ -205,8 +209,17 @@
mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
- mBubbleSizePx = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
+ mBubbleSizePx = mPositioner.getBubbleSize();
mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered);
+ mBubblesMaxSpace = res.getDimensionPixelSize(R.dimen.bubble_max_spacing);
+ final float availableSpace = mPositioner.isLandscape()
+ ? mPositioner.getAvailableRect().height()
+ : mPositioner.getAvailableRect().width();
+ final float spaceForMaxBubbles = (mExpandedViewPadding * 2)
+ + (mBubblesMaxRendered + 1) * mBubbleSizePx;
+ float spaceBetweenBubbles =
+ (availableSpace - spaceForMaxBubbles) / mBubblesMaxRendered;
+ mSpaceBetweenBubbles = Math.min(spaceBetweenBubbles, mBubblesMaxSpace);
}
/**
@@ -250,13 +263,15 @@
final Path path = new Path();
path.moveTo(bubble.getTranslationX(), bubble.getTranslationY());
- final float expandedY = getExpandedY();
+ final float expandedY = mPositioner.showBubblesVertically()
+ ? getBubbleXOrYForOrientation(index)
+ : getExpandedY();
if (expanding) {
// If we're expanding, first draw a line from the bubble's current position to the
// top of the screen.
path.lineTo(bubble.getTranslationX(), expandedY);
// Then, draw a line across the screen to the bubble's resting position.
- if (mPositioner.getOrientation() == Configuration.ORIENTATION_LANDSCAPE) {
+ if (mPositioner.showBubblesVertically()) {
Rect availableRect = mPositioner.getAvailableRect();
boolean onLeft = mCollapsePoint != null
&& mCollapsePoint.x < (availableRect.width() / 2f);
@@ -422,6 +437,9 @@
* bubbles to accommodate it if it was previously dragged out past the threshold.
*/
public void snapBubbleBack(View bubbleView, float velX, float velY) {
+ if (mLayout == null) {
+ return;
+ }
final int index = mLayout.indexOfChild(bubbleView);
animationForChildAtIndex(index)
@@ -580,7 +598,7 @@
return;
}
- if (mPositioner.getOrientation() == Configuration.ORIENTATION_LANDSCAPE) {
+ if (mPositioner.showBubblesVertically()) {
Rect availableRect = mPositioner.getAvailableRect();
boolean onLeft = mCollapsePoint != null
&& mCollapsePoint.x < (availableRect.width() / 2f);
@@ -613,23 +631,15 @@
if (mLayout == null) {
return 0;
}
+ final float positionInBar = index * (mBubbleSizePx + mSpaceBetweenBubbles);
Rect availableRect = mPositioner.getAvailableRect();
- final boolean isLandscape =
- mPositioner.getOrientation() == Configuration.ORIENTATION_LANDSCAPE;
- final float availableSpace = isLandscape
- ? availableRect.height()
- : availableRect.width();
- final float spaceForMaxBubbles = (mExpandedViewPadding * 2)
- + (mBubblesMaxRendered + 1) * mBubbleSizePx;
- final float spaceBetweenBubbles =
- (availableSpace - spaceForMaxBubbles) / mBubblesMaxRendered;
+ final boolean isLandscape = mPositioner.showBubblesVertically();
final float expandedStackSize = (mLayout.getChildCount() * mBubbleSizePx)
- + ((mLayout.getChildCount() - 1) * spaceBetweenBubbles);
+ + ((mLayout.getChildCount() - 1) * mSpaceBetweenBubbles);
final float centerPosition = isLandscape
? availableRect.centerY()
: availableRect.centerX();
final float rowStart = centerPosition - (expandedStackSize / 2f);
- final float positionInBar = index * (mBubbleSizePx + spaceBetweenBubbles);
return rowStart + positionInBar;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index d7f2e4b..24a8809d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -133,12 +133,6 @@
/** Whether or not the stack's start position has been set. */
private boolean mStackMovedToStartPosition = false;
- /**
- * The stack's most recent position along the edge of the screen. This is saved when the last
- * bubble is removed, so that the stack can be restored in its previous position.
- */
- private PointF mRestingStackPosition;
-
/** The height of the most recently visible IME. */
private float mImeHeight = 0f;
@@ -434,6 +428,9 @@
* Where the stack would be if it were snapped to the nearest horizontal edge (left or right).
*/
public PointF getStackPositionAlongNearestHorizontalEdge() {
+ if (mPositioner.showingInTaskbar()) {
+ return mPositioner.getRestingPosition();
+ }
final PointF stackPos = getStackPosition();
final boolean onLeft = mLayout.isFirstChildXLeftOfCenter(stackPos.x);
final RectF bounds = getAllowableStackPositionRegion();
@@ -447,7 +444,7 @@
pw.println("StackAnimationController state:");
pw.print(" isActive: "); pw.println(isActiveController());
pw.print(" restingStackPos: ");
- pw.println(mRestingStackPosition != null ? mRestingStackPosition.toString() : "null");
+ pw.println(mPositioner.getRestingPosition().toString());
pw.print(" currentStackPos: "); pw.println(mStackPosition.toString());
pw.print(" isMovingFromFlinging: "); pw.println(mIsMovingFromFlinging);
pw.print(" withinDismiss: "); pw.println(isStackStuckToTarget());
@@ -501,7 +498,7 @@
.addEndListener((animation, canceled, endValue, endVelocity) -> {
if (!canceled) {
- mRestingStackPosition.set(mStackPosition);
+ mPositioner.setRestingPosition(mStackPosition);
springFirstBubbleWithStackFollowing(property, spring, endVelocity,
finalPosition != null
@@ -679,7 +676,7 @@
// resting position - the touch location is not a valid resting
// position. We'll set this when the stack springs to the left or
// right side of the screen after the touch gesture ends.
- mRestingStackPosition.set(mStackPosition);
+ mPositioner.setRestingPosition(mStackPosition);
}
if (after != null) {
@@ -772,10 +769,9 @@
if (getBubbleCount() > 0) {
animationForChildAtIndex(0).translationX(mStackPosition.x).start();
} else {
+ // TODO: still needed with positioner?
// When all children are removed ensure stack position is sane
- setStackPosition(mRestingStackPosition == null
- ? getStartPosition()
- : mRestingStackPosition);
+ mPositioner.setRestingPosition(mPositioner.getRestingPosition());
// Remove the stack from the coordinator since we don't have any bubbles and aren't
// visible.
@@ -848,8 +844,8 @@
mSwapAnimationOffset = res.getDimensionPixelSize(R.dimen.bubble_swap_animation_offset);
mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
mElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
- mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
- mBubbleBitmapSize = res.getDimensionPixelSize(R.dimen.bubble_bitmap_size);
+ mBubbleSize = mPositioner.getBubbleSize();
+ mBubbleBitmapSize = mPositioner.getBubbleBitmapSize();
mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen);
}
@@ -873,9 +869,8 @@
// Post to ensure that the layout's width and height have been calculated.
mLayout.setVisibility(View.INVISIBLE);
mLayout.post(() -> {
- setStackPosition(mRestingStackPosition == null
- ? getStartPosition()
- : mRestingStackPosition);
+ setStackPosition(mPositioner.getRestingPosition());
+
mStackMovedToStartPosition = true;
mLayout.setVisibility(View.VISIBLE);
@@ -919,11 +914,7 @@
Log.d(TAG, String.format("Setting position to (%f, %f).", pos.x, pos.y));
mStackPosition.set(pos.x, pos.y);
- if (mRestingStackPosition == null) {
- mRestingStackPosition = new PointF();
- }
-
- mRestingStackPosition.set(mStackPosition);
+ mPositioner.setRestingPosition(mStackPosition);
// If we're not the active controller, we don't want to physically move the bubble views.
if (isActiveController()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
new file mode 100644
index 0000000..38e0519
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
@@ -0,0 +1,39 @@
+/*
+ * 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.wm.shell.hidedisplaycutout;
+
+import android.content.res.Configuration;
+
+import androidx.annotation.NonNull;
+
+import java.io.PrintWriter;
+
+/**
+ * Interface to engage hide display cutout feature.
+ */
+public interface HideDisplayCutout {
+ /**
+ * Notifies {@link Configuration} changed.
+ * @param newConfig
+ */
+ void onConfigurationChanged(Configuration newConfig);
+
+ /**
+ * Dumps hide display cutout status.
+ */
+ void dump(@NonNull PrintWriter pw);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
new file mode 100644
index 0000000..e4e2546
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
@@ -0,0 +1,101 @@
+/*
+ * 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.wm.shell.hidedisplaycutout;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.SystemProperties;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.wm.shell.common.DisplayController;
+
+import java.io.PrintWriter;
+
+/**
+ * Manages the hide display cutout status.
+ */
+public class HideDisplayCutoutController implements HideDisplayCutout {
+ private static final String TAG = "HideDisplayCutoutController";
+
+ private final Context mContext;
+ private final HideDisplayCutoutOrganizer mOrganizer;
+ @VisibleForTesting
+ boolean mEnabled;
+
+ HideDisplayCutoutController(Context context, HideDisplayCutoutOrganizer organizer) {
+ mContext = context;
+ mOrganizer = organizer;
+ updateStatus();
+ }
+
+ /**
+ * Creates {@link HideDisplayCutoutController}, returns {@code null} if the feature is not
+ * supported.
+ */
+ @Nullable
+ public static HideDisplayCutoutController create(
+ Context context, DisplayController displayController) {
+ // The SystemProperty is set for devices that support this feature and is used to control
+ // whether to create the HideDisplayCutout instance.
+ // It's defined in the device.mk (e.g. device/google/crosshatch/device.mk).
+ if (!SystemProperties.getBoolean("ro.support_hide_display_cutout", false)) {
+ return null;
+ }
+
+ HideDisplayCutoutOrganizer organizer =
+ new HideDisplayCutoutOrganizer(context, displayController);
+ return new HideDisplayCutoutController(context, organizer);
+ }
+
+ @VisibleForTesting
+ void updateStatus() {
+ // The config value is used for controlling enabling/disabling status of the feature and is
+ // defined in the config.xml in a "Hide Display Cutout" overlay package (e.g. device/google/
+ // crosshatch/crosshatch/overlay/packages/apps/overlays/NoCutoutOverlay).
+ final boolean enabled = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_hideDisplayCutoutWithDisplayArea);
+ if (enabled == mEnabled) {
+ return;
+ }
+
+ mEnabled = enabled;
+ if (enabled) {
+ mOrganizer.enableHideDisplayCutout();
+ } else {
+ mOrganizer.disableHideDisplayCutout();
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ updateStatus();
+ }
+
+ @Override
+ public void dump(@NonNull PrintWriter pw) {
+ final String prefix = " ";
+ pw.print(TAG);
+ pw.println(" states: ");
+ pw.print(prefix);
+ pw.print("mEnabled=");
+ pw.println(mEnabled);
+ mOrganizer.dump(pw);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
new file mode 100644
index 0000000..090d227
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
@@ -0,0 +1,330 @@
+/*
+ * 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.wm.shell.hidedisplaycutout;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.content.Context;
+import android.graphics.Insets;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.RotationUtils;
+import android.view.Display;
+import android.view.DisplayCutout;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.window.DisplayAreaAppearedInfo;
+import android.window.DisplayAreaInfo;
+import android.window.DisplayAreaOrganizer;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.R;
+import com.android.wm.shell.common.DisplayChangeController;
+import com.android.wm.shell.common.DisplayController;
+
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Manages the display areas of hide display cutout feature.
+ */
+class HideDisplayCutoutOrganizer extends DisplayAreaOrganizer {
+ private static final String TAG = "HideDisplayCutoutOrganizer";
+
+ private final Context mContext;
+ private final DisplayController mDisplayController;
+
+ @VisibleForTesting
+ @GuardedBy("this")
+ ArrayMap<WindowContainerToken, SurfaceControl> mDisplayAreaMap = new ArrayMap();
+ // The default display bound in natural orientation.
+ private final Rect mDefaultDisplayBounds = new Rect();
+ @VisibleForTesting
+ final Rect mCurrentDisplayBounds = new Rect();
+ // The default display cutout in natural orientation.
+ private Insets mDefaultCutoutInsets;
+ private Insets mCurrentCutoutInsets;
+ private boolean mIsDefaultPortrait;
+ private int mStatusBarHeight;
+ @VisibleForTesting
+ int mOffsetX;
+ @VisibleForTesting
+ int mOffsetY;
+ @VisibleForTesting
+ int mRotation;
+
+ /**
+ * Handles rotation based on OnDisplayChangingListener callback.
+ */
+ private final DisplayChangeController.OnDisplayChangingListener mRotationController =
+ (display, fromRotation, toRotation, wct) -> {
+ mRotation = toRotation;
+ updateBoundsAndOffsets(true /* enable */);
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ applyAllBoundsAndOffsets(wct, t);
+ // Only apply t here since the server will do the wct.apply when the method
+ // finishes.
+ t.apply();
+ };
+
+ HideDisplayCutoutOrganizer(Context context, DisplayController displayController) {
+ mContext = context;
+ mDisplayController = displayController;
+ }
+
+ @Override
+ public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo,
+ @NonNull SurfaceControl leash) {
+ if (!addDisplayAreaInfoAndLeashToMap(displayAreaInfo, leash)) {
+ return;
+ }
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+ applyBoundsAndOffsets(displayAreaInfo.token, leash, wct, tx);
+ applyTransaction(wct, tx);
+ }
+
+ @Override
+ public void onDisplayAreaVanished(@NonNull DisplayAreaInfo displayAreaInfo) {
+ synchronized (this) {
+ if (!mDisplayAreaMap.containsKey(displayAreaInfo.token)) {
+ Log.w(TAG, "Unrecognized token: " + displayAreaInfo.token);
+ return;
+ }
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ applyBoundsAndOffsets(
+ displayAreaInfo.token, mDisplayAreaMap.get(displayAreaInfo.token), wct, t);
+ applyTransaction(wct, t);
+ mDisplayAreaMap.remove(displayAreaInfo.token);
+ }
+ }
+
+ private void updateDisplayAreaMap(List<DisplayAreaAppearedInfo> displayAreaInfos) {
+ for (int i = 0; i < displayAreaInfos.size(); i++) {
+ final DisplayAreaInfo info = displayAreaInfos.get(i).getDisplayAreaInfo();
+ final SurfaceControl leash = displayAreaInfos.get(i).getLeash();
+ addDisplayAreaInfoAndLeashToMap(info, leash);
+ }
+ }
+
+ @VisibleForTesting
+ boolean addDisplayAreaInfoAndLeashToMap(@NonNull DisplayAreaInfo displayAreaInfo,
+ @NonNull SurfaceControl leash) {
+ synchronized (this) {
+ if (displayAreaInfo.displayId != DEFAULT_DISPLAY) {
+ return false;
+ }
+ if (mDisplayAreaMap.containsKey(displayAreaInfo.token)) {
+ Log.w(TAG, "Already appeared token: " + displayAreaInfo.token);
+ return false;
+ }
+ mDisplayAreaMap.put(displayAreaInfo.token, leash);
+ return true;
+ }
+ }
+
+ /**
+ * Enables hide display cutout.
+ */
+ void enableHideDisplayCutout() {
+ mDisplayController.addDisplayChangingController(mRotationController);
+ final Display display = mDisplayController.getDisplay(DEFAULT_DISPLAY);
+ if (display != null) {
+ mRotation = display.getRotation();
+ }
+ final List<DisplayAreaAppearedInfo> displayAreaInfos =
+ registerOrganizer(DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT);
+ updateDisplayAreaMap(displayAreaInfos);
+ updateBoundsAndOffsets(true /* enabled */);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ applyAllBoundsAndOffsets(wct, t);
+ applyTransaction(wct, t);
+ }
+
+ /**
+ * Disables hide display cutout.
+ */
+ void disableHideDisplayCutout() {
+ updateBoundsAndOffsets(false /* enabled */);
+ mDisplayController.removeDisplayChangingController(mRotationController);
+ unregisterOrganizer();
+ }
+
+ @VisibleForTesting
+ Insets getDisplayCutoutInsetsOfNaturalOrientation() {
+ final Display display = mDisplayController.getDisplay(DEFAULT_DISPLAY);
+ if (display == null) {
+ return Insets.NONE;
+ }
+ DisplayCutout cutout = display.getCutout();
+ Insets insets = cutout != null ? Insets.of(cutout.getSafeInsets()) : Insets.NONE;
+ return mRotation != Surface.ROTATION_0
+ ? RotationUtils.rotateInsets(insets, 4 /* total number of rotation */ - mRotation)
+ : insets;
+ }
+
+ @VisibleForTesting
+ Rect getDisplayBoundsOfNaturalOrientation() {
+ Point realSize = new Point(0, 0);
+ final Display display = mDisplayController.getDisplay(DEFAULT_DISPLAY);
+ if (display != null) {
+ display.getRealSize(realSize);
+ }
+ final boolean isDisplaySizeFlipped = isDisplaySizeFlipped();
+ return new Rect(
+ 0,
+ 0,
+ isDisplaySizeFlipped ? realSize.y : realSize.x,
+ isDisplaySizeFlipped ? realSize.x : realSize.y);
+ }
+
+ private boolean isDisplaySizeFlipped() {
+ return mRotation == Surface.ROTATION_90 || mRotation == Surface.ROTATION_270;
+ }
+
+ /**
+ * Updates bounds and offsets according to current state.
+ *
+ * @param enabled whether the hide display cutout feature is enabled.
+ */
+ @VisibleForTesting
+ void updateBoundsAndOffsets(boolean enabled) {
+ if (!enabled) {
+ resetBoundsAndOffsets();
+ } else {
+ initDefaultValuesIfNeeded();
+
+ // Reset to default values.
+ mCurrentDisplayBounds.set(mDefaultDisplayBounds);
+ mOffsetX = 0;
+ mOffsetY = 0;
+
+ // Update bounds and insets according to the rotation.
+ mCurrentCutoutInsets = RotationUtils.rotateInsets(mDefaultCutoutInsets, mRotation);
+ if (isDisplaySizeFlipped()) {
+ mCurrentDisplayBounds.set(
+ mCurrentDisplayBounds.top,
+ mCurrentDisplayBounds.left,
+ mCurrentDisplayBounds.bottom,
+ mCurrentDisplayBounds.right);
+ }
+ mCurrentDisplayBounds.inset(mCurrentCutoutInsets);
+
+ // Replace the top bound with the max(status bar height, cutout height) if there is
+ // cutout on the top side.
+ mStatusBarHeight = getStatusBarHeight();
+ if (mCurrentCutoutInsets.top != 0) {
+ mCurrentDisplayBounds.top = Math.max(mStatusBarHeight, mCurrentCutoutInsets.top);
+ }
+ mOffsetX = mCurrentDisplayBounds.left;
+ mOffsetY = mCurrentDisplayBounds.top;
+ }
+ }
+
+ private void resetBoundsAndOffsets() {
+ mCurrentDisplayBounds.setEmpty();
+ mOffsetX = 0;
+ mOffsetY = 0;
+ }
+
+ private void initDefaultValuesIfNeeded() {
+ if (!mDefaultDisplayBounds.isEmpty()) {
+ return;
+ }
+ mDefaultDisplayBounds.set(getDisplayBoundsOfNaturalOrientation());
+ mDefaultCutoutInsets = getDisplayCutoutInsetsOfNaturalOrientation();
+ mIsDefaultPortrait = mDefaultDisplayBounds.width() < mDefaultDisplayBounds.height();
+ }
+
+ private void applyAllBoundsAndOffsets(
+ WindowContainerTransaction wct, SurfaceControl.Transaction t) {
+ synchronized (this) {
+ mDisplayAreaMap.forEach((token, leash) -> {
+ applyBoundsAndOffsets(token, leash, wct, t);
+ });
+ }
+ }
+
+ @VisibleForTesting
+ void applyBoundsAndOffsets(WindowContainerToken token, SurfaceControl leash,
+ WindowContainerTransaction wct, SurfaceControl.Transaction t) {
+ wct.setBounds(token, mCurrentDisplayBounds.isEmpty() ? null : mCurrentDisplayBounds);
+ t.setPosition(leash, mOffsetX, mOffsetY);
+ }
+
+ @VisibleForTesting
+ void applyTransaction(WindowContainerTransaction wct, SurfaceControl.Transaction t) {
+ applyTransaction(wct);
+ t.apply();
+ }
+
+ private int getStatusBarHeight() {
+ final boolean isLandscape =
+ mIsDefaultPortrait ? isDisplaySizeFlipped() : !isDisplaySizeFlipped();
+ return mContext.getResources().getDimensionPixelSize(
+ isLandscape ? R.dimen.status_bar_height_landscape
+ : R.dimen.status_bar_height_portrait);
+ }
+
+ void dump(@NonNull PrintWriter pw) {
+ final String prefix = " ";
+ pw.print(TAG);
+ pw.println(" states: ");
+ synchronized (this) {
+ pw.print(prefix);
+ pw.print("mDisplayAreaMap=");
+ pw.println(mDisplayAreaMap);
+ }
+ pw.print(prefix);
+ pw.print("getDisplayBoundsOfNaturalOrientation()=");
+ pw.println(getDisplayBoundsOfNaturalOrientation());
+ pw.print(prefix);
+ pw.print("mDefaultDisplayBounds=");
+ pw.println(mDefaultDisplayBounds);
+ pw.print(prefix);
+ pw.print("mCurrentDisplayBounds=");
+ pw.println(mCurrentDisplayBounds);
+ pw.print(prefix);
+ pw.print("mDefaultCutoutInsets=");
+ pw.println(mDefaultCutoutInsets);
+ pw.print(prefix);
+ pw.print("mCurrentCutoutInsets=");
+ pw.println(mCurrentCutoutInsets);
+ pw.print(prefix);
+ pw.print("mRotation=");
+ pw.println(mRotation);
+ pw.print(prefix);
+ pw.print("mStatusBarHeight=");
+ pw.println(mStatusBarHeight);
+ pw.print(prefix);
+ pw.print("mOffsetX=");
+ pw.println(mOffsetX);
+ pw.print(prefix);
+ pw.print("mOffsetY=");
+ pw.println(mOffsetY);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
index 33b6902..728cc51 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
@@ -16,9 +16,10 @@
package com.android.wm.shell.flicker.pip.tv
-import android.os.SystemClock
import androidx.test.filters.RequiresDevice
-import org.junit.Assert.assertNotNull
+import androidx.test.uiautomator.UiObject2
+import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
+import com.android.wm.shell.flicker.wait
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
@@ -34,21 +35,27 @@
class TvPipMenuTests(rotationName: String, rotation: Int)
: TvPipTestBase(rotationName, rotation) {
+ private val systemUiResources =
+ packageManager.getResourcesForApplication(SYSTEM_UI_PACKAGE_NAME)
+ private val playButtonDescription = systemUiResources.run {
+ getString(getIdentifier("pip_play", "string", SYSTEM_UI_PACKAGE_NAME))
+ }
+ private val pauseButtonDescription = systemUiResources.run {
+ getString(getIdentifier("pip_pause", "string", SYSTEM_UI_PACKAGE_NAME))
+ }
+
@Before
override fun setUp() {
super.setUp()
// Launch the app and go to PiP
testApp.launchViaIntent()
- testApp.clickEnterPipButton()
}
@Test
fun pipMenu_open() {
- // Pressing the Window key should bring up Pip menu
- uiDevice.pressWindowKey()
- val pipMenu = uiDevice.waitForTvPipMenu()
- ?: fail("Pip notification should have been dismissed")
+ val pipMenu = enterPip_openMenu_assertShown()
+ // Make sure it's fullscreen
assertTrue("Pip menu should be shown fullscreen", pipMenu.isFullscreen(uiDevice))
testApp.closePipWindow()
@@ -56,22 +63,29 @@
@Test
fun pipMenu_backButton() {
- // Pressing the Window key should bring up Pip menu
- uiDevice.pressWindowKey()
- assertNotNull("Pip notification should have been dismissed", uiDevice.waitForTvPipMenu())
+ enterPip_openMenu_assertShown()
// Pressing the Back key should close the Pip menu
uiDevice.pressBack()
- assertTrue("Pip notification should have closed", uiDevice.waitForTvPipMenuToClose())
+ assertTrue("Pip menu should have closed", uiDevice.waitForTvPipMenuToClose())
+
+ testApp.closePipWindow()
+ }
+
+ @Test
+ fun pipMenu_homeButton() {
+ enterPip_openMenu_assertShown()
+
+ // Pressing the Home key should close the Pip menu
+ uiDevice.pressHome()
+ assertTrue("Pip menu should have closed", uiDevice.waitForTvPipMenuToClose())
testApp.closePipWindow()
}
@Test
fun pipMenu_closeButton() {
- // Pressing the Window key should bring up Pip menu
- uiDevice.pressWindowKey()
- assertNotNull("Pip notification should have been dismissed", uiDevice.waitForTvPipMenu())
+ enterPip_openMenu_assertShown()
// PiP menu should contain the Close button
val closeButton = uiDevice.findTvPipMenuCloseButton()
@@ -84,26 +98,53 @@
@Test
fun pipMenu_fullscreenButton() {
- // Pressing the Window key should bring up Pip menu
- uiDevice.pressWindowKey()
- assertNotNull("Pip notification should have been dismissed", uiDevice.waitForTvPipMenu())
+ enterPip_openMenu_assertShown()
// PiP menu should contain the Fullscreen button
val fullscreenButton = uiDevice.findTvPipMenuFullscreenButton()
?: fail("\"Full screen\" button should be shown in Pip menu")
// Clicking on the fullscreen button should return app to the fullscreen mode.
- // Click, wait for 3 seconds, check the app is fullscreen
+ // Click, wait for the app to go fullscreen
fullscreenButton.click()
- SystemClock.sleep(3_000L)
assertTrue("\"Full screen\" button should open the app fullscreen",
- testApp.ui?.isFullscreen(uiDevice) ?: false)
+ wait { testApp.ui?.isFullscreen(uiDevice) ?: false })
// Close the app
uiDevice.pressBack()
testApp.waitUntilClosed()
}
+ @Test
+ fun pipMenu_mediaPlayPauseButtons() {
+ // Start media session before entering PiP
+ testApp.clickStartMediaSessionButton()
+
+ enterPip_openMenu_assertShown()
+
+ // PiP menu should contain the Pause button
+ val pauseButton = uiDevice.findTvPipMenuElementWithDescription(pauseButtonDescription)
+ ?: fail("\"Pause\" button should be shown in Pip menu if there is an active " +
+ "playing media session.")
+
+ // When we pause media, the button should change from Pause to Play
+ pauseButton.click()
+
+ // PiP menu should contain the Play button now
+ uiDevice.waitForTvPipMenuElementWithDescription(playButtonDescription)
+ ?: fail("\"Play\" button should be shown in Pip menu if there is an active " +
+ "paused media session.")
+
+ testApp.closePipWindow()
+ }
+
+ private fun enterPip_openMenu_assertShown(): UiObject2 {
+ testApp.clickEnterPipButton()
+ // Pressing the Window key should bring up Pip menu
+ uiDevice.pressWindowKey()
+ return uiDevice.waitForTvPipMenu() ?: fail("Pip menu should have been shown")
+ }
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
index d9e6ff3..8db8bc67 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
@@ -45,6 +45,19 @@
fun UiDevice.findTvPipMenuFullscreenButton(): UiObject2? = findObject(
By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_FULLSCREEN_BUTTON_ID))
+fun UiDevice.findTvPipMenuElementWithDescription(desc: String): UiObject2? {
+ val buttonSelector = By.desc(desc)
+ val menuWithButtonSelector = By.copy(tvPipMenuSelector).hasDescendant(buttonSelector)
+ return findObject(menuWithButtonSelector)?.findObject(buttonSelector)
+}
+
+fun UiDevice.waitForTvPipMenuElementWithDescription(desc: String): UiObject2? {
+ val buttonSelector = By.desc(desc)
+ val menuWithButtonSelector = By.copy(tvPipMenuSelector).hasDescendant(buttonSelector)
+ return wait(Until.findObject(menuWithButtonSelector), WAIT_TIME_MS)
+ ?.findObject(buttonSelector)
+}
+
fun UiObject2.isFullscreen(uiDevice: UiDevice): Boolean = visibleBounds.run {
height() == uiDevice.displayHeight && width() == uiDevice.displayWidth
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java
index f70603e..b60068a 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java
@@ -17,7 +17,8 @@
package com.android.wm.shell.flicker.testapp;
import static android.media.MediaMetadata.METADATA_KEY_TITLE;
-import static android.media.session.PlaybackState.ACTION_PLAY_PAUSE;
+import static android.media.session.PlaybackState.ACTION_PAUSE;
+import static android.media.session.PlaybackState.ACTION_PLAY;
import static android.media.session.PlaybackState.ACTION_STOP;
import static android.media.session.PlaybackState.STATE_PAUSED;
import static android.media.session.PlaybackState.STATE_PLAYING;
@@ -48,7 +49,7 @@
private MediaSession mMediaSession;
private final PlaybackState.Builder mPlaybackStateBuilder = new PlaybackState.Builder()
- .setActions(ACTION_PLAY_PAUSE | ACTION_STOP)
+ .setActions(ACTION_PLAY | ACTION_PAUSE | ACTION_STOP)
.setState(STATE_STOPPED, 0, 1f);
private PlaybackState mPlaybackState = mPlaybackStateBuilder.build();
private final MediaMetadata.Builder mMediaMetadataBuilder = new MediaMetadata.Builder();
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index 9940ea5..dca2732 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -30,6 +30,7 @@
"mockito-target-extended-minus-junit4",
"truth-prebuilt",
"testables",
+ "platform-test-annotations",
],
libs: [
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 7adc411..7c9b9c3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -35,6 +35,7 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.Pair;
+import android.view.WindowManager;
import androidx.test.filters.SmallTest;
@@ -135,8 +136,9 @@
mBubbleB2 = new Bubble(mEntryB2, mSuppressionListener, mPendingIntentCanceledListener);
mBubbleB3 = new Bubble(mEntryB3, mSuppressionListener, mPendingIntentCanceledListener);
mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener, mPendingIntentCanceledListener);
-
- mBubbleData = new BubbleData(getContext(), mBubbleLogger);
+ TestableBubblePositioner positioner = new TestableBubblePositioner(mContext,
+ mock(WindowManager.class));
+ mBubbleData = new BubbleData(getContext(), mBubbleLogger, positioner);
// Used by BubbleData to set lastAccessedTime
when(mTimeSource.currentTimeMillis()).thenReturn(1000L);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleFlyoutViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleFlyoutViewTest.java
index 5b77e4a..69d5244 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleFlyoutViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleFlyoutViewTest.java
@@ -19,7 +19,9 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotSame;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.graphics.Color;
import android.graphics.PointF;
@@ -36,7 +38,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mockito;
+import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
@@ -48,11 +50,16 @@
private TextView mSenderName;
private float[] mDotCenter = new float[2];
private Bubble.FlyoutMessage mFlyoutMessage;
+ @Mock
+ private BubblePositioner mPositioner;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ when(mPositioner.getBubbleBitmapSize()).thenReturn(40);
+ when(mPositioner.getBubbleSize()).thenReturn(60);
+
mFlyoutMessage = new Bubble.FlyoutMessage();
mFlyoutMessage.senderName = "Josh";
mFlyoutMessage.message = "Hello";
@@ -70,7 +77,8 @@
mFlyout.setupFlyoutStartingAsDot(
mFlyoutMessage,
new PointF(100, 100), 500, true, Color.WHITE, null, null, mDotCenter,
- false);
+ false,
+ mPositioner);
mFlyout.setVisibility(View.VISIBLE);
assertEquals("Hello", mFlyoutText.getText());
@@ -80,10 +88,11 @@
@Test
public void testFlyoutHide_runsCallback() {
- Runnable after = Mockito.mock(Runnable.class);
+ Runnable after = mock(Runnable.class);
mFlyout.setupFlyoutStartingAsDot(mFlyoutMessage,
new PointF(100, 100), 500, true, Color.WHITE, null, after, mDotCenter,
- false);
+ false,
+ mPositioner);
mFlyout.hideFlyout();
verify(after).run();
@@ -93,7 +102,8 @@
public void testSetCollapsePercent() {
mFlyout.setupFlyoutStartingAsDot(mFlyoutMessage,
new PointF(100, 100), 500, true, Color.WHITE, null, null, mDotCenter,
- false);
+ false,
+ mPositioner);
mFlyout.setVisibility(View.VISIBLE);
mFlyout.setCollapsePercent(1f);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/TestableBubblePositioner.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/TestableBubblePositioner.java
new file mode 100644
index 0000000..96bc533
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/TestableBubblePositioner.java
@@ -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.
+ */
+
+package com.android.wm.shell.bubbles;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.view.WindowManager;
+
+public class TestableBubblePositioner extends BubblePositioner {
+
+ public TestableBubblePositioner(Context context,
+ WindowManager windowManager) {
+ super(context, windowManager);
+
+ updateInternal(Configuration.ORIENTATION_PORTRAIT,
+ Insets.of(0, 0, 0, 0),
+ new Rect(0, 0, 500, 1000));
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
index 9c4f341..1eba3c2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -64,7 +64,7 @@
super.setUp();
BubblePositioner positioner = new BubblePositioner(getContext(), mock(WindowManager.class));
- positioner.update(Configuration.ORIENTATION_PORTRAIT,
+ positioner.updateInternal(Configuration.ORIENTATION_PORTRAIT,
Insets.of(0, 0, 0, 0),
new Rect(0, 0, mDisplayWidth, mDisplayHeight));
mExpandedController = new ExpandedAnimationController(positioner, mExpandedViewPadding,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/StackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/StackAnimationControllerTest.java
index 6b01462..f36dcbe 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/StackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/StackAnimationControllerTest.java
@@ -27,6 +27,7 @@
import android.graphics.PointF;
import android.testing.AndroidTestingRunner;
import android.view.View;
+import android.view.WindowManager;
import android.widget.FrameLayout;
import androidx.dynamicanimation.animation.DynamicAnimation;
@@ -34,7 +35,7 @@
import androidx.test.filters.SmallTest;
import com.android.wm.shell.R;
-import com.android.wm.shell.bubbles.BubblePositioner;
+import com.android.wm.shell.bubbles.TestableBubblePositioner;
import com.android.wm.shell.common.FloatingContentCoordinator;
import org.junit.Before;
@@ -310,7 +311,7 @@
super(floatingContentCoordinator,
bubbleCountSupplier,
onBubbleAnimatedOutAction,
- mock(BubblePositioner.class));
+ new TestableBubblePositioner(mContext, mock(WindowManager.class)));
}
@Override
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
new file mode 100644
index 0000000..595440f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.wm.shell.hidedisplaycutout;
+
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+
+import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class HideDisplayCutoutControllerTest {
+ private TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+ private HideDisplayCutoutController mHideDisplayCutoutController;
+ @Mock
+ private HideDisplayCutoutOrganizer mMockDisplayAreaOrganizer;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mHideDisplayCutoutController = new HideDisplayCutoutController(
+ mContext, mMockDisplayAreaOrganizer);
+ }
+
+ @Test
+ public void testToggleHideDisplayCutout_On() {
+ mHideDisplayCutoutController.mEnabled = false;
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_hideDisplayCutoutWithDisplayArea, true);
+ reset(mMockDisplayAreaOrganizer);
+ mHideDisplayCutoutController.updateStatus();
+ verify(mMockDisplayAreaOrganizer).enableHideDisplayCutout();
+ }
+
+ @Test
+ public void testToggleHideDisplayCutout_Off() {
+ mHideDisplayCutoutController.mEnabled = true;
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_hideDisplayCutoutWithDisplayArea, false);
+ mHideDisplayCutoutController.updateStatus();
+ verify(mMockDisplayAreaOrganizer).disableHideDisplayCutout();
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java
new file mode 100644
index 0000000..2e4fd6a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java
@@ -0,0 +1,218 @@
+/*
+ * 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.wm.shell.hidedisplaycutout;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Configuration;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+import android.view.Display;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.window.DisplayAreaAppearedInfo;
+import android.window.DisplayAreaInfo;
+import android.window.DisplayAreaOrganizer;
+import android.window.IWindowContainerToken;
+import android.window.WindowContainerToken;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.wm.shell.common.DisplayController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class HideDisplayCutoutOrganizerTest {
+ private TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+ @Mock
+ private DisplayController mMockDisplayController;
+ private HideDisplayCutoutOrganizer mOrganizer;
+
+ private DisplayAreaInfo mDisplayAreaInfo;
+ private SurfaceControl mLeash;
+
+ @Mock
+ private Display mDisplay;
+ @Mock
+ private IWindowContainerToken mMockRealToken;
+ private WindowContainerToken mToken;
+
+ private final Rect mFakeDefaultBounds = new Rect(0, 0, 100, 200);
+ private final Insets mFakeDefaultCutoutInsets = Insets.of(0, 10, 0, 0);
+ private final int mFakeStatusBarHeightPortrait = 15;
+ private final int mFakeStatusBarHeightLandscape = 10;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay);
+
+ HideDisplayCutoutOrganizer organizer = new HideDisplayCutoutOrganizer(
+ mContext, mMockDisplayController);
+ mOrganizer = Mockito.spy(organizer);
+ doNothing().when(mOrganizer).unregisterOrganizer();
+ doNothing().when(mOrganizer).applyBoundsAndOffsets(any(), any(), any(), any());
+ doNothing().when(mOrganizer).applyTransaction(any(), any());
+
+ // It will be called when mDisplayAreaMap.containKey(token) is called.
+ Binder binder = new Binder();
+ doReturn(binder).when(mMockRealToken).asBinder();
+ mToken = new WindowContainerToken(mMockRealToken);
+ mLeash = new SurfaceControl();
+ mDisplayAreaInfo = new DisplayAreaInfo(
+ mToken, DEFAULT_DISPLAY, FEATURE_HIDE_DISPLAY_CUTOUT);
+ mDisplayAreaInfo.configuration.orientation = Configuration.ORIENTATION_PORTRAIT;
+ DisplayAreaAppearedInfo info = new DisplayAreaAppearedInfo(mDisplayAreaInfo, mLeash);
+ ArrayList<DisplayAreaAppearedInfo> infoList = new ArrayList<>();
+ infoList.add(info);
+ doReturn(infoList).when(mOrganizer).registerOrganizer(
+ DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT);
+ }
+
+ @Test
+ public void testEnableHideDisplayCutout() {
+ mOrganizer.enableHideDisplayCutout();
+
+ verify(mOrganizer).registerOrganizer(DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT);
+ verify(mOrganizer).addDisplayAreaInfoAndLeashToMap(mDisplayAreaInfo, mLeash);
+ verify(mOrganizer).updateBoundsAndOffsets(true);
+ assertThat(mOrganizer.mDisplayAreaMap.containsKey(mDisplayAreaInfo.token)).isTrue();
+ assertThat(mOrganizer.mDisplayAreaMap.containsValue(mLeash)).isTrue();
+ }
+
+ @Test
+ public void testOnDisplayAreaAppeared() {
+ mOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
+
+ assertThat(mOrganizer.mDisplayAreaMap.containsKey(mToken)).isTrue();
+ assertThat(mOrganizer.mDisplayAreaMap.containsValue(mLeash)).isTrue();
+ }
+
+ @Test
+ public void testOnDisplayAreaVanished() {
+ mOrganizer.mDisplayAreaMap.put(mDisplayAreaInfo.token, mLeash);
+ mOrganizer.onDisplayAreaVanished(mDisplayAreaInfo);
+
+ assertThat(mOrganizer.mDisplayAreaMap.containsKey(mDisplayAreaInfo.token)).isFalse();
+ }
+
+ @Test
+ public void testToggleHideDisplayCutout_enable_rot0() {
+ doReturn(mFakeDefaultBounds).when(mOrganizer).getDisplayBoundsOfNaturalOrientation();
+ doReturn(mFakeDefaultCutoutInsets).when(mOrganizer)
+ .getDisplayCutoutInsetsOfNaturalOrientation();
+ mContext.getOrCreateTestableResources().addOverride(
+ R.dimen.status_bar_height_portrait, mFakeStatusBarHeightPortrait);
+ doReturn(Surface.ROTATION_0).when(mDisplay).getRotation();
+ mOrganizer.enableHideDisplayCutout();
+
+ verify(mOrganizer).registerOrganizer(DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT);
+ verify(mOrganizer).addDisplayAreaInfoAndLeashToMap(mDisplayAreaInfo, mLeash);
+ verify(mOrganizer).updateBoundsAndOffsets(true);
+ assertThat(mOrganizer.mCurrentDisplayBounds).isEqualTo(new Rect(0, 15, 100, 200));
+ assertThat(mOrganizer.mOffsetX).isEqualTo(0);
+ assertThat(mOrganizer.mOffsetY).isEqualTo(15);
+ assertThat(mOrganizer.mRotation).isEqualTo(Surface.ROTATION_0);
+ }
+
+ @Test
+ public void testToggleHideDisplayCutout_enable_rot90() {
+ doReturn(mFakeDefaultBounds).when(mOrganizer).getDisplayBoundsOfNaturalOrientation();
+ doReturn(mFakeDefaultCutoutInsets).when(mOrganizer)
+ .getDisplayCutoutInsetsOfNaturalOrientation();
+ mContext.getOrCreateTestableResources().addOverride(
+ R.dimen.status_bar_height_landscape, mFakeStatusBarHeightLandscape);
+ doReturn(Surface.ROTATION_90).when(mDisplay).getRotation();
+ mOrganizer.enableHideDisplayCutout();
+
+ verify(mOrganizer).registerOrganizer(DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT);
+ verify(mOrganizer).addDisplayAreaInfoAndLeashToMap(mDisplayAreaInfo, mLeash);
+ verify(mOrganizer).updateBoundsAndOffsets(true);
+ assertThat(mOrganizer.mCurrentDisplayBounds).isEqualTo(new Rect(10, 0, 200, 100));
+ assertThat(mOrganizer.mOffsetX).isEqualTo(10);
+ assertThat(mOrganizer.mOffsetY).isEqualTo(0);
+ assertThat(mOrganizer.mRotation).isEqualTo(Surface.ROTATION_90);
+ }
+
+ @Test
+ public void testToggleHideDisplayCutout_enable_rot270() {
+ doReturn(mFakeDefaultBounds).when(mOrganizer).getDisplayBoundsOfNaturalOrientation();
+ doReturn(mFakeDefaultCutoutInsets).when(mOrganizer)
+ .getDisplayCutoutInsetsOfNaturalOrientation();
+ mContext.getOrCreateTestableResources().addOverride(
+ R.dimen.status_bar_height_landscape, mFakeStatusBarHeightLandscape);
+ doReturn(Surface.ROTATION_270).when(mDisplay).getRotation();
+ mOrganizer.enableHideDisplayCutout();
+
+ verify(mOrganizer).registerOrganizer(DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT);
+ verify(mOrganizer).addDisplayAreaInfoAndLeashToMap(mDisplayAreaInfo, mLeash);
+ verify(mOrganizer).updateBoundsAndOffsets(true);
+ assertThat(mOrganizer.mCurrentDisplayBounds).isEqualTo(new Rect(0, 0, 190, 100));
+ assertThat(mOrganizer.mOffsetX).isEqualTo(0);
+ assertThat(mOrganizer.mOffsetY).isEqualTo(0);
+ assertThat(mOrganizer.mRotation).isEqualTo(Surface.ROTATION_270);
+ }
+
+ @Test
+ public void testToggleHideDisplayCutout_disable() {
+ doReturn(mFakeDefaultBounds).when(mOrganizer).getDisplayBoundsOfNaturalOrientation();
+ doReturn(mFakeDefaultCutoutInsets).when(mOrganizer)
+ .getDisplayCutoutInsetsOfNaturalOrientation();
+ mContext.getOrCreateTestableResources().addOverride(
+ R.dimen.status_bar_height_portrait, mFakeStatusBarHeightPortrait);
+ mOrganizer.enableHideDisplayCutout();
+
+ // disable hide display cutout
+ mOrganizer.disableHideDisplayCutout();
+ verify(mOrganizer).updateBoundsAndOffsets(false);
+ verify(mOrganizer).unregisterOrganizer();
+ assertThat(mOrganizer.mCurrentDisplayBounds).isEqualTo(new Rect(0, 0, 0, 0));
+ assertThat(mOrganizer.mOffsetX).isEqualTo(0);
+ assertThat(mOrganizer.mOffsetY).isEqualTo(0);
+ }
+}
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 4fbcfe1..047b809 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -202,6 +202,7 @@
*
* @hide
*/
+ @SystemApi
@TestApi
public static final String FUSED_PROVIDER = "fused";
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 44890be..f216c8c 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -79,7 +79,8 @@
/**
* This is a class for reading and writing Exif tags in various image file formats.
* <p>
- * Supported for reading: JPEG, PNG, WebP, HEIF, DNG, CR2, NEF, NRW, ARW, RW2, ORF, PEF, SRW, RAF.
+ * Supported for reading: JPEG, PNG, WebP, HEIF, DNG, CR2, NEF, NRW, ARW, RW2, ORF, PEF, SRW, RAF,
+ * AVIF.
* <p>
* Supported for writing: JPEG, PNG, WebP.
* <p>
@@ -543,6 +544,8 @@
private static final byte[] HEIF_TYPE_FTYP = new byte[] {'f', 't', 'y', 'p'};
private static final byte[] HEIF_BRAND_MIF1 = new byte[] {'m', 'i', 'f', '1'};
private static final byte[] HEIF_BRAND_HEIC = new byte[] {'h', 'e', 'i', 'c'};
+ private static final byte[] HEIF_BRAND_AVIF = new byte[] {'a', 'v', 'i', 'f'};
+ private static final byte[] HEIF_BRAND_AVIS = new byte[] {'a', 'v', 'i', 's'};
// See http://fileformats.archiveteam.org/wiki/Olympus_ORF
private static final short ORF_SIGNATURE_1 = 0x4f52;
@@ -2662,6 +2665,7 @@
byte[] brand = new byte[4];
boolean isMif1 = false;
boolean isHeic = false;
+ boolean isAvif = false;
for (long i = 0; i < chunkDataSize / 4; ++i) {
if (signatureInputStream.read(brand) != brand.length) {
return false;
@@ -2674,8 +2678,11 @@
isMif1 = true;
} else if (Arrays.equals(brand, HEIF_BRAND_HEIC)) {
isHeic = true;
+ } else if (Arrays.equals(brand, HEIF_BRAND_AVIF)
+ || Arrays.equals(brand, HEIF_BRAND_AVIS)) {
+ isAvif = true;
}
- if (isMif1 && isHeic) {
+ if (isMif1 && (isHeic || isAvif)) {
return true;
}
}
diff --git a/media/java/android/media/ThumbnailUtils.java b/media/java/android/media/ThumbnailUtils.java
index fbf38dc..e6d95eb6 100644
--- a/media/java/android/media/ThumbnailUtils.java
+++ b/media/java/android/media/ThumbnailUtils.java
@@ -272,7 +272,8 @@
if (mimeType.equals("image/heif")
|| mimeType.equals("image/heif-sequence")
|| mimeType.equals("image/heic")
- || mimeType.equals("image/heic-sequence")) {
+ || mimeType.equals("image/heic-sequence")
+ || mimeType.equals("image/avif")) {
try (MediaMetadataRetriever retriever = new MediaMetadataRetriever()) {
retriever.setDataSource(file.getAbsolutePath());
bitmap = retriever.getThumbnailImageAtIndex(-1,
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index 00e3bce..f60c708 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -41,6 +41,7 @@
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.provider.Settings;
@@ -69,6 +70,7 @@
private final Context mContext;
private final ISoundTriggerSession mSoundTriggerSession;
+ private final IBinder mBinderToken = new Binder();
// Stores a mapping from the sound model UUID to the SoundTriggerInstance created by
// the createSoundTriggerDetector() call.
@@ -90,7 +92,8 @@
originatorIdentity.packageName = ActivityThread.currentOpPackageName();
try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
- mSoundTriggerSession = soundTriggerService.attachAsOriginator(originatorIdentity);
+ mSoundTriggerSession = soundTriggerService.attachAsOriginator(originatorIdentity,
+ mBinderToken);
}
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index aa3143f..97161e5 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -61,6 +61,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -369,6 +370,28 @@
mTunerResourceManager.setFrontendInfoList(infos);
}
+ /**
+ * Get frontend info list from native and build them into a {@link FrontendInfo} list. Any
+ * {@code null} FrontendInfo element would be removed.
+ */
+ private FrontendInfo[] getFrontendInfoListInternal() {
+ List<Integer> ids = getFrontendIds();
+ if (ids == null) {
+ return null;
+ }
+ FrontendInfo[] infos = new FrontendInfo[ids.size()];
+ for (int i = 0; i < ids.size(); i++) {
+ int id = ids.get(i);
+ FrontendInfo frontendInfo = getFrontendInfoById(id);
+ if (frontendInfo == null) {
+ Log.e(TAG, "Failed to get a FrontendInfo on frontend id:" + id + "!");
+ continue;
+ }
+ infos[i] = frontendInfo;
+ }
+ return Arrays.stream(infos).filter(Objects::nonNull).toArray(FrontendInfo[]::new);
+ }
+
/** @hide */
public static int getTunerVersion() {
return sTunerVersion;
@@ -932,7 +955,7 @@
}
/**
- * Gets the frontend information.
+ * Gets the initialized frontend information.
*
* @return The frontend information. {@code null} if the operation failed.
*/
@@ -950,6 +973,16 @@
return mFrontendInfo;
}
+ /**
+ * Get a list all the existed frontend information.
+ *
+ * @return The list of all the frontend information. {@code null} if the operation failed.
+ */
+ @Nullable
+ public List<FrontendInfo> getFrontendInfoList() {
+ return Arrays.asList(getFrontendInfoListInternal());
+ }
+
/** @hide */
public FrontendInfo getFrontendInfoById(int id) {
return nativeGetFrontendInfo(id);
diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
index 2f2d8f7..82cc78d 100644
--- a/media/java/android/media/tv/tuner/filter/Filter.java
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -25,6 +25,7 @@
import android.media.tv.tuner.Tuner;
import android.media.tv.tuner.Tuner.Result;
import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.TunerVersionChecker;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -148,7 +149,6 @@
public static final int SUBTYPE_PTP = 16;
-
/** @hide */
@IntDef(flag = true, prefix = "STATUS_", value = {STATUS_DATA_READY, STATUS_LOW_WATER,
STATUS_HIGH_WATER, STATUS_OVERFLOW})
@@ -180,6 +180,31 @@
*/
public static final int STATUS_OVERFLOW = Constants.DemuxFilterStatus.OVERFLOW;
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "SCRAMBLING_STATUS_",
+ value = {SCRAMBLING_STATUS_UNKNOWN, SCRAMBLING_STATUS_NOT_SCRAMBLED,
+ SCRAMBLING_STATUS_SCRAMBLED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ScramblingStatusMask {}
+
+ /**
+ * Content’s scrambling status is unknown
+ */
+ public static final int SCRAMBLING_STATUS_UNKNOWN =
+ android.hardware.tv.tuner.V1_1.Constants.ScramblingStatus.UNKNOWN;
+ /**
+ * Content is not scrambled.
+ */
+ public static final int SCRAMBLING_STATUS_NOT_SCRAMBLED =
+ android.hardware.tv.tuner.V1_1.Constants.ScramblingStatus.NOT_SCRAMBLED;
+ /**
+ * Content is scrambled.
+ */
+ public static final int SCRAMBLING_STATUS_SCRAMBLED =
+ android.hardware.tv.tuner.V1_1.Constants.ScramblingStatus.SCRAMBLED;
+
+
private static final String TAG = "Filter";
private long mNativeContext;
@@ -197,6 +222,7 @@
int type, int subType, FilterConfiguration settings);
private native int nativeGetId();
private native long nativeGetId64Bit();
+ private native int nativeconfigureScramblingEvent(int scramblingStatusMask);
private native int nativeSetDataSource(Filter source);
private native int nativeStartFilter();
private native int nativeStopFilter();
@@ -280,6 +306,38 @@
}
/**
+ * Configure the Filter to monitor specific Scrambling Status through
+ * {@link ScramblingStatusEvent}.
+ *
+ * <p>{@link ScramblingStatusEvent} should be sent at the following two scenarios:
+ *
+ * <ul>
+ * <li>When this method is called, the first detected scrambling status should be sent.
+ * <li>When the filter transits into the monitored statuses configured in
+ * {@code scramblingStatusMask}, event should be sent.
+ * <ul/>
+ *
+ * <p>This configuration is only supported in Tuner 1.1 or higher version. Unsupported version
+ * will cause no-op. Use {@link TunerVersionChecker.getTunerVersion()} to get the version
+ * information.
+ *
+ * @param scramblingStatusMask Scrambling Statuses to be monitored. Set corresponding bit to
+ * monitor it. Reset to stop monitoring.
+ * @return result status of the operation.
+ */
+ @Result
+ public int configureScramblingStatusEvent(@ScramblingStatusMask int scramblingStatusMask) {
+ synchronized (mLock) {
+ TunerUtils.checkResourceState(TAG, mIsClosed);
+ if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_1_1, "configureScramblingStatusEvent")) {
+ return Tuner.RESULT_UNAVAILABLE;
+ }
+ return nativeconfigureScramblingEvent(scramblingStatusMask);
+ }
+ }
+
+ /**
* Sets the filter's data source.
*
* A filter uses demux as data source by default. If the data was packetized
diff --git a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
index 1031e33..1bbd6e3 100644
--- a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
@@ -94,7 +94,7 @@
* {@link android.media.tv.tuner.TunerVersionChecker.getTunerVersion()} to get the version
* information.
*/
- public int getFirstMbInSlice() {
+ public int getFirstMacroblockInSlice() {
return mFirstMbInSlice;
}
diff --git a/media/java/android/media/tv/tuner/filter/ScramblingStatusEvent.java b/media/java/android/media/tv/tuner/filter/ScramblingStatusEvent.java
new file mode 100644
index 0000000..a78b96e
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/ScramblingStatusEvent.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 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 android.media.tv.tuner.filter;
+
+import android.annotation.SystemApi;
+
+/**
+ * Scrambling Status event sent from {@link Filter} objects with Scrambling Status type.
+ *
+ * <p>This event is only sent in Tuner 1.1 or higher version. Use
+ * {@link TunerVersionChecker.getTunerVersion()} to get the version information.
+ *
+ * @hide
+ */
+@SystemApi
+public final class ScramblingStatusEvent extends FilterEvent {
+ private final int mScramblingStatus;
+
+ private ScramblingStatusEvent(@Filter.ScramblingStatusMask int scramblingStatus) {
+ mScramblingStatus = scramblingStatus;
+ }
+
+ /**
+ * Gets Scrambling Status Type.
+ *
+ * <p>This event field is only sent in Tuner 1.1 or higher version. Unsupported version returns
+ * default value 0. Use {@link TunerVersionChecker.getTunerVersion()} to get the version
+ * information.
+ */
+ @Filter.ScramblingStatusMask
+ public int getScramblingStatus() {
+ return mScramblingStatus;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
index 6ea3bf8..2816199 100644
--- a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
+++ b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
@@ -99,7 +99,7 @@
* {@link android.media.tv.tuner.TunerVersionChecker.getTunerVersion()} to get the version
* information.
*/
- public int getFirstMbInSlice() {
+ public int getFirstMacroblockInSlice() {
return mFirstMbInSlice;
}
}
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
index 68c0c96..f3e9476 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
@@ -22,6 +22,9 @@
import android.media.tv.tuner.frontend.FrontendStatus.FrontendStatusType;
import android.util.Range;
+import java.util.Arrays;
+import java.util.Objects;
+
/**
* This class is used to specify meta information of a frontend.
*
@@ -118,4 +121,30 @@
public FrontendCapabilities getFrontendCapabilities() {
return mFrontendCap;
}
+
+
+ /** @hide */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || !(o instanceof FrontendInfo)) {
+ return false;
+ }
+ // TODO: compare FrontendCapabilities
+ FrontendInfo info = (FrontendInfo) o;
+ return mId == info.getId() && mType == info.getType()
+ && Objects.equals(mFrequencyRange, info.getFrequencyRange())
+ && Objects.equals(mSymbolRateRange, info.getSymbolRateRange())
+ && mAcquireRange == info.getAcquireRange()
+ && mExclusiveGroupId == info.getExclusiveGroupId()
+ && Arrays.equals(mStatusCaps, info.getStatusCapabilities());
+ }
+
+ /** @hide */
+ @Override
+ public int hashCode() {
+ return mId;
+ }
}
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 2541750..b9e7848 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -4024,6 +4024,28 @@
::android::hardware::tv::tuner::V1_1::Constant64Bit::INVALID_FILTER_ID_64BIT);
}
+static jint android_media_tv_Tuner_configure_scrambling_status_event(
+ JNIEnv* env, jobject filter, int scramblingStatusMask) {
+ sp<IFilter> iFilterSp = getFilter(env, filter)->getIFilter();
+ if (iFilterSp == NULL) {
+ ALOGD("Failed to configure scrambling event: filter not found");
+ return (jint) Result::NOT_INITIALIZED;
+ }
+
+ sp<::android::hardware::tv::tuner::V1_1::IFilter> iFilterSp_1_1;
+ iFilterSp_1_1 = ::android::hardware::tv::tuner::V1_1::IFilter::castFrom(iFilterSp);
+ Result res;
+
+ if (iFilterSp_1_1 != NULL) {
+ res = iFilterSp_1_1->configureScramblingEvent(scramblingStatusMask);
+ } else {
+ ALOGW("configureScramblingEvent is not supported with the current HAL implementation.");
+ return (jint) Result::INVALID_STATE;
+ }
+
+ return (jint) res;
+}
+
static jint android_media_tv_Tuner_set_filter_data_source(
JNIEnv* env, jobject filter, jobject srcFilter) {
sp<IFilter> iFilterSp = getFilter(env, filter)->getIFilter();
@@ -4695,6 +4717,8 @@
{ "nativeGetId", "()I", (void *)android_media_tv_Tuner_get_filter_id },
{ "nativeGetId64Bit", "()J",
(void *)android_media_tv_Tuner_get_filter_64bit_id },
+ { "nativeconfigureScramblingEvent", "(I)I",
+ (void *)android_media_tv_Tuner_configure_scrambling_status_event },
{ "nativeSetDataSource", "(Landroid/media/tv/tuner/filter/Filter;)I",
(void *)android_media_tv_Tuner_set_filter_data_source },
{ "nativeStartFilter", "()I", (void *)android_media_tv_Tuner_start_filter },
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index c5876af..c653c73 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -87,7 +87,9 @@
private BluetoothAdapter mBluetoothAdapter;
private WifiManager mWifiManager;
@Nullable private BluetoothLeScanner mBLEScanner;
- private ScanSettings mDefaultScanSettings = new ScanSettings.Builder().build();
+ private ScanSettings mDefaultScanSettings = new ScanSettings.Builder()
+ .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
+ .build();
private List<DeviceFilter<?>> mFilters;
private List<BluetoothLeDeviceFilter> mBLEFilters;
diff --git a/packages/SettingsProvider/src/android/provider/settings/OWNERS b/packages/SettingsProvider/src/android/provider/settings/OWNERS
index 7e7710b..0f88811 100644
--- a/packages/SettingsProvider/src/android/provider/settings/OWNERS
+++ b/packages/SettingsProvider/src/android/provider/settings/OWNERS
@@ -1,4 +1,4 @@
# Bug component: 656484
-include platform/frameworks/base/services/backup:/OWNERS
+include platform/frameworks/base:/services/backup/OWNERS
diff --git a/packages/SettingsProvider/test/src/android/provider/OWNERS b/packages/SettingsProvider/test/src/android/provider/OWNERS
index 7e7710b..0f88811 100644
--- a/packages/SettingsProvider/test/src/android/provider/OWNERS
+++ b/packages/SettingsProvider/test/src/android/provider/OWNERS
@@ -1,4 +1,4 @@
# Bug component: 656484
-include platform/frameworks/base/services/backup:/OWNERS
+include platform/frameworks/base:/services/backup/OWNERS
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index cc8bf4f..2cdd7f1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -154,8 +154,8 @@
}
mView.resetPasswordText(true /* animate */,
/* announce */
- result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS);
- if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) {
+ result.getResult() != PinResult.PIN_RESULT_TYPE_SUCCESS);
+ if (result.getResult() == PinResult.PIN_RESULT_TYPE_SUCCESS) {
mKeyguardUpdateMonitor.reportSimUnlocked(mSubId);
mRemainingAttempts = -1;
mShowDefaultMessage = true;
@@ -163,7 +163,7 @@
true, KeyguardUpdateMonitor.getCurrentUser());
} else {
mShowDefaultMessage = false;
- if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) {
+ if (result.getResult() == PinResult.PIN_RESULT_TYPE_INCORRECT) {
if (result.getAttemptsRemaining() <= 2) {
// this is getting critical - show dialog
getSimRemainingAttemptsDialog(
@@ -289,20 +289,14 @@
@Override
public void run() {
if (DEBUG) {
- Log.v(TAG, "call supplyPinReportResultForSubscriber(subid=" + mSubId + ")");
+ Log.v(TAG, "call supplyIccLockPin(subid=" + mSubId + ")");
}
- TelephonyManager telephonyManager =
- mTelephonyManager.createForSubscriptionId(mSubId);
- final PinResult result = telephonyManager.supplyPinReportPinResult(mPin);
- if (result == null) {
- Log.e(TAG, "Error result for supplyPinReportResult.");
- mView.post(() -> onSimCheckResponse(PinResult.getDefaultFailedResult()));
- } else {
- if (DEBUG) {
- Log.v(TAG, "supplyPinReportResult returned: " + result.toString());
- }
- mView.post(() -> onSimCheckResponse(result));
+ TelephonyManager telephonyManager = mTelephonyManager.createForSubscriptionId(mSubId);
+ final PinResult result = telephonyManager.supplyIccLockPin(mPin);
+ if (DEBUG) {
+ Log.v(TAG, "supplyIccLockPin returned: " + result.toString());
}
+ mView.post(() -> onSimCheckResponse(result));
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index a873749..adb4c13 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -267,8 +267,8 @@
}
mView.resetPasswordText(true /* animate */,
/* announce */
- result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS);
- if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) {
+ result.getResult() != PinResult.PIN_RESULT_TYPE_SUCCESS);
+ if (result.getResult() == PinResult.PIN_RESULT_TYPE_SUCCESS) {
mKeyguardUpdateMonitor.reportSimUnlocked(mSubId);
mRemainingAttempts = -1;
mShowDefaultMessage = true;
@@ -277,7 +277,7 @@
true, KeyguardUpdateMonitor.getCurrentUser());
} else {
mShowDefaultMessage = false;
- if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) {
+ if (result.getResult() == PinResult.PIN_RESULT_TYPE_INCORRECT) {
// show message
mMessageAreaController.setMessage(mView.getPukPasswordErrorMessage(
result.getAttemptsRemaining(), false,
@@ -390,23 +390,15 @@
@Override
public void run() {
- if (DEBUG) Log.v(TAG, "call supplyPukReportResult()");
- TelephonyManager telephonyManager = mTelephonyManager.createForSubscriptionId(mSubId);
- final PinResult result = telephonyManager.supplyPukReportPinResult(mPuk, mPin);
- if (result == null) {
- Log.e(TAG, "Error result for supplyPukReportResult.");
- mView.post(() -> onSimLockChangedResponse(PinResult.getDefaultFailedResult()));
- } else {
- if (DEBUG) {
- Log.v(TAG, "supplyPukReportResult returned: " + result.toString());
- }
- mView.post(new Runnable() {
- @Override
- public void run() {
- onSimLockChangedResponse(result);
- }
- });
+ if (DEBUG) {
+ Log.v(TAG, "call supplyIccLockPuk(subid=" + mSubId + ")");
}
+ TelephonyManager telephonyManager = mTelephonyManager.createForSubscriptionId(mSubId);
+ final PinResult result = telephonyManager.supplyIccLockPuk(mPuk, mPin);
+ if (DEBUG) {
+ Log.v(TAG, "supplyIccLockPuk returned: " + result.toString());
+ }
+ mView.post(() -> onSimLockChangedResponse(result));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index f073ced..7e48edf 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -109,6 +109,7 @@
.setSplitScreen(mWMComponent.getSplitScreen())
.setOneHanded(mWMComponent.getOneHanded())
.setBubbles(mWMComponent.getBubbles())
+ .setHideDisplayCutout(mWMComponent.getHideDisplayCutout())
.setShellDump(mWMComponent.getShellDump());
} else {
// TODO: Call on prepareSysUIComponentBuilder but not with real components.
@@ -116,6 +117,7 @@
.setSplitScreen(Optional.ofNullable(null))
.setOneHanded(Optional.ofNullable(null))
.setBubbles(Optional.ofNullable(null))
+ .setHideDisplayCutout(Optional.ofNullable(null))
.setShellDump(Optional.ofNullable(null));
}
mSysUIComponent = builder.build();
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index b3e6033..7127f26 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -23,6 +23,7 @@
import android.app.INotificationManager;
import android.content.Context;
import android.content.SharedPreferences;
+import android.content.om.OverlayManager;
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Handler;
import android.os.HandlerThread;
@@ -43,6 +44,7 @@
import com.android.keyguard.ViewMediatorCallback;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.Prefs;
+import com.android.systemui.R;
import com.android.systemui.accessibility.ModeSwitchesController;
import com.android.systemui.accessibility.SystemActions;
import com.android.systemui.assist.AssistManager;
@@ -78,6 +80,7 @@
import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.theme.ThemeOverlayApplier;
import com.android.systemui.util.leak.LeakDetector;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.splitscreen.SplitScreen;
@@ -194,6 +197,17 @@
}
/** */
+ @SysUISingleton
+ @Provides
+ static ThemeOverlayApplier provideThemeOverlayManager(Context context,
+ @Background Executor bgExecutor, OverlayManager overlayManager,
+ DumpManager dumpManager) {
+ return new ThemeOverlayApplier(overlayManager, bgExecutor,
+ context.getString(R.string.launcher_overlayable_package),
+ context.getString(R.string.themepicker_overlayable_package), dumpManager);
+ }
+
+ /** */
@Provides
@SysUISingleton
public NavigationBarController provideNavigationBarController(Context context,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 9705551..c3f2e18 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -31,6 +31,7 @@
import android.app.trust.TrustManager;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.om.OverlayManager;
import android.content.pm.IPackageManager;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
@@ -351,7 +352,7 @@
@Provides
static WallpaperManager provideWallpaperManager(Context context) {
- return (WallpaperManager) context.getSystemService(Context.WALLPAPER_SERVICE);
+ return context.getSystemService(WallpaperManager.class);
}
@Provides
@@ -363,6 +364,12 @@
@Provides
@Singleton
+ static OverlayManager provideOverlayManager(Context context) {
+ return context.getSystemService(OverlayManager.class);
+ }
+
+ @Provides
+ @Singleton
static WindowManager provideWindowManager(Context context) {
return context.getSystemService(WindowManager.class);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 54aeab5..02c3c2f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -26,6 +26,7 @@
import com.android.systemui.util.InjectionInflationController;
import com.android.wm.shell.ShellDump;
import com.android.wm.shell.bubbles.Bubbles;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.splitscreen.SplitScreen;
@@ -66,6 +67,9 @@
Builder setBubbles(Optional<Bubbles> b);
@BindsInstance
+ Builder setHideDisplayCutout(Optional<HideDisplayCutout> h);
+
+ @BindsInstance
Builder setShellDump(Optional<ShellDump> shellDump);
SysUIComponent build();
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index 9154ddb..b3e62e52 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -20,6 +20,7 @@
import com.android.wm.shell.ShellDump;
import com.android.wm.shell.ShellInit;
import com.android.wm.shell.bubbles.Bubbles;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.splitscreen.SplitScreen;
@@ -71,4 +72,7 @@
@WMSingleton
Optional<Bubbles> getBubbles();
+
+ @WMSingleton
+ Optional<HideDisplayCutout> getHideDisplayCutout();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 07a5839..35874cd 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -154,7 +154,6 @@
private SaveImageInBackgroundTask mSaveInBgTask;
private Animator mScreenshotAnimation;
- private Animator mDismissAnimation;
private Runnable mOnCompleteRunnable;
private boolean mInDarkMode;
@@ -168,7 +167,6 @@
case MESSAGE_CORNER_TIMEOUT:
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT);
ScreenshotController.this.dismissScreenshot(false);
- mOnCompleteRunnable.run();
break;
default:
break;
@@ -279,30 +277,22 @@
rect -> takeScreenshotInternal(finisher, rect));
}
- boolean isDismissing() {
- return (mDismissAnimation != null && mDismissAnimation.isRunning());
- }
-
/**
* Clears current screenshot
*/
void dismissScreenshot(boolean immediate) {
- Log.v(TAG, "clearing screenshot");
+ // If we're already animating out, don't restart the animation
+ // (but do obey an immediate dismissal)
+ if (!immediate && mScreenshotView.isDismissing()) {
+ Log.v(TAG, "Already dismissing, ignoring duplicate command");
+ return;
+ }
+ Log.v(TAG, "Clearing screenshot");
mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
- mScreenshotView.getViewTreeObserver().removeOnComputeInternalInsetsListener(
- mScreenshotView);
- if (!immediate) {
- mDismissAnimation = mScreenshotView.createScreenshotDismissAnimation();
- mDismissAnimation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- clearScreenshot();
- }
- });
- mDismissAnimation.start();
+ if (immediate) {
+ resetScreenshotView();
} else {
- clearScreenshot();
+ mScreenshotView.animateDismissal();
}
}
@@ -373,6 +363,7 @@
// Inflate the screenshot layout
mScreenshotView = (ScreenshotView)
LayoutInflater.from(mContext).inflate(R.layout.global_screenshot, null);
+ mScreenshotView.init(mUiEventLogger, this::resetScreenshotView);
// TODO(159460485): Remove this when focus is handled properly in the system
mScreenshotView.setOnTouchListener((v, event) -> {
@@ -438,10 +429,10 @@
if (mScreenshotView.isAttachedToWindow()) {
// if we didn't already dismiss for another reason
- if (mDismissAnimation == null || !mDismissAnimation.isRunning()) {
+ if (!mScreenshotView.isDismissing()) {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED);
}
- dismissScreenshot(true);
+ mScreenshotView.reset();
}
mScreenBitmap = screenshot;
@@ -459,10 +450,6 @@
onConfigChanged(mContext.getResources().getConfiguration());
- if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
- mDismissAnimation.cancel();
- }
-
// The window is focusable by default
setWindowFocusable(true);
@@ -474,7 +461,8 @@
mScrollCaptureClient.request(DEFAULT_DISPLAY, (connection) ->
mScreenshotView.showScrollChip(() ->
runScrollCapture(connection,
- () -> dismissScreenshot(true))));
+ () -> mScreenshotHandler.post(
+ () -> dismissScreenshot(false)))));
}
}
@@ -519,6 +507,7 @@
*/
private void startAnimation(final Consumer<Uri> finisher, Rect screenRect, Insets screenInsets,
boolean showFlash) {
+ mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
mScreenshotHandler.post(() -> {
if (!mScreenshotView.isAttachedToWindow()) {
mWindowManager.addView(mScreenshotView, mWindowLayoutParams);
@@ -531,8 +520,7 @@
mScreenshotView);
mScreenshotAnimation =
- mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash,
- this::onElementTapped);
+ mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash);
saveScreenshotInWorkerThread(finisher,
new ScreenshotController.ActionsReadyListener() {
@@ -551,6 +539,14 @@
});
}
+ private void resetScreenshotView() {
+ if (mScreenshotView.isAttachedToWindow()) {
+ mWindowManager.removeView(mScreenshotView);
+ }
+ mScreenshotView.reset();
+ mOnCompleteRunnable.run();
+ }
+
/**
* Creates a new worker thread and saves the screenshot to the media store.
*/
@@ -590,7 +586,6 @@
SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS,
AccessibilityManager.FLAG_CONTENT_CONTROLS);
- mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
mScreenshotHandler.sendMessageDelayed(
mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
timeoutMs);
@@ -602,24 +597,16 @@
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
- mScreenshotView.setChipIntents(
- imageData, event -> onElementTapped(event));
+ mScreenshotView.setChipIntents(imageData);
}
});
} else {
- mScreenshotView.setChipIntents(
- imageData, this::onElementTapped);
+ mScreenshotView.setChipIntents(imageData);
}
});
}
}
- private void onElementTapped(ScreenshotEvent event) {
- mUiEventLogger.log(event);
- dismissScreenshot(false);
- mOnCompleteRunnable.run();
- }
-
/**
* Logs success/failure of the screenshot saving task, and shows an error if it failed.
*/
@@ -633,20 +620,11 @@
}
}
- private void clearScreenshot() {
- if (mScreenshotView.isAttachedToWindow()) {
- mWindowManager.removeView(mScreenshotView);
- }
-
- mScreenshotView.reset();
- }
-
private boolean isUserSetupComplete() {
return Settings.Secure.getInt(mContext.getContentResolver(),
SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
}
-
/**
* Updates the window focusability. If the window is already showing, then it updates the
* window immediately, otherwise the layout params will be applied when the window is next
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 3383f80..2b4eacb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -60,6 +60,7 @@
import android.widget.ImageView;
import android.widget.LinearLayout;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.shared.system.QuickStepContract;
@@ -115,6 +116,10 @@
private ScreenshotActionChip mEditChip;
private ScreenshotActionChip mScrollChip;
+ private UiEventLogger mUiEventLogger;
+ private Runnable mOnDismissRunnable;
+ private Animator mDismissAnimation;
+
private final ArrayList<ScreenshotActionChip> mSmartChips = new ArrayList<>();
private PendingInteraction mPendingInteraction;
@@ -161,7 +166,7 @@
public void showScrollChip(Runnable onClick) {
mScrollChip.setVisibility(VISIBLE);
mScrollChip.setOnClickListener((v) ->
- onClick.run()
+ onClick.run()
// TODO Logging, store event consumer to a field
//onElementTapped.accept(ScreenshotEvent.SCREENSHOT_SCROLL_TAPPED);
);
@@ -248,6 +253,17 @@
requestFocus();
}
+ /**
+ * Set up the logger and callback on dismissal.
+ *
+ * Note: must be called before any other (non-constructor) method or null pointer exceptions
+ * may occur.
+ */
+ void init(UiEventLogger uiEventLogger, Runnable onDismissRunnable) {
+ mUiEventLogger = uiEventLogger;
+ mOnDismissRunnable = onDismissRunnable;
+ }
+
void takePartialScreenshot(Consumer<Rect> onPartialScreenshotSelected) {
mScreenshotSelectorView.setOnScreenshotSelected(onPartialScreenshotSelected);
mScreenshotSelectorView.setVisibility(View.VISIBLE);
@@ -260,8 +276,7 @@
mScreenshotPreview.setVisibility(View.INVISIBLE);
}
- AnimatorSet createScreenshotDropInAnimation(Rect bounds, boolean showFlash,
- Consumer<ScreenshotEvent> onElementTapped) {
+ AnimatorSet createScreenshotDropInAnimation(Rect bounds, boolean showFlash) {
mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
mScreenshotPreview.buildLayer();
@@ -362,8 +377,10 @@
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
- mDismissButton.setOnClickListener(view ->
- onElementTapped.accept(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL));
+ mDismissButton.setOnClickListener(view -> {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL);
+ animateDismissal();
+ });
mDismissButton.setAlpha(1);
float dismissOffset = mDismissButton.getWidth() / 2f;
float finalDismissX = mDirectionLTR
@@ -460,19 +477,25 @@
return animator;
}
- void setChipIntents(ScreenshotController.SavedImageData imageData,
- Consumer<ScreenshotEvent> onElementTapped) {
+ void setChipIntents(ScreenshotController.SavedImageData imageData) {
mShareChip.setPendingIntent(imageData.shareAction.actionIntent,
- () -> onElementTapped.accept(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED));
+ () -> {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED);
+ animateDismissal();
+ });
mEditChip.setPendingIntent(imageData.editAction.actionIntent,
- () -> onElementTapped.accept(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED));
+ () -> {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED);
+ animateDismissal();
+ });
mScreenshotPreview.setOnClickListener(v -> {
try {
imageData.editAction.actionIntent.send();
} catch (PendingIntent.CanceledException e) {
Log.e(TAG, "Intent cancelled", e);
}
- onElementTapped.accept(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED);
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED);
+ animateDismissal();
});
if (mPendingInteraction != null) {
@@ -496,43 +519,49 @@
actionChip.setText(smartAction.title);
actionChip.setIcon(smartAction.getIcon(), false);
actionChip.setPendingIntent(smartAction.actionIntent,
- () -> onElementTapped.accept(
- ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED));
+ () -> {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED);
+ animateDismissal();
+ });
mActionsView.addView(actionChip);
mSmartChips.add(actionChip);
}
}
}
+ boolean isDismissing() {
+ return (mDismissAnimation != null && mDismissAnimation.isRunning());
+ }
- AnimatorSet createScreenshotDismissAnimation() {
- ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
- alphaAnim.setStartDelay(SCREENSHOT_DISMISS_ALPHA_OFFSET_MS);
- alphaAnim.setDuration(SCREENSHOT_DISMISS_ALPHA_DURATION_MS);
- alphaAnim.addUpdateListener(animation -> {
- setAlpha(1 - animation.getAnimatedFraction());
+ void animateDismissal() {
+ getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+ mDismissAnimation = createScreenshotDismissAnimation();
+ mDismissAnimation.addListener(new AnimatorListenerAdapter() {
+ private boolean mCancelled = false;
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ super.onAnimationCancel(animation);
+ mCancelled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (!mCancelled) {
+ mOnDismissRunnable.run();
+ }
+ }
});
-
- ValueAnimator yAnim = ValueAnimator.ofFloat(0, 1);
- yAnim.setInterpolator(mAccelerateInterpolator);
- yAnim.setDuration(SCREENSHOT_DISMISS_Y_DURATION_MS);
- float screenshotStartY = mScreenshotPreview.getTranslationY();
- float dismissStartY = mDismissButton.getTranslationY();
- yAnim.addUpdateListener(animation -> {
- float yDelta = MathUtils.lerp(0, mDismissDeltaY, animation.getAnimatedFraction());
- mScreenshotPreview.setTranslationY(screenshotStartY + yDelta);
- mDismissButton.setTranslationY(dismissStartY + yDelta);
- mActionsContainer.setTranslationY(yDelta);
- mActionsContainerBackground.setTranslationY(yDelta);
- });
-
- AnimatorSet animSet = new AnimatorSet();
- animSet.play(yAnim).with(alphaAnim);
-
- return animSet;
+ mDismissAnimation.start();
}
void reset() {
+ if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
+ mDismissAnimation.cancel();
+ }
+ // Make sure we clean up the view tree observer
+ getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
// Clear any references to the bitmap
mScreenshotPreview.setImageDrawable(null);
mActionsContainerBackground.setVisibility(View.GONE);
@@ -561,6 +590,33 @@
mScreenshotSelectorView.stop();
}
+ private AnimatorSet createScreenshotDismissAnimation() {
+ ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
+ alphaAnim.setStartDelay(SCREENSHOT_DISMISS_ALPHA_OFFSET_MS);
+ alphaAnim.setDuration(SCREENSHOT_DISMISS_ALPHA_DURATION_MS);
+ alphaAnim.addUpdateListener(animation -> {
+ setAlpha(1 - animation.getAnimatedFraction());
+ });
+
+ ValueAnimator yAnim = ValueAnimator.ofFloat(0, 1);
+ yAnim.setInterpolator(mAccelerateInterpolator);
+ yAnim.setDuration(SCREENSHOT_DISMISS_Y_DURATION_MS);
+ float screenshotStartY = mScreenshotPreview.getTranslationY();
+ float dismissStartY = mDismissButton.getTranslationY();
+ yAnim.addUpdateListener(animation -> {
+ float yDelta = MathUtils.lerp(0, mDismissDeltaY, animation.getAnimatedFraction());
+ mScreenshotPreview.setTranslationY(screenshotStartY + yDelta);
+ mDismissButton.setTranslationY(dismissStartY + yDelta);
+ mActionsContainer.setTranslationY(yDelta);
+ mActionsContainerBackground.setTranslationY(yDelta);
+ });
+
+ AnimatorSet animSet = new AnimatorSet();
+ animSet.play(yAnim).with(alphaAnim);
+
+ return animSet;
+ }
+
/**
* Create a drawable using the size of the bitmap and insets as the fractional inset parameters.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 4e22833..5f35e60 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -144,7 +144,7 @@
@Override
public boolean onUnbind(Intent intent) {
- if (mScreenshot != null && !mScreenshot.isDismissing()) {
+ if (mScreenshot != null) {
mScreenshot.dismissScreenshot(true);
}
unregisterReceiver(mBroadcastReceiver);
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayManager.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
similarity index 64%
rename from packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayManager.java
rename to packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index 665cb63..0aa2a73 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayManager.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
@@ -21,11 +21,18 @@
import android.util.ArrayMap;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
+import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
+
import com.google.android.collect.Lists;
import com.google.android.collect.Sets;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -34,9 +41,19 @@
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
-class ThemeOverlayManager {
+/**
+ * Responsible for orchestrating overlays, based on user preferences and other inputs from
+ * {@link ThemeOverlayController}.
+ */
+@SysUISingleton
+public class ThemeOverlayApplier implements Dumpable {
private static final String TAG = "ThemeOverlayManager";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ @VisibleForTesting
+ static final String MONET_ACCENT_COLOR_PACKAGE = "com.android.theme.accentcolor.color";
+ @VisibleForTesting
+ static final String MONET_SYSTEM_PALETTE_PACKAGE = "com.android.theme.systemcolors.color";
@VisibleForTesting
static final String ANDROID_PACKAGE = "android";
@@ -46,7 +63,11 @@
static final String SYSUI_PACKAGE = "com.android.systemui";
@VisibleForTesting
- static final String OVERLAY_CATEGORY_COLOR = "android.theme.customization.accent_color";
+ static final String OVERLAY_CATEGORY_ACCENT_COLOR =
+ "android.theme.customization.accent_color";
+ @VisibleForTesting
+ static final String OVERLAY_CATEGORY_SYSTEM_PALETTE =
+ "android.theme.customization.system_palette";
@VisibleForTesting
static final String OVERLAY_CATEGORY_FONT = "android.theme.customization.font";
@VisibleForTesting
@@ -73,24 +94,36 @@
* starts with launcher and grouped by target package.
*/
static final List<String> THEME_CATEGORIES = Lists.newArrayList(
+ OVERLAY_CATEGORY_SYSTEM_PALETTE,
OVERLAY_CATEGORY_ICON_LAUNCHER,
OVERLAY_CATEGORY_SHAPE,
OVERLAY_CATEGORY_FONT,
- OVERLAY_CATEGORY_COLOR,
+ OVERLAY_CATEGORY_ACCENT_COLOR,
OVERLAY_CATEGORY_ICON_ANDROID,
OVERLAY_CATEGORY_ICON_SYSUI,
OVERLAY_CATEGORY_ICON_SETTINGS,
OVERLAY_CATEGORY_ICON_THEME_PICKER);
- /* Categories that need to applied to the current user as well as the system user. */
+ /* Categories that need to be applied to the current user as well as the system user. */
@VisibleForTesting
static final Set<String> SYSTEM_USER_CATEGORIES = Sets.newHashSet(
- OVERLAY_CATEGORY_COLOR,
+ OVERLAY_CATEGORY_SYSTEM_PALETTE,
+ OVERLAY_CATEGORY_ACCENT_COLOR,
OVERLAY_CATEGORY_FONT,
OVERLAY_CATEGORY_SHAPE,
OVERLAY_CATEGORY_ICON_ANDROID,
OVERLAY_CATEGORY_ICON_SYSUI);
+ /**
+ * List of main colors of Monet themes. These are extracted from overlays installed
+ * on the system.
+ */
+ private final ArrayList<Integer> mMainSystemColors = new ArrayList<>();
+ /**
+ * Same as above, but providing accent colors instead of a system palette.
+ */
+ private final ArrayList<Integer> mAccentColors = new ArrayList<>();
+
/* Allowed overlay categories for each target package. */
private final Map<String, Set<String>> mTargetPackageToCategories = new ArrayMap<>();
/* Target package for each overlay category. */
@@ -100,15 +133,15 @@
private final String mLauncherPackage;
private final String mThemePickerPackage;
- ThemeOverlayManager(OverlayManager overlayManager, Executor executor,
- String launcherPackage, String themePickerPackage) {
+ public ThemeOverlayApplier(OverlayManager overlayManager, Executor executor,
+ String launcherPackage, String themePickerPackage, DumpManager dumpManager) {
mOverlayManager = overlayManager;
mExecutor = executor;
mLauncherPackage = launcherPackage;
mThemePickerPackage = themePickerPackage;
mTargetPackageToCategories.put(ANDROID_PACKAGE, Sets.newHashSet(
- OVERLAY_CATEGORY_COLOR, OVERLAY_CATEGORY_FONT,
- OVERLAY_CATEGORY_SHAPE, OVERLAY_CATEGORY_ICON_ANDROID));
+ OVERLAY_CATEGORY_SYSTEM_PALETTE, OVERLAY_CATEGORY_ACCENT_COLOR,
+ OVERLAY_CATEGORY_FONT, OVERLAY_CATEGORY_SHAPE, OVERLAY_CATEGORY_ICON_ANDROID));
mTargetPackageToCategories.put(SYSUI_PACKAGE,
Sets.newHashSet(OVERLAY_CATEGORY_ICON_SYSUI));
mTargetPackageToCategories.put(SETTINGS_PACKAGE,
@@ -117,7 +150,7 @@
Sets.newHashSet(OVERLAY_CATEGORY_ICON_LAUNCHER));
mTargetPackageToCategories.put(mThemePickerPackage,
Sets.newHashSet(OVERLAY_CATEGORY_ICON_THEME_PICKER));
- mCategoryToTargetPackage.put(OVERLAY_CATEGORY_COLOR, ANDROID_PACKAGE);
+ mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR, ANDROID_PACKAGE);
mCategoryToTargetPackage.put(OVERLAY_CATEGORY_FONT, ANDROID_PACKAGE);
mCategoryToTargetPackage.put(OVERLAY_CATEGORY_SHAPE, ANDROID_PACKAGE);
mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_ANDROID, ANDROID_PACKAGE);
@@ -125,6 +158,54 @@
mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_SETTINGS, SETTINGS_PACKAGE);
mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_LAUNCHER, mLauncherPackage);
mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_THEME_PICKER, mThemePickerPackage);
+
+ collectMonetSystemOverlays();
+ dumpManager.registerDumpable(TAG, this);
+ }
+
+ /**
+ * List of accent colors available as Monet overlays.
+ */
+ List<Integer> getAvailableAccentColors() {
+ return mAccentColors;
+ }
+
+ /**
+ * List of main system colors available as Monet overlays.
+ */
+ List<Integer> getAvailableSystemColors() {
+ return mMainSystemColors;
+ }
+
+ private void collectMonetSystemOverlays() {
+ List<OverlayInfo> androidOverlays = mOverlayManager
+ .getOverlayInfosForTarget(ANDROID_PACKAGE, UserHandle.SYSTEM);
+ for (OverlayInfo overlayInfo : androidOverlays) {
+ String packageName = overlayInfo.packageName;
+ if (DEBUG) {
+ Log.d(TAG, "Processing overlay " + packageName);
+ }
+ if (OVERLAY_CATEGORY_SYSTEM_PALETTE.equals(overlayInfo.category)
+ && packageName.startsWith(MONET_SYSTEM_PALETTE_PACKAGE)) {
+ try {
+ String color = packageName.replace(MONET_SYSTEM_PALETTE_PACKAGE, "");
+ mMainSystemColors.add(Integer.parseInt(color, 16));
+ } catch (NumberFormatException e) {
+ Log.w(TAG, "Invalid package name for overlay " + packageName, e);
+ }
+ } else if (OVERLAY_CATEGORY_ACCENT_COLOR.equals(overlayInfo.category)
+ && packageName.startsWith(MONET_ACCENT_COLOR_PACKAGE)) {
+ try {
+ String color = packageName.replace(MONET_ACCENT_COLOR_PACKAGE, "");
+ mAccentColors.add(Integer.parseInt(color, 16));
+ } catch (NumberFormatException e) {
+ Log.w(TAG, "Invalid package name for overlay " + packageName, e);
+ }
+ } else if (DEBUG) {
+ Log.d(TAG, "Unknown overlay: " + packageName + " category: "
+ + overlayInfo.category);
+ }
+ }
}
/**
@@ -184,4 +265,13 @@
}
});
}
+
+ /**
+ * @inherit
+ */
+ @Override
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println("mMainSystemColors=" + mMainSystemColors.size());
+ pw.println("mAccentColors=" + mAccentColors.size());
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 132e092..c4e2b5d 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -15,16 +15,20 @@
*/
package com.android.systemui.theme;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
+
import android.app.ActivityManager;
+import android.app.WallpaperColors;
+import android.app.WallpaperManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.om.OverlayManager;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
+import android.graphics.Color;
import android.net.Uri;
-import android.os.AsyncTask;
import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
@@ -33,20 +37,32 @@
import android.util.ArrayMap;
import android.util.Log;
-import com.android.systemui.R;
+import androidx.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.ColorUtils;
+import com.android.systemui.Dumpable;
import com.android.systemui.SystemUI;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.settings.SecureSettings;
import com.google.android.collect.Sets;
import org.json.JSONException;
import org.json.JSONObject;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.Collection;
+import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -60,49 +76,76 @@
* associated work profiles
*/
@SysUISingleton
-public class ThemeOverlayController extends SystemUI {
+public class ThemeOverlayController extends SystemUI implements Dumpable {
private static final String TAG = "ThemeOverlayController";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private ThemeOverlayManager mThemeManager;
- private UserManager mUserManager;
- private BroadcastDispatcher mBroadcastDispatcher;
+ // If lock screen wallpaper colors should also be considered when selecting the theme.
+ // Doing this has performance impact, given that overlays would need to be swapped when
+ // the device unlocks.
+ @VisibleForTesting
+ static final boolean USE_LOCK_SCREEN_WALLPAPER = false;
+
+ private final ThemeOverlayApplier mThemeManager;
+ private final UserManager mUserManager;
+ private final BroadcastDispatcher mBroadcastDispatcher;
+ private final Executor mBgExecutor;
+ private final SecureSettings mSecureSettings;
+ private final Executor mMainExecutor;
private final Handler mBgHandler;
+ private final WallpaperManager mWallpaperManager;
+ private final KeyguardStateController mKeyguardStateController;
+ private WallpaperColors mLockColors;
+ private WallpaperColors mSystemColors;
+ // Color extracted from wallpaper, NOT the color used on the overlay
+ private int mMainWallpaperColor = Color.TRANSPARENT;
+ // Color extracted from wallpaper, NOT the color used on the overlay
+ private int mWallpaperAccentColor = Color.TRANSPARENT;
+ // Main system color that maps to an overlay color
+ private int mSystemOverlayColor = Color.TRANSPARENT;
+ // Accent color that maps to an overlay color
+ private int mAccentOverlayColor = Color.TRANSPARENT;
@Inject
public ThemeOverlayController(Context context, BroadcastDispatcher broadcastDispatcher,
- @Background Handler bgHandler) {
+ @Background Handler bgHandler, @Main Executor mainExecutor,
+ @Background Executor bgExecutor, ThemeOverlayApplier themeOverlayApplier,
+ SecureSettings secureSettings, WallpaperManager wallpaperManager,
+ UserManager userManager, KeyguardStateController keyguardStateController,
+ DumpManager dumpManager) {
super(context);
+
mBroadcastDispatcher = broadcastDispatcher;
+ mUserManager = userManager;
+ mBgExecutor = bgExecutor;
+ mMainExecutor = mainExecutor;
mBgHandler = bgHandler;
+ mThemeManager = themeOverlayApplier;
+ mSecureSettings = secureSettings;
+ mWallpaperManager = wallpaperManager;
+ mKeyguardStateController = keyguardStateController;
+ dumpManager.registerDumpable(TAG, this);
}
@Override
public void start() {
if (DEBUG) Log.d(TAG, "Start");
- mUserManager = mContext.getSystemService(UserManager.class);
- mThemeManager = new ThemeOverlayManager(
- mContext.getSystemService(OverlayManager.class),
- AsyncTask.THREAD_POOL_EXECUTOR,
- mContext.getString(R.string.launcher_overlayable_package),
- mContext.getString(R.string.themepicker_overlayable_package));
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USER_SWITCHED);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
- mBroadcastDispatcher.registerReceiverWithHandler(new BroadcastReceiver() {
+ mBroadcastDispatcher.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added.");
updateThemeOverlays();
}
- }, filter, mBgHandler, UserHandle.ALL);
- mContext.getContentResolver().registerContentObserver(
+ }, filter, mBgExecutor, UserHandle.ALL);
+ mSecureSettings.registerContentObserverForUser(
Settings.Secure.getUriFor(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES),
false,
new ContentObserver(mBgHandler) {
-
@Override
- public void onChange(boolean selfChange, Collection<Uri> uris, int flags,
+ public void onChange(boolean selfChange, Collection<Uri> collection, int flags,
int userId) {
if (DEBUG) Log.d(TAG, "Overlay changed for user: " + userId);
if (ActivityManager.getCurrentUser() == userId) {
@@ -111,20 +154,147 @@
}
},
UserHandle.USER_ALL);
+
+ // Upon boot, make sure we have the most up to date colors
+ mBgExecutor.execute(() -> {
+ WallpaperColors lockColors = mWallpaperManager.getWallpaperColors(
+ WallpaperManager.FLAG_LOCK);
+ WallpaperColors systemColor = mWallpaperManager.getWallpaperColors(
+ WallpaperManager.FLAG_SYSTEM);
+ mMainExecutor.execute(() -> {
+ if (USE_LOCK_SCREEN_WALLPAPER) {
+ mLockColors = lockColors;
+ }
+ mSystemColors = systemColor;
+ reevaluateSystemTheme();
+ });
+ });
+ if (USE_LOCK_SCREEN_WALLPAPER) {
+ mKeyguardStateController.addCallback(new KeyguardStateController.Callback() {
+ @Override
+ public void onKeyguardShowingChanged() {
+ if (mLockColors == null) {
+ return;
+ }
+ // It's possible that the user has a lock screen wallpaper. On this case we'll
+ // end up with different colors after unlocking.
+ reevaluateSystemTheme();
+ }
+ });
+ }
+ mWallpaperManager.addOnColorsChangedListener((wallpaperColors, which) -> {
+ if (USE_LOCK_SCREEN_WALLPAPER && (which & WallpaperManager.FLAG_LOCK) != 0) {
+ mLockColors = wallpaperColors;
+ if (DEBUG) {
+ Log.d(TAG, "got new lock colors: " + wallpaperColors + " where: " + which);
+ }
+ }
+ if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
+ mSystemColors = wallpaperColors;
+ if (DEBUG) {
+ Log.d(TAG, "got new lock colors: " + wallpaperColors + " where: " + which);
+ }
+ }
+ reevaluateSystemTheme();
+ }, null, UserHandle.USER_ALL);
+ }
+
+ private void reevaluateSystemTheme() {
+ if (mLockColors == null && mSystemColors == null) {
+ Log.w(TAG, "Cannot update theme, colors are null");
+ return;
+ }
+ WallpaperColors currentColor =
+ mKeyguardStateController.isShowing() && mLockColors != null
+ ? mLockColors : mSystemColors;
+ int mainColor = currentColor.getPrimaryColor().toArgb();
+
+ //TODO(b/172860591) implement more complex logic for picking accent color.
+ //For now, picking the secondary should be enough.
+ Color accentCandidate = currentColor.getSecondaryColor();
+ if (accentCandidate == null) {
+ accentCandidate = currentColor.getTertiaryColor();
+ }
+ if (accentCandidate == null) {
+ accentCandidate = currentColor.getPrimaryColor();
+ }
+
+ if (mMainWallpaperColor == mainColor && mWallpaperAccentColor == accentCandidate.toArgb()) {
+ return;
+ }
+
+ mMainWallpaperColor = mainColor;
+ mWallpaperAccentColor = accentCandidate.toArgb();
+
+ // Let's compare these colors to our finite set of overlays, and then pick an overlay.
+ List<Integer> systemColors = mThemeManager.getAvailableSystemColors();
+ List<Integer> accentColors = mThemeManager.getAvailableAccentColors();
+
+ if (systemColors.size() == 0 || accentColors.size() == 0) {
+ if (DEBUG) {
+ Log.d(TAG, "Cannot apply system theme, palettes are unavailable");
+ }
+ return;
+ }
+
+ mSystemOverlayColor = getClosest(systemColors, mMainWallpaperColor);
+ mAccentOverlayColor = getClosest(accentColors, mWallpaperAccentColor);
+
+ updateThemeOverlays();
+ }
+
+ /**
+ * Given a color and a list of candidates, return the candidate that's the most similar to the
+ * given color.
+ */
+ private static int getClosest(List<Integer> candidates, int color) {
+ float[] hslMain = new float[3];
+ float[] hslCandidate = new float[3];
+
+ ColorUtils.RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), hslMain);
+ hslMain[0] /= 360f;
+
+ float minDistance = Float.MAX_VALUE;
+ int closestColor = Color.TRANSPARENT;
+ for (int candidate: candidates) {
+ ColorUtils.RGBToHSL(Color.red(candidate), Color.green(candidate), Color.blue(candidate),
+ hslCandidate);
+ hslCandidate[0] /= 360f;
+
+ float sqDistance = squared(hslCandidate[0] - hslMain[0])
+ + squared(hslCandidate[1] - hslMain[1])
+ + squared(hslCandidate[2] - hslMain[2]);
+ if (sqDistance < minDistance) {
+ minDistance = sqDistance;
+ closestColor = candidate;
+ }
+ }
+ return closestColor;
+ }
+
+ private static float squared(float f) {
+ return f * f;
}
private void updateThemeOverlays() {
final int currentUser = ActivityManager.getCurrentUser();
- final String overlayPackageJson = Settings.Secure.getStringForUser(
- mContext.getContentResolver(), Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+ final String overlayPackageJson = mSecureSettings.getStringForUser(
+ Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
currentUser);
if (DEBUG) Log.d(TAG, "updateThemeOverlays: " + overlayPackageJson);
+ boolean hasSystemPalette = false;
+ boolean hasAccentColor = false;
final Map<String, String> categoryToPackage = new ArrayMap<>();
if (!TextUtils.isEmpty(overlayPackageJson)) {
try {
JSONObject object = new JSONObject(overlayPackageJson);
- for (String category : ThemeOverlayManager.THEME_CATEGORIES) {
+ for (String category : ThemeOverlayApplier.THEME_CATEGORIES) {
if (object.has(category)) {
+ if (category.equals(OVERLAY_CATEGORY_ACCENT_COLOR)) {
+ hasAccentColor = true;
+ } else if (category.equals(OVERLAY_CATEGORY_SYSTEM_PALETTE)) {
+ hasSystemPalette = true;
+ }
categoryToPackage.put(category, object.getString(category));
}
}
@@ -132,6 +302,20 @@
Log.i(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e);
}
}
+
+ // Let's apply the system palette, but only if it was not overridden by the style picker.
+ if (!hasSystemPalette && mSystemOverlayColor != Color.TRANSPARENT) {
+ categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE,
+ ThemeOverlayApplier.MONET_SYSTEM_PALETTE_PACKAGE
+ + Integer.toHexString(mSystemOverlayColor).toUpperCase());
+ }
+ // Same for the accent color
+ if (!hasAccentColor && mAccentOverlayColor != Color.TRANSPARENT) {
+ categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR,
+ ThemeOverlayApplier.MONET_ACCENT_COLOR_PACKAGE
+ + Integer.toHexString(mAccentOverlayColor).toUpperCase());
+ }
+
Set<UserHandle> userHandles = Sets.newHashSet(UserHandle.of(currentUser));
for (UserInfo userInfo : mUserManager.getEnabledProfiles(currentUser)) {
if (userInfo.isManagedProfile()) {
@@ -140,4 +324,14 @@
}
mThemeManager.applyCurrentUserOverlays(categoryToPackage, userHandles);
}
+
+ @Override
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println("mLockColors=" + mLockColors);
+ pw.println("mSystemColors=" + mSystemColors);
+ pw.println("mMainWallpaperColor=" + Integer.toHexString(mMainWallpaperColor));
+ pw.println("mWallpaperAccentColor=" + Integer.toHexString(mWallpaperAccentColor));
+ pw.println("mSystemOverlayColor=" + Integer.toHexString(mSystemOverlayColor));
+ pw.println("mAccentOverlayColor=" + Integer.toHexString(mAccentOverlayColor));
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 7a24438..04f1f86 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -16,8 +16,6 @@
package com.android.systemui.wmshell;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
@@ -29,21 +27,14 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
-import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.app.ActivityTaskManager.RootTaskInfo;
-import android.content.ComponentName;
import android.content.Context;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.util.Log;
-import android.util.Pair;
import android.view.KeyEvent;
import com.android.internal.annotations.VisibleForTesting;
@@ -55,8 +46,6 @@
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.shared.tracing.ProtoTraceable;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -64,6 +53,7 @@
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.tracing.nano.SystemUiTraceProto;
import com.android.wm.shell.ShellDump;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.nano.WmShellTraceProto;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEvents;
@@ -105,6 +95,7 @@
private final Optional<Pip> mPipOptional;
private final Optional<SplitScreen> mSplitScreenOptional;
private final Optional<OneHanded> mOneHandedOptional;
+ private final Optional<HideDisplayCutout> mHideDisplayCutoutOptional;
private final ProtoTracer mProtoTracer;
private final Optional<ShellDump> mShellDump;
@@ -123,6 +114,7 @@
Optional<Pip> pipOptional,
Optional<SplitScreen> splitScreenOptional,
Optional<OneHanded> oneHandedOptional,
+ Optional<HideDisplayCutout> hideDisplayCutoutOptional,
ProtoTracer protoTracer,
Optional<ShellDump> shellDump) {
super(context);
@@ -135,6 +127,7 @@
mPipOptional = pipOptional;
mSplitScreenOptional = splitScreenOptional;
mOneHandedOptional = oneHandedOptional;
+ mHideDisplayCutoutOptional = hideDisplayCutoutOptional;
mProtoTracer = protoTracer;
mProtoTracer.add(this);
mShellDump = shellDump;
@@ -146,6 +139,7 @@
mPipOptional.ifPresent(this::initPip);
mSplitScreenOptional.ifPresent(this::initSplitScreen);
mOneHandedOptional.ifPresent(this::initOneHanded);
+ mHideDisplayCutoutOptional.ifPresent(this::initHideDisplayCutout);
}
@VisibleForTesting
@@ -284,6 +278,16 @@
});
}
+ @VisibleForTesting
+ void initHideDisplayCutout(HideDisplayCutout hideDisplayCutout) {
+ mConfigurationController.addCallback(new ConfigurationController.ConfigurationListener() {
+ @Override
+ public void onConfigChanged(Configuration newConfig) {
+ hideDisplayCutout.onConfigurationChanged(newConfig);
+ }
+ });
+ }
+
@Override
public void writeToProto(SystemUiTraceProto proto) {
if (proto.wmShell == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index a197789..8c2980f 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -28,7 +28,6 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.dagger.WMSingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.shared.system.InputConsumerController;
import com.android.wm.shell.ShellDump;
import com.android.wm.shell.ShellInit;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -46,6 +45,8 @@
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
@@ -90,9 +91,10 @@
static Optional<ShellDump> provideShellDump(ShellTaskOrganizer shellTaskOrganizer,
Optional<SplitScreen> splitScreenOptional,
Optional<Pip> pipOptional,
- Optional<OneHanded> oneHandedOptional) {
+ Optional<OneHanded> oneHandedOptional,
+ Optional<HideDisplayCutout> hideDisplayCutout) {
return Optional.of(new ShellDump(shellTaskOrganizer, splitScreenOptional, pipOptional,
- oneHandedOptional));
+ oneHandedOptional, hideDisplayCutout));
}
@WMSingleton
@@ -215,4 +217,10 @@
return new HandlerExecutor(handler);
}
+ @WMSingleton
+ @Provides
+ static Optional<HideDisplayCutout> provideHideDisplayCutoutController(Context context,
+ DisplayController displayController) {
+ return Optional.ofNullable(HideDisplayCutoutController.create(context, displayController));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
similarity index 85%
rename from packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayManagerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
index c99deb6..edaff5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
@@ -15,22 +15,24 @@
*/
package com.android.systemui.theme;
-import static com.android.systemui.theme.ThemeOverlayManager.ANDROID_PACKAGE;
-import static com.android.systemui.theme.ThemeOverlayManager.OVERLAY_CATEGORY_COLOR;
-import static com.android.systemui.theme.ThemeOverlayManager.OVERLAY_CATEGORY_FONT;
-import static com.android.systemui.theme.ThemeOverlayManager.OVERLAY_CATEGORY_ICON_ANDROID;
-import static com.android.systemui.theme.ThemeOverlayManager.OVERLAY_CATEGORY_ICON_LAUNCHER;
-import static com.android.systemui.theme.ThemeOverlayManager.OVERLAY_CATEGORY_ICON_SETTINGS;
-import static com.android.systemui.theme.ThemeOverlayManager.OVERLAY_CATEGORY_ICON_SYSUI;
-import static com.android.systemui.theme.ThemeOverlayManager.OVERLAY_CATEGORY_ICON_THEME_PICKER;
-import static com.android.systemui.theme.ThemeOverlayManager.OVERLAY_CATEGORY_SHAPE;
-import static com.android.systemui.theme.ThemeOverlayManager.SETTINGS_PACKAGE;
-import static com.android.systemui.theme.ThemeOverlayManager.SYSTEM_USER_CATEGORIES;
-import static com.android.systemui.theme.ThemeOverlayManager.SYSUI_PACKAGE;
-import static com.android.systemui.theme.ThemeOverlayManager.THEME_CATEGORIES;
+import static com.android.systemui.theme.ThemeOverlayApplier.ANDROID_PACKAGE;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_FONT;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_ANDROID;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_LAUNCHER;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_SETTINGS;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_SYSUI;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_THEME_PICKER;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SHAPE;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
+import static com.android.systemui.theme.ThemeOverlayApplier.SETTINGS_PACKAGE;
+import static com.android.systemui.theme.ThemeOverlayApplier.SYSTEM_USER_CATEGORIES;
+import static com.android.systemui.theme.ThemeOverlayApplier.SYSUI_PACKAGE;
+import static com.android.systemui.theme.ThemeOverlayApplier.THEME_CATEGORIES;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -44,6 +46,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import com.google.android.collect.Maps;
import com.google.common.collect.Lists;
@@ -63,7 +66,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class ThemeOverlayManagerTest extends SysuiTestCase {
+public class ThemeOverlayApplierTest extends SysuiTestCase {
private static final String TEST_DISABLED_PREFIX = "com.example.";
private static final String TEST_ENABLED_PREFIX = "com.example.enabled.";
@@ -82,26 +85,32 @@
@Mock
OverlayManager mOverlayManager;
+ @Mock
+ DumpManager mDumpManager;
- private ThemeOverlayManager mManager;
+ private ThemeOverlayApplier mManager;
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
- mManager = new ThemeOverlayManager(mOverlayManager, MoreExecutors.directExecutor(),
- LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE);
+ mManager = new ThemeOverlayApplier(mOverlayManager, MoreExecutors.directExecutor(),
+ LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE, mDumpManager);
when(mOverlayManager.getOverlayInfosForTarget(ANDROID_PACKAGE, UserHandle.SYSTEM))
.thenReturn(Lists.newArrayList(
- createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_COLOR,
- ANDROID_PACKAGE, OVERLAY_CATEGORY_COLOR, false),
+ createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_ACCENT_COLOR,
+ ANDROID_PACKAGE, OVERLAY_CATEGORY_ACCENT_COLOR, false),
+ createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_SYSTEM_PALETTE,
+ ANDROID_PACKAGE, OVERLAY_CATEGORY_SYSTEM_PALETTE, false),
createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_FONT,
ANDROID_PACKAGE, OVERLAY_CATEGORY_FONT, false),
createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_SHAPE,
ANDROID_PACKAGE, OVERLAY_CATEGORY_SHAPE, false),
createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_ICON_ANDROID,
ANDROID_PACKAGE, OVERLAY_CATEGORY_ICON_ANDROID, false),
- createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_COLOR,
- ANDROID_PACKAGE, OVERLAY_CATEGORY_COLOR, true),
+ createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ACCENT_COLOR,
+ ANDROID_PACKAGE, OVERLAY_CATEGORY_ACCENT_COLOR, true),
+ createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_SYSTEM_PALETTE,
+ ANDROID_PACKAGE, OVERLAY_CATEGORY_SYSTEM_PALETTE, true),
createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_FONT,
ANDROID_PACKAGE, OVERLAY_CATEGORY_FONT, true),
createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_SHAPE,
@@ -132,6 +141,8 @@
THEMEPICKER_PACKAGE, OVERLAY_CATEGORY_ICON_THEME_PICKER, false),
createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_THEME_PICKER,
THEMEPICKER_PACKAGE, OVERLAY_CATEGORY_ICON_THEME_PICKER, true)));
+ clearInvocations(mOverlayManager);
+ verify(mDumpManager).registerDumpable(any(), any());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
new file mode 100644
index 0000000..aee8840
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -0,0 +1,173 @@
+/*
+ * 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.theme;
+
+import static com.android.systemui.theme.ThemeOverlayApplier.MONET_ACCENT_COLOR_PACKAGE;
+import static com.android.systemui.theme.ThemeOverlayApplier.MONET_SYSTEM_PALETTE_PACKAGE;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
+import static com.android.systemui.theme.ThemeOverlayController.USE_LOCK_SCREEN_WALLPAPER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.WallpaperColors;
+import android.app.WallpaperManager;
+import android.graphics.Color;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.settings.SecureSettings;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ThemeOverlayControllerTest extends SysuiTestCase {
+
+ private ThemeOverlayController mThemeOverlayController;
+ @Mock
+ private Executor mBgExecutor;
+ @Mock
+ private Executor mMainExecutor;
+ @Mock
+ private BroadcastDispatcher mBroadcastDispatcher;
+ @Mock
+ private Handler mBgHandler;
+ @Mock
+ private ThemeOverlayApplier mThemeOverlayApplier;
+ @Mock
+ private SecureSettings mSecureSettings;
+ @Mock
+ private WallpaperManager mWallpaperManager;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private KeyguardStateController mKeyguardStateController;
+ @Mock
+ private DumpManager mDumpManager;
+ @Captor
+ private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallback;
+ @Captor
+ private ArgumentCaptor<WallpaperManager.OnColorsChangedListener> mColorsListener;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mThemeOverlayController = new ThemeOverlayController(null /* context */,
+ mBroadcastDispatcher, mBgHandler, mMainExecutor, mBgExecutor, mThemeOverlayApplier,
+ mSecureSettings, mWallpaperManager, mUserManager, mKeyguardStateController,
+ mDumpManager);
+
+ mThemeOverlayController.start();
+ if (USE_LOCK_SCREEN_WALLPAPER) {
+ verify(mKeyguardStateController).addCallback(
+ mKeyguardStateControllerCallback.capture());
+ }
+ verify(mWallpaperManager).addOnColorsChangedListener(mColorsListener.capture(), eq(null),
+ eq(UserHandle.USER_ALL));
+ verify(mDumpManager).registerDumpable(any(), any());
+
+ List<Integer> colorList = List.of(Color.RED, Color.BLUE);
+ when(mThemeOverlayApplier.getAvailableAccentColors()).thenReturn(colorList);
+ when(mThemeOverlayApplier.getAvailableSystemColors()).thenReturn(colorList);
+ }
+
+ @Test
+ public void start_checksWallpaper() {
+ ArgumentCaptor<Runnable> registrationRunnable = ArgumentCaptor.forClass(Runnable.class);
+ verify(mBgExecutor).execute(registrationRunnable.capture());
+
+ registrationRunnable.getValue().run();
+ verify(mWallpaperManager).getWallpaperColors(eq(WallpaperManager.FLAG_LOCK));
+ verify(mWallpaperManager).getWallpaperColors(eq(WallpaperManager.FLAG_SYSTEM));
+ }
+
+ @Test
+ public void onWallpaperColorsChanged_setsTheme() {
+ // Should ask for a new theme when wallpaper colors change
+ WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+ Color.valueOf(Color.BLUE), null);
+ mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+ ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
+
+ verify(mThemeOverlayApplier).getAvailableSystemColors();
+ verify(mThemeOverlayApplier).getAvailableAccentColors();
+ verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
+
+ // Assert that we received the colors that we were expecting
+ assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
+ .isEqualTo(MONET_SYSTEM_PALETTE_PACKAGE
+ + Integer.toHexString(Color.RED).toUpperCase());
+ assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR))
+ .isEqualTo(MONET_ACCENT_COLOR_PACKAGE
+ + Integer.toHexString(Color.BLUE).toUpperCase());
+
+ // Should not ask again if changed to same value
+ mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+ verifyNoMoreInteractions(mThemeOverlayApplier);
+ }
+
+ @Test
+ public void onWallpaperColorsChanged_preservesWallpaperPickerTheme() {
+ // Should ask for a new theme when wallpaper colors change
+ WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+ Color.valueOf(Color.BLUE), null);
+
+ String jsonString =
+ "{\"android.theme.customization.system_palette\":\"override.package.name\"}";
+ when(mSecureSettings.getStringForUser(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+ .thenReturn(jsonString);
+
+ mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+ ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
+
+ verify(mThemeOverlayApplier).getAvailableSystemColors();
+ verify(mThemeOverlayApplier).getAvailableAccentColors();
+ verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
+
+ // Assert that we received the colors that we were expecting
+ assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
+ .isEqualTo("override.package.name");
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index a46e563..230aeab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -45,9 +45,6 @@
import android.app.Notification;
import android.app.PendingIntent;
import android.content.pm.LauncherApps;
-import android.content.res.Configuration;
-import android.graphics.Insets;
-import android.graphics.Rect;
import android.hardware.display.AmbientDisplayConfiguration;
import android.hardware.face.FaceManager;
import android.os.Handler;
@@ -99,7 +96,7 @@
import com.android.wm.shell.bubbles.BubbleDataRepository;
import com.android.wm.shell.bubbles.BubbleEntry;
import com.android.wm.shell.bubbles.BubbleLogger;
-import com.android.wm.shell.bubbles.BubblePositioner;
+import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubbleStackView;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.common.FloatingContentCoordinator;
@@ -206,8 +203,8 @@
private WindowManagerShellWrapper mWindowManagerShellWrapper;
@Mock
private BubbleLogger mBubbleLogger;
- @Mock
- private BubblePositioner mPositioner;
+
+ private TestableBubblePositioner mPositioner;
private BubbleData mBubbleData;
@@ -255,12 +252,8 @@
mSysUiStateBubblesExpanded =
(sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0);
- mBubbleData = new BubbleData(mContext, mBubbleLogger);
-
- Rect availableRect = new Rect(0, 0, 1000, 5000);
- when(mPositioner.getAvailableRect()).thenReturn(availableRect);
- when(mPositioner.getOrientation()).thenReturn(Configuration.ORIENTATION_PORTRAIT);
- when(mPositioner.getInsets()).thenReturn(Insets.of(0, 0, 0, 0));
+ mPositioner = new TestableBubblePositioner(mContext, mWindowManager);
+ mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner);
TestableNotificationInterruptStateProviderImpl interruptionStateProvider =
new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
@@ -585,7 +578,7 @@
}
@Test
- public void testRemoveLastExpandedCollapses() {
+ public void testRemoveLastExpanded_selectsOverflow() {
// Mark it as a bubble and add it explicitly
mEntryListener.onPendingEntryAdded(mRow.getEntry());
mEntryListener.onPendingEntryAdded(mRow2.getEntry());
@@ -625,10 +618,38 @@
stackView.getExpandedBubble().getKey()).getKey(),
Bubbles.DISMISS_USER_GESTURE);
- // Make sure state changes and collapse happens
+ // Overflow should be selected
+ assertEquals(mBubbleData.getSelectedBubble().getKey(), BubbleOverflow.KEY);
+ verify(mBubbleExpandListener).onBubbleExpandChanged(true, BubbleOverflow.KEY);
+ assertTrue(mBubbleController.hasBubbles());
+ assertTrue(mSysUiStateBubblesExpanded);
+ }
+
+ @Test
+ public void testRemoveLastExpandedEmptyOverflow_collapses() {
+ // Mark it as a bubble and add it explicitly
+ mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mBubbleController.updateBubble(mBubbleEntry);
+
+ // Expand
+ BubbleStackView stackView = mBubbleController.getStackView();
+ mBubbleData.setExpanded(true);
+
+ assertTrue(mSysUiStateBubblesExpanded);
+ assertTrue(mBubbleController.isStackExpanded());
+ verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
+
+ // Block the bubble so it won't be in the overflow
+ mBubbleController.removeBubble(
+ mBubbleData.getBubbleInStackWithKey(
+ stackView.getExpandedBubble().getKey()).getKey(),
+ Bubbles.DISMISS_BLOCKED);
+
+ verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey());
+
+ // We should be collapsed
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey());
assertFalse(mBubbleController.hasBubbles());
-
assertFalse(mSysUiStateBubblesExpanded);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
index d8033db..bbcc30a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -42,9 +42,6 @@
import android.app.Notification;
import android.app.PendingIntent;
import android.content.pm.LauncherApps;
-import android.content.res.Configuration;
-import android.graphics.Insets;
-import android.graphics.Rect;
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Handler;
import android.os.PowerManager;
@@ -92,7 +89,7 @@
import com.android.wm.shell.bubbles.BubbleDataRepository;
import com.android.wm.shell.bubbles.BubbleEntry;
import com.android.wm.shell.bubbles.BubbleLogger;
-import com.android.wm.shell.bubbles.BubblePositioner;
+import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubbleStackView;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.common.FloatingContentCoordinator;
@@ -188,8 +185,8 @@
private WindowManagerShellWrapper mWindowManagerShellWrapper;
@Mock
private BubbleLogger mBubbleLogger;
- @Mock
- private BubblePositioner mPositioner;
+
+ private TestableBubblePositioner mPositioner;
private BubbleData mBubbleData;
@@ -224,12 +221,8 @@
mZenModeConfig.suppressedVisualEffects = 0;
when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
- mBubbleData = new BubbleData(mContext, mBubbleLogger);
-
- Rect availableRect = new Rect(0, 0, 1000, 5000);
- when(mPositioner.getAvailableRect()).thenReturn(availableRect);
- when(mPositioner.getOrientation()).thenReturn(Configuration.ORIENTATION_PORTRAIT);
- when(mPositioner.getInsets()).thenReturn(Insets.of(0, 0, 0, 0));
+ mPositioner = new TestableBubblePositioner(mContext, mWindowManager);
+ mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner);
TestableNotificationInterruptStateProviderImpl interruptionStateProvider =
new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
@@ -516,7 +509,7 @@
}
@Test
- public void testRemoveLastExpandedCollapses() {
+ public void testRemoveLastExpanded_selectsOverflow() {
// Mark it as a bubble and add it explicitly
mEntryListener.onEntryAdded(mRow.getEntry());
mEntryListener.onEntryAdded(mRow2.getEntry());
@@ -554,11 +547,39 @@
stackView.getExpandedBubble().getKey()).getKey(),
Bubbles.DISMISS_USER_GESTURE);
- // Make sure state changes and collapse happens
+ // Overflow should be selected
+ assertEquals(mBubbleData.getSelectedBubble().getKey(), BubbleOverflow.KEY);
+ verify(mBubbleExpandListener).onBubbleExpandChanged(true, BubbleOverflow.KEY);
+ assertTrue(mBubbleController.hasBubbles());
+ }
+
+ @Test
+ public void testRemoveLastExpandedEmptyOverflow_collapses() {
+ // Mark it as a bubble and add it explicitly
+ mEntryListener.onEntryAdded(mRow.getEntry());
+ mBubbleController.updateBubble(mBubbleEntry);
+
+ // Expand
+ BubbleStackView stackView = mBubbleController.getStackView();
+ mBubbleData.setExpanded(true);
+
+ assertTrue(mBubbleController.isStackExpanded());
+ verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
+
+ // Block the bubble so it won't be in the overflow
+ mBubbleController.removeBubble(
+ mBubbleData.getBubbleInStackWithKey(
+ stackView.getExpandedBubble().getKey()).getKey(),
+ Bubbles.DISMISS_BLOCKED);
+
+ verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey());
+
+ // We should be collapsed
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().getKey());
assertFalse(mBubbleController.hasBubbles());
}
+
@Test
public void testAutoExpand_fails_noFlag() {
assertFalse(mBubbleController.isStackExpanded());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubblePositioner.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubblePositioner.java
new file mode 100644
index 0000000..24a7cd5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubblePositioner.java
@@ -0,0 +1,37 @@
+/*
+ * 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.wmshell;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.view.WindowManager;
+
+import com.android.wm.shell.bubbles.BubblePositioner;
+
+public class TestableBubblePositioner extends BubblePositioner {
+
+ public TestableBubblePositioner(Context context,
+ WindowManager windowManager) {
+ super(context, windowManager);
+
+ updateInternal(Configuration.ORIENTATION_PORTRAIT,
+ Insets.of(0, 0, 0, 0),
+ new Rect(0, 0, 500, 1000));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 6a303a9..34889ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -20,7 +20,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.content.pm.PackageManager;
import android.test.suitebuilder.annotation.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -31,13 +30,11 @@
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.shared.system.InputConsumerController;
-import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.wm.shell.ShellDump;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedGestureHandler;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
@@ -56,13 +53,11 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class WMShellTest extends SysuiTestCase {
- InputConsumerController mInputConsumerController;
WMShell mWMShell;
@Mock CommandQueue mCommandQueue;
@Mock ConfigurationController mConfigurationController;
@Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock InputConsumerController mMockInputConsumerController;
@Mock NavigationModeController mNavigationModeController;
@Mock ScreenLifecycle mScreenLifecycle;
@Mock SysUiState mSysUiState;
@@ -70,6 +65,7 @@
@Mock PipTouchHandler mPipTouchHandler;
@Mock SplitScreen mSplitScreen;
@Mock OneHanded mOneHanded;
+ @Mock HideDisplayCutout mHideDisplayCutout;
@Mock ProtoTracer mProtoTracer;
@Mock ShellDump mShellDump;
@@ -80,7 +76,8 @@
mWMShell = new WMShell(mContext, mCommandQueue, mConfigurationController,
mKeyguardUpdateMonitor, mNavigationModeController,
mScreenLifecycle, mSysUiState, Optional.of(mPip), Optional.of(mSplitScreen),
- Optional.of(mOneHanded), mProtoTracer, Optional.of(mShellDump));
+ Optional.of(mOneHanded), Optional.of(mHideDisplayCutout), mProtoTracer,
+ Optional.of(mShellDump));
when(mPip.getPipTouchHandler()).thenReturn(mPipTouchHandler);
}
@@ -113,4 +110,12 @@
OneHandedGestureHandler.OneHandedGestureEventCallback.class));
verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback.class));
}
+
+ @Test
+ public void initHideDisplayCutout_registersCallbacks() {
+ mWMShell.initHideDisplayCutout(mHideDisplayCutout);
+
+ verify(mConfigurationController).addCallback(
+ any(ConfigurationController.ConfigurationListener.class));
+ }
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index fb3c61b..e9edebd 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -211,6 +211,7 @@
":connectivity-service-srcs",
],
installable: true,
+ jarjar_rules: "jarjar-rules.txt",
libs: [
"android.net.ipsec.ike",
"services.core",
diff --git a/services/core/jarjar-rules.txt b/services/core/jarjar-rules.txt
new file mode 100644
index 0000000..58f7bcb
--- /dev/null
+++ b/services/core/jarjar-rules.txt
@@ -0,0 +1,2 @@
+# For ConnectivityService module
+rule com.android.modules.utils.** com.android.connectivity.utils.@1
\ No newline at end of file
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index d586f00..1eba37d 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -137,7 +137,6 @@
import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker;
import android.net.util.NetdService;
-import android.os.BasicShellCommandHandler;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -190,6 +189,7 @@
import com.android.internal.util.LocationPermissionChecker;
import com.android.internal.util.MessageUtils;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.BasicShellCommandHandler;
import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult;
import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
import com.android.server.am.BatteryStatsService;
diff --git a/services/core/java/com/android/server/adb/AdbShellCommand.java b/services/core/java/com/android/server/adb/AdbShellCommand.java
index 7691852..d7e95df 100644
--- a/services/core/java/com/android/server/adb/AdbShellCommand.java
+++ b/services/core/java/com/android/server/adb/AdbShellCommand.java
@@ -16,7 +16,7 @@
package com.android.server.adb;
-import android.os.BasicShellCommandHandler;
+import com.android.modules.utils.BasicShellCommandHandler;
import java.io.PrintWriter;
import java.util.Objects;
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index d52cf02..eeec1bb 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -333,7 +333,7 @@
// initialization from here. AIDL HALs are initialized by FingerprintService since
// the HAL interface provides ID, strength, and other configuration information.
fingerprintService.initializeConfiguration(config.id, config.strength);
- authenticator = new FingerprintAuthenticator(fingerprintService, config);
+ authenticator = new FingerprintAuthenticator(fingerprintService, config.id);
break;
case TYPE_FACE:
@@ -348,7 +348,7 @@
// initialization from here. AIDL HALs are initialized by FaceService since
// the HAL interface provides ID, strength, and other configuration information.
faceService.initializeConfiguration(config.id, config.strength);
- authenticator = new FaceAuthenticator(faceService, config);
+ authenticator = new FaceAuthenticator(faceService, config.id);
break;
case TYPE_IRIS:
@@ -359,7 +359,8 @@
return;
}
- authenticator = new IrisAuthenticator(irisService, config);
+ irisService.initializeConfiguration(config.id, config.strength);
+ authenticator = new IrisAuthenticator(irisService, config.id);
break;
default:
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 3e0a40f..29424b4 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -594,20 +594,6 @@
}
}
- // This happens infrequently enough, not worth caching.
- final String[] configs = mInjector.getConfiguration(getContext());
- boolean idFound = false;
- for (int i = 0; i < configs.length; i++) {
- SensorConfig config = new SensorConfig(configs[i]);
- if (config.id == id) {
- idFound = true;
- break;
- }
- }
- if (!idFound) {
- throw new IllegalStateException("Cannot register unknown id");
- }
-
mSensors.add(new BiometricSensor(id, modality, strength, authenticator) {
@Override
boolean confirmationAlwaysRequired(int userId) {
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 88804e2..6741942 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -453,7 +453,7 @@
* {@link SensorPropertiesInternal} strength.
*/
public static @SensorProperties.Strength int authenticatorStrengthToPropertyStrength(
- @BiometricManager.Authenticators.Types int strength) {
+ @Authenticators.Types int strength) {
switch (strength) {
case BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE:
return SensorProperties.STRENGTH_CONVENIENCE;
@@ -465,4 +465,18 @@
throw new IllegalArgumentException("Unknown strength: " + strength);
}
}
+
+ public static @Authenticators.Types int propertyStrengthToAuthenticatorStrength(
+ @SensorProperties.Strength int strength) {
+ switch (strength) {
+ case SensorProperties.STRENGTH_CONVENIENCE:
+ return Authenticators.BIOMETRIC_CONVENIENCE;
+ case SensorProperties.STRENGTH_WEAK:
+ return Authenticators.BIOMETRIC_WEAK;
+ case SensorProperties.STRENGTH_STRONG:
+ return Authenticators.BIOMETRIC_STRONG;
+ default:
+ throw new IllegalArgumentException("Unknown strength: " + strength);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceCallback.java b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceCallback.java
new file mode 100644
index 0000000..2ae6ccd
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceCallback.java
@@ -0,0 +1,28 @@
+/*
+ * 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.biometrics.sensors;
+
+/**
+ * System_server services that require BiometricService to load before finishing initialization
+ * should implement this interface.
+ */
+public interface BiometricServiceCallback {
+ /**
+ * Notifies the service that BiometricService is initialized.
+ */
+ void onBiometricServiceReady();
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
index e742d10..b3e6cad 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
@@ -32,10 +32,10 @@
private final IFaceService mFaceService;
private final int mSensorId;
- public FaceAuthenticator(IFaceService faceService, SensorConfig config)
+ public FaceAuthenticator(IFaceService faceService, int sensorId)
throws RemoteException {
mFaceService = faceService;
- mSensorId = config.id;
+ mSensorId = sensorId;
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 65e57f1..1e0764a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -26,6 +27,7 @@
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IBiometricSensorReceiver;
+import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.SensorProps;
@@ -54,7 +56,9 @@
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.BiometricServiceCallback;
import com.android.server.biometrics.sensors.face.aidl.FaceProvider;
+import com.android.server.biometrics.sensors.face.hidl.Face10;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -67,10 +71,11 @@
* The service is responsible for maintaining a list of clients and dispatching all
* face-related events.
*/
-public class FaceService extends SystemService {
+public class FaceService extends SystemService implements BiometricServiceCallback {
protected static final String TAG = "FaceService";
+ private final FaceServiceWrapper mServiceWrapper;
private final LockoutResetDispatcher mLockoutResetDispatcher;
private final LockPatternUtils mLockPatternUtils;
@NonNull
@@ -506,21 +511,23 @@
@BiometricManager.Authenticators.Types int strength) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
mServiceProviders.add(
- new com.android.server.biometrics.sensors.face.hidl.Face10(getContext(),
- sensorId, strength, mLockoutResetDispatcher));
+ new Face10(getContext(), sensorId, strength, mLockoutResetDispatcher));
}
}
public FaceService(Context context) {
super(context);
+ mServiceWrapper = new FaceServiceWrapper();
mLockoutResetDispatcher = new LockoutResetDispatcher(context);
mLockPatternUtils = new LockPatternUtils(context);
mServiceProviders = new ArrayList<>();
-
- initializeAidlHals();
}
- private void initializeAidlHals() {
+ @Override
+ public void onBiometricServiceReady() {
+ final IBiometricService biometricService = IBiometricService.Stub.asInterface(
+ ServiceManager.getService(Context.BIOMETRIC_SERVICE));
+
final String[] instances = ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
if (instances == null || instances.length == 0) {
return;
@@ -543,6 +550,23 @@
final FaceProvider provider = new FaceProvider(getContext(), props, instance,
mLockoutResetDispatcher);
mServiceProviders.add(provider);
+
+ // Register each sensor individually with BiometricService
+ for (SensorProps prop : props) {
+ final int sensorId = prop.commonProps.sensorId;
+ @BiometricManager.Authenticators.Types int strength =
+ Utils.propertyStrengthToAuthenticatorStrength(
+ prop.commonProps.sensorStrength);
+ final FaceAuthenticator authenticator =
+ new FaceAuthenticator(mServiceWrapper, sensorId);
+ try {
+ biometricService.registerAuthenticator(sensorId, TYPE_FACE, strength,
+ authenticator);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when registering sensorId: "
+ + sensorId);
+ }
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when initializing instance: " + fqName);
}
@@ -552,7 +576,7 @@
@Override
public void onStart() {
- publishBinderService(Context.FACE_SERVICE, new FaceServiceWrapper());
+ publishBinderService(Context.FACE_SERVICE, mServiceWrapper);
}
private native NativeHandle convertSurfaceToNativeHandle(Surface surface);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
index f77bc79..9f9d9cc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
@@ -32,10 +32,10 @@
private final IFingerprintService mFingerprintService;
private final int mSensorId;
- public FingerprintAuthenticator(IFingerprintService fingerprintService, SensorConfig config)
+ public FingerprintAuthenticator(IFingerprintService fingerprintService, int sensorId)
throws RemoteException {
mFingerprintService = fingerprintService;
- mSensorId = config.id;
+ mSensorId = sensorId;
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 99569b1..b84d095 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -24,14 +24,17 @@
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.Manifest.permission.USE_FINGERPRINT;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IBiometricSensorReceiver;
+import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.fingerprint.IFingerprint;
@@ -65,6 +68,7 @@
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.BiometricServiceCallback;
import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
import com.android.server.biometrics.sensors.fingerprint.hidl.Fingerprint21;
import com.android.server.biometrics.sensors.fingerprint.hidl.Fingerprint21UdfpsMock;
@@ -80,7 +84,7 @@
* The service is responsible for maintaining a list of clients and dispatching all
* fingerprint-related events.
*/
-public class FingerprintService extends SystemService {
+public class FingerprintService extends SystemService implements BiometricServiceCallback {
protected static final String TAG = "FingerprintService";
@@ -88,6 +92,7 @@
private final LockoutResetDispatcher mLockoutResetDispatcher;
private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
private final LockPatternUtils mLockPatternUtils;
+ private final FingerprintServiceWrapper mServiceWrapper;
@NonNull private List<ServiceProvider> mServiceProviders;
/**
@@ -572,7 +577,8 @@
}
@Override // Binder call
- public void initializeConfiguration(int sensorId, int strength) {
+ public void initializeConfiguration(int sensorId,
+ @BiometricManager.Authenticators.Types int strength) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
final Fingerprint21 fingerprint21;
@@ -626,16 +632,19 @@
public FingerprintService(Context context) {
super(context);
+ mServiceWrapper = new FingerprintServiceWrapper();
mAppOps = context.getSystemService(AppOpsManager.class);
mGestureAvailabilityDispatcher = new GestureAvailabilityDispatcher();
mLockoutResetDispatcher = new LockoutResetDispatcher(context);
mLockPatternUtils = new LockPatternUtils(context);
mServiceProviders = new ArrayList<>();
-
- initializeAidlHals();
}
- private void initializeAidlHals() {
+ @Override
+ public void onBiometricServiceReady() {
+ final IBiometricService biometricService = IBiometricService.Stub.asInterface(
+ ServiceManager.getService(Context.BIOMETRIC_SERVICE));
+
final String[] instances = ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR);
if (instances == null || instances.length == 0) {
return;
@@ -659,6 +668,23 @@
new FingerprintProvider(getContext(), props, instance,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
mServiceProviders.add(provider);
+
+ // Register each sensor individually with BiometricService
+ for (SensorProps prop : props) {
+ final int sensorId = prop.commonProps.sensorId;
+ @BiometricManager.Authenticators.Types int strength =
+ Utils.propertyStrengthToAuthenticatorStrength(
+ prop.commonProps.sensorStrength);
+ final FingerprintAuthenticator authenticator =
+ new FingerprintAuthenticator(mServiceWrapper, sensorId);
+ try {
+ biometricService.registerAuthenticator(sensorId,
+ TYPE_FINGERPRINT, strength, authenticator);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when registering sensorId: "
+ + sensorId);
+ }
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when initializing instance: " + fqName);
}
@@ -668,7 +694,7 @@
@Override
public void onStart() {
- publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper());
+ publishBinderService(Context.FINGERPRINT_SERVICE, mServiceWrapper);
}
@Nullable
diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
index 0400ef5..b756d8e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
@@ -31,10 +31,8 @@
public final class IrisAuthenticator extends IBiometricAuthenticator.Stub {
private final IIrisService mIrisService;
- public IrisAuthenticator(IIrisService irisService, SensorConfig config) throws
- RemoteException {
+ public IrisAuthenticator(IIrisService irisService, int sensorId) throws RemoteException {
mIrisService = irisService;
- mIrisService.initializeConfiguration(config.id);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
index bcf63dc..08b2489 100644
--- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
@@ -42,7 +42,7 @@
*/
private final class IrisServiceWrapper extends IIrisService.Stub {
@Override // Binder call
- public void initializeConfiguration(int sensorId) {
+ public void initializeConfiguration(int sensorId, int strength) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
}
}
diff --git a/services/core/java/com/android/server/location/LocationShellCommand.java b/services/core/java/com/android/server/location/LocationShellCommand.java
index 909873f..3464704 100644
--- a/services/core/java/com/android/server/location/LocationShellCommand.java
+++ b/services/core/java/com/android/server/location/LocationShellCommand.java
@@ -16,9 +16,10 @@
package com.android.server.location;
-import android.os.BasicShellCommandHandler;
import android.os.UserHandle;
+import com.android.modules.utils.BasicShellCommandHandler;
+
import java.io.PrintWriter;
import java.util.Objects;
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index bf5a50e..7a37bdd 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -820,7 +820,7 @@
CONTACTS_PERMISSIONS, STORAGE_PERMISSIONS);
}
- // Atthention Service
+ // Attention Service
String attentionServicePackageName =
mContext.getPackageManager().getAttentionServicePackageName();
if (!TextUtils.isEmpty(attentionServicePackageName)) {
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
index be18d0a..76a5cda 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
@@ -22,8 +22,11 @@
import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
import static android.window.DisplayAreaOrganizer.FEATURE_FULLSCREEN_MAGNIFICATION;
+import static android.window.DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT;
import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED;
import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION;
@@ -99,6 +102,12 @@
// layer
.setNewDisplayAreaSupplier(DisplayArea.Dimmable::new)
.build())
+ .addFeature(new Feature.Builder(wmService.mPolicy, "HideDisplayCutout",
+ FEATURE_HIDE_DISPLAY_CUTOUT)
+ .all()
+ .except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL, TYPE_STATUS_BAR,
+ TYPE_NOTIFICATION_SHADE)
+ .build())
.addFeature(new Feature.Builder(wmService.mPolicy, "OneHanded",
FEATURE_ONE_HANDED)
.all()
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 9011ba0..61cc430 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2969,10 +2969,6 @@
mDisplayContent.getInsetsPolicy().updateBarControlTarget(win);
- final int fullscreenAppearance = updateLightStatusBarLw(0 /* appearance */,
- mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState);
- final int dockedAppearance = updateLightStatusBarLw(0 /* appearance */,
- mTopDockedOpaqueWindowState, mTopDockedOpaqueOrDimmingWindowState);
final boolean inSplitScreen =
mService.mRoot.getDefaultTaskDisplayArea().isSplitScreenModeActivated();
if (inSplitScreen) {
@@ -2984,6 +2980,12 @@
mService.getStackBounds(inSplitScreen ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
: WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_UNDEFINED, mNonDockedStackBounds);
+ final int fullscreenAppearance = updateLightStatusBarLw(0 /* appearance */,
+ mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState,
+ mNonDockedStackBounds);
+ final int dockedAppearance = updateLightStatusBarLw(0 /* appearance */,
+ mTopDockedOpaqueWindowState, mTopDockedOpaqueOrDimmingWindowState,
+ mDockedStackBounds);
final int disableFlags = win.getSystemUiVisibility() & StatusBarManager.DISABLE_MASK;
final int opaqueAppearance = updateSystemBarsLw(win, disableFlags);
final WindowState navColorWin = chooseNavigationColorWindowLw(
@@ -3049,10 +3051,14 @@
}
private int updateLightStatusBarLw(@Appearance int appearance, WindowState opaque,
- WindowState opaqueOrDimming) {
+ WindowState opaqueOrDimming, Rect stackBounds) {
+ final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
+ final int statusBarHeight = mStatusBarHeightForRotation[displayRotation.getRotation()];
+ final boolean stackBoundsContainStatusBar =
+ stackBounds.isEmpty() ? false : stackBounds.top < statusBarHeight;
final boolean onKeyguard = isKeyguardShowing() && !isKeyguardOccluded();
final WindowState statusColorWin = onKeyguard ? mNotificationShade : opaqueOrDimming;
- if (statusColorWin != null) {
+ if (stackBoundsContainStatusBar && statusColorWin != null) {
if (statusColorWin == opaque || onKeyguard) {
// If the top fullscreen-or-dimming window is also the top fullscreen, respect
// its light flag.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index eff222a..ce61d50 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -15,8 +15,10 @@
*/
package com.android.server.devicepolicy;
+import android.app.admin.DevicePolicySafetyChecker;
import android.app.admin.IDevicePolicyManager;
import android.content.ComponentName;
+import android.util.Slog;
import com.android.server.SystemService;
@@ -30,6 +32,9 @@
* should be added here to avoid build breakage in downstream branches.
*/
abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub {
+
+ private static final String TAG = BaseIDevicePolicyManager.class.getSimpleName();
+
/**
* To be called by {@link DevicePolicyManagerService#Lifecycle} during the various boot phases.
*
@@ -55,6 +60,16 @@
*/
abstract void handleStopUser(int userId);
+ /**
+ * Sets the {@link DevicePolicySafetyChecker}.
+ *
+ * <p>Currently, it's called only by {@code SystemServer} on
+ * {@link android.content.pm.PackageManager#FEATURE_AUTOMOTIVE automotive builds}
+ */
+ public void setDevicePolicySafetyChecker(DevicePolicySafetyChecker safetyChecker) {
+ Slog.w(TAG, "setDevicePolicySafetyChecker() not implemented by " + getClass());
+ }
+
public void clearSystemUpdatePolicyFreezePeriodRecord() {
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index a1167e9..f2c9718 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -135,9 +135,11 @@
import android.app.admin.DevicePolicyCache;
import android.app.admin.DevicePolicyEventLogger;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.app.admin.DevicePolicyManager.PersonalAppsSuspensionReason;
import android.app.admin.DevicePolicyManagerInternal;
+import android.app.admin.DevicePolicySafetyChecker;
import android.app.admin.DeviceStateCache;
import android.app.admin.FactoryResetProtectionPolicy;
import android.app.admin.NetworkEvent;
@@ -148,6 +150,7 @@
import android.app.admin.StartInstallingUpdateCallback;
import android.app.admin.SystemUpdateInfo;
import android.app.admin.SystemUpdatePolicy;
+import android.app.admin.UnsafeStateException;
import android.app.backup.IBackupManager;
import android.app.trust.TrustManager;
import android.app.usage.UsageStatsManagerInternal;
@@ -634,6 +637,9 @@
@VisibleForTesting
final TransferOwnershipMetadataManager mTransferOwnershipMetadataManager;
+ @Nullable
+ private DevicePolicySafetyChecker mSafetyChecker;
+
public static final class Lifecycle extends SystemService {
private BaseIDevicePolicyManager mService;
@@ -645,8 +651,8 @@
dpmsClassName = DevicePolicyManagerService.class.getName();
}
try {
- Class serviceClass = Class.forName(dpmsClassName);
- Constructor constructor = serviceClass.getConstructor(Context.class);
+ Class<?> serviceClass = Class.forName(dpmsClassName);
+ Constructor<?> constructor = serviceClass.getConstructor(Context.class);
mService = (BaseIDevicePolicyManager) constructor.newInstance(context);
} catch (Exception e) {
throw new IllegalStateException(
@@ -655,6 +661,11 @@
}
}
+ /** Sets the {@link DevicePolicySafetyChecker}. */
+ public void setDevicePolicySafetyChecker(DevicePolicySafetyChecker safetyChecker) {
+ mService.setDevicePolicySafetyChecker(safetyChecker);
+ }
+
@Override
public void onStart() {
publishBinderService(Context.DEVICE_POLICY_SERVICE, mService);
@@ -953,6 +964,38 @@
}
}
+ @Override
+ public void setDevicePolicySafetyChecker(DevicePolicySafetyChecker safetyChecker) {
+ Slog.i(LOG_TAG, "Setting DevicePolicySafetyChecker as " + safetyChecker.getClass());
+ mSafetyChecker = safetyChecker;
+ }
+
+ /**
+ * Checks if the feature is supported and it's safe to execute the given {@code operation}.
+ *
+ * <p>Typically called at the beginning of each API method as:
+ *
+ * <pre><code>
+ *
+ * if (!canExecute(operation, permission)) return;
+ *
+ * </code></pre>
+ *
+ * @return {@code true} when it's safe to execute, {@code false} when the feature is not
+ * supported or the caller does not have the given {@code requiredPermission}.
+ *
+ * @throws UnsafeStateException if it's not safe to execute the operation.
+ */
+ boolean canExecute(@DevicePolicyOperation int operation, @NonNull String requiredPermission) {
+ if (!mHasFeature && !hasCallingPermission(requiredPermission)) {
+ return false;
+ }
+ if (mSafetyChecker == null || mSafetyChecker.isDevicePolicyOperationSafe(operation)) {
+ return true;
+ }
+ throw mSafetyChecker.newUnsafeStateException(operation);
+ }
+
/**
* Unit test will subclass it to inject mocks.
*/
@@ -1228,10 +1271,15 @@
SystemProperties.set(key, value);
}
+ // TODO (b/137101239): clean up split system user codes
boolean userManagerIsSplitSystemUser() {
return UserManager.isSplitSystemUser();
}
+ boolean userManagerIsHeadlessSystemUserMode() {
+ return UserManager.isHeadlessSystemUserMode();
+ }
+
String getDevicePolicyFilePathForSystemUser() {
return "/data/system/";
}
@@ -4755,9 +4803,10 @@
@Override
public void lockNow(int flags, boolean parent) {
- if (!mHasFeature && !hasCallingPermission(permission.LOCK_DEVICE)) {
+ if (!canExecute(DevicePolicyManager.OPERATION_LOCK_NOW, permission.LOCK_DEVICE)) {
return;
}
+
final CallerIdentity caller = getCallerIdentity();
final int callingUserId = caller.getUserId();
@@ -7228,7 +7277,6 @@
|| (caller.hasPackage()
&& isCallerDelegate(caller, DELEGATION_KEEP_UNINSTALLED_PACKAGES)));
- // TODO In split system user mode, allow apps on user 0 to query the list
synchronized (getLockObject()) {
return getKeepUninstalledPackagesLocked();
}
@@ -8550,6 +8598,8 @@
pw.printf("mIsWatch=%b\n", mIsWatch);
pw.printf("mIsAutomotive=%b\n", mIsAutomotive);
pw.printf("mHasTelephonyFeature=%b\n", mHasTelephonyFeature);
+ String safetyChecker = mSafetyChecker == null ? "N/A" : mSafetyChecker.getClass().getName();
+ pw.printf("mSafetyChecker=%b\n", safetyChecker);
pw.decreaseIndent();
}
@@ -11988,6 +12038,9 @@
case DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE:
case DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE:
return checkDeviceOwnerProvisioningPreCondition(callingUserId);
+ // TODO (b/137101239): clean up split system user codes
+ // ACTION_PROVISION_MANAGED_USER and ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE
+ // only supported on split-user systems.
case DevicePolicyManager.ACTION_PROVISION_MANAGED_USER:
return checkManagedUserProvisioningPreCondition(callingUserId);
case DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE:
@@ -12010,24 +12063,27 @@
if (mOwners.hasProfileOwner(deviceOwnerUserId)) {
return CODE_USER_HAS_PROFILE_OWNER;
}
- if (!mUserManager.isUserRunning(new UserHandle(deviceOwnerUserId))) {
+ // System user is always running in headless system user mode.
+ if (!mInjector.userManagerIsHeadlessSystemUserMode()
+ && !mUserManager.isUserRunning(new UserHandle(deviceOwnerUserId))) {
return CODE_USER_NOT_RUNNING;
}
if (mIsWatch && hasPaired(UserHandle.USER_SYSTEM)) {
return CODE_HAS_PAIRED;
}
+ // TODO (b/137101239): clean up split system user codes
if (isAdb) {
- // if shell command runs after user setup completed check device status. Otherwise, OK.
+ // If shell command runs after user setup completed check device status. Otherwise, OK.
if (mIsWatch || hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
- if (!mInjector.userManagerIsSplitSystemUser()) {
- if (mUserManager.getUserCount() > 1) {
- return CODE_NONSYSTEM_USER_EXISTS;
- }
- if (hasIncompatibleAccountsOrNonAdb) {
- return CODE_ACCOUNTS_NOT_EMPTY;
- }
- } else {
- // STOPSHIP Do proper check in split user mode
+ // In non-headless system user mode, DO can be setup only if
+ // there's no non-system user
+ if (!mInjector.userManagerIsHeadlessSystemUserMode()
+ && !mInjector.userManagerIsSplitSystemUser()
+ && mUserManager.getUserCount() > 1) {
+ return CODE_NONSYSTEM_USER_EXISTS;
+ }
+ if (hasIncompatibleAccountsOrNonAdb) {
+ return CODE_ACCOUNTS_NOT_EMPTY;
}
}
return CODE_OK;
@@ -12037,19 +12093,26 @@
if (deviceOwnerUserId != UserHandle.USER_SYSTEM) {
return CODE_NOT_SYSTEM_USER;
}
- // In non-split user mode, only provision DO before setup wizard completes
+ // Only provision DO before setup wizard completes
+ // TODO (b/171423186): implement deferred DO setup for headless system user mode
if (hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
return CODE_USER_SETUP_COMPLETED;
}
- } else {
+ } else {
// STOPSHIP Do proper check in split user mode
}
return CODE_OK;
}
}
- private int checkDeviceOwnerProvisioningPreCondition(@UserIdInt int deviceOwnerUserId) {
+ private int checkDeviceOwnerProvisioningPreCondition(@UserIdInt int callingUserId) {
synchronized (getLockObject()) {
+ final int deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode()
+ ? UserHandle.USER_SYSTEM
+ : callingUserId;
+ Slog.i(LOG_TAG,
+ String.format("Calling user %d, device owner will be set on user %d",
+ callingUserId, deviceOwnerUserId));
// hasIncompatibleAccountsOrNonAdb doesn't matter since the caller is not adb.
return checkDeviceOwnerProvisioningPreConditionLocked(/* owner unknown */ null,
deviceOwnerUserId, /* isAdb= */ false,
@@ -12057,6 +12120,7 @@
}
}
+ // TODO (b/137101239): clean up split system user codes
private int checkManagedProfileProvisioningPreCondition(String packageName,
@UserIdInt int callingUserId) {
if (!hasFeatureManagedUsers()) {
@@ -12145,6 +12209,7 @@
return null;
}
+ // TODO (b/137101239): clean up split system user codes
private int checkManagedUserProvisioningPreCondition(int callingUserId) {
if (!hasFeatureManagedUsers()) {
return CODE_MANAGED_USERS_NOT_SUPPORTED;
@@ -12166,6 +12231,7 @@
return CODE_OK;
}
+ // TODO (b/137101239): clean up split system user codes
private int checkManagedShareableDeviceProvisioningPreCondition(int callingUserId) {
if (!mInjector.userManagerIsSplitSystemUser()) {
// ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE only supported on split-user systems.
@@ -12738,9 +12804,8 @@
return true;
}
if (userId == UserHandle.USER_SYSTEM) {
- // The system user is always affiliated in a DO device, even if the DO is set on a
- // different user. This could be the case if the DO is set in the primary user
- // of a split user device.
+ // The system user is always affiliated in a DO device,
+ // even if in headless system user mode.
return true;
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index dfa726f..10b3265 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -34,6 +34,7 @@
import android.app.ApplicationErrorReport;
import android.app.INotificationManager;
import android.app.SystemServiceRegistry;
+import android.app.admin.DevicePolicySafetyChecker;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ContentResolver;
import android.content.Context;
@@ -101,6 +102,7 @@
import com.android.server.audio.AudioService;
import com.android.server.biometrics.AuthService;
import com.android.server.biometrics.BiometricService;
+import com.android.server.biometrics.sensors.BiometricServiceCallback;
import com.android.server.biometrics.sensors.face.FaceService;
import com.android.server.biometrics.sensors.fingerprint.FingerprintService;
import com.android.server.biometrics.sensors.iris.IrisService;
@@ -195,8 +197,10 @@
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
+import java.util.List;
import java.util.Locale;
import java.util.Timer;
import java.util.concurrent.CountDownLatch;
@@ -1468,7 +1472,10 @@
}
t.traceEnd();
- if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
+ final DevicePolicyManagerService.Lifecycle dpms;
+ if (mFactoryTestMode == FactoryTest.FACTORY_TEST_LOW_LEVEL) {
+ dpms = null;
+ } else {
t.traceBegin("StartLockSettingsService");
try {
mSystemServiceManager.startService(LOCK_SETTINGS_SERVICE_CLASS);
@@ -1505,7 +1512,7 @@
// Always start the Device Policy Manager, so that the API is compatible with
// API8.
t.traceBegin("StartDevicePolicyManager");
- mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class);
+ dpms = mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class);
t.traceEnd();
if (!isWatch) {
@@ -2089,9 +2096,12 @@
final boolean hasFeatureFingerprint
= mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
+ final List<BiometricServiceCallback> biometricServiceCallback = new ArrayList<>();
if (hasFeatureFace) {
t.traceBegin("StartFaceSensor");
- mSystemServiceManager.startService(FaceService.class);
+ final FaceService faceService =
+ mSystemServiceManager.startService(FaceService.class);
+ biometricServiceCallback.add(faceService);
t.traceEnd();
}
@@ -2103,13 +2113,20 @@
if (hasFeatureFingerprint) {
t.traceBegin("StartFingerprintSensor");
- mSystemServiceManager.startService(FingerprintService.class);
+ final FingerprintService fingerprintService =
+ mSystemServiceManager.startService(FingerprintService.class);
+ biometricServiceCallback.add(fingerprintService);
t.traceEnd();
}
- // Start this service after all biometric services.
+ // Start this service after all biometric sensor services are started.
t.traceBegin("StartBiometricService");
mSystemServiceManager.startService(BiometricService.class);
+ for (BiometricServiceCallback service : biometricServiceCallback) {
+ Slog.d(TAG, "Notifying onBiometricServiceReady for: "
+ + service.getClass().getSimpleName());
+ service.onBiometricServiceReady();
+ }
t.traceEnd();
t.traceBegin("StartAuthService");
@@ -2427,6 +2444,9 @@
if (cshs instanceof Dumpable) {
mDumper.addDumpable((Dumpable) cshs);
}
+ if (cshs instanceof DevicePolicySafetyChecker) {
+ dpms.setDevicePolicySafetyChecker((DevicePolicySafetyChecker) cshs);
+ }
t.traceEnd();
}
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 1ccd512..a52fe12 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -22,13 +22,14 @@
// Version of services.net for usage by the wifi mainline module.
// Note: This is compiled against module_current.
-// TODO(b/145825329): This should be moved to networkstack-client,
+// TODO(b/172457099): This should be moved to networkstack-client,
// with dependencies moved to frameworks/libs/net right.
java_library {
name: "services.net-module-wifi",
srcs: [
":framework-services-net-module-wifi-shared-srcs",
":net-module-utils-srcs",
+ ":net-utils-services-common-srcs",
"java/android/net/ip/IpClientCallbacks.java",
"java/android/net/ip/IpClientManager.java",
"java/android/net/ip/IpClientUtil.java",
@@ -39,6 +40,7 @@
"java/android/net/TcpKeepalivePacketData.java",
],
sdk_version: "module_current",
+ min_sdk_version: "30",
libs: [
"unsupportedappusage",
"framework-wifi-util-lib",
@@ -49,7 +51,6 @@
"netd_aidl_interface-V3-java",
"netlink-client",
"networkstack-client",
- "net-utils-services-common",
],
apex_available: [
"com.android.wifi",
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 1944965..0d878b4 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -28,7 +28,6 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.SystemProperties;
import android.os.UpdateEngine;
import android.os.UpdateEngineCallback;
import android.provider.DeviceConfig;
@@ -227,8 +226,8 @@
}
// Sample for a fraction of app launches.
- int traceFrequency =
- SystemProperties.getInt("persist.profcollectd.applaunch_trace_freq", 2);
+ int traceFrequency = DeviceConfig.getInt(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT,
+ "applaunch_trace_freq", 2);
int randomNum = ThreadLocalRandom.current().nextInt(100);
if (randomNum < traceFrequency) {
try {
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 3b60594..435c700 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -1456,16 +1456,6 @@
mFingerprintAuthenticator);
}
- @Test(expected = IllegalStateException.class)
- public void testRegistrationWithUnknownId_throwsIllegalStateException() throws Exception {
- mBiometricService = new BiometricService(mContext, mInjector);
- mBiometricService.onStart();
-
- mBiometricService.mImpl.registerAuthenticator(
- 100 /* id */, 2 /* modality */, 15 /* strength */,
- mFingerprintAuthenticator);
- }
-
@Test(expected = IllegalArgumentException.class)
public void testRegistrationWithNullAuthenticator_throwsIllegalArgumentException()
throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/Face10Test.java
index b0f7b0c..35fc7f0 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/Face10Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/Face10Test.java
@@ -16,22 +16,28 @@
package com.android.server.biometrics.sensors.face;
+import static org.mockito.Mockito.when;
+
import android.content.Context;
import android.hardware.biometrics.BiometricManager;
import android.os.Binder;
import android.os.IBinder;
+import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.face.hidl.Face10;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+
@Presubmit
@SmallTest
public class Face10Test {
@@ -41,6 +47,8 @@
@Mock
private Context mContext;
+ @Mock
+ private UserManager mUserManager;
private LockoutResetDispatcher mLockoutResetDispatcher;
private com.android.server.biometrics.sensors.face.hidl.Face10 mFace10;
@@ -54,10 +62,13 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
+ when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+ when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
+
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
- mFace10 = new com.android.server.biometrics.sensors.face.hidl.Face10(mContext, SENSOR_ID,
- BiometricManager.Authenticators.BIOMETRIC_STRONG, mLockoutResetDispatcher,
- false /* supportsSelfIllumination */, 1 /* maxTemplatesAllowed */);
+ mFace10 = new Face10(mContext, SENSOR_ID, BiometricManager.Authenticators.BIOMETRIC_STRONG,
+ mLockoutResetDispatcher, false /* supportsSelfIllumination */,
+ 1 /* maxTemplatesAllowed */);
mBinder = new Binder();
}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 64f8c58..632ad4c 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -1140,6 +1140,25 @@
}
/**
+ * Stops and unloads all models. This is intended as a clean-up call with the expectation that
+ * this instance is not used after.
+ * @hide
+ */
+ public void detach() {
+ synchronized (mLock) {
+ for (ModelData model : mModelDataMap.values()) {
+ forceStopAndUnloadModelLocked(model, null);
+ }
+ mModelDataMap.clear();
+ internalClearGlobalStateLocked();
+ if (mModule != null) {
+ mModule.detach();
+ mModule = null;
+ }
+ }
+ }
+
+ /**
* Stops and unloads a sound model, and removes any reference to the model if successful.
*
* @param modelData The model data to remove.
@@ -1170,7 +1189,7 @@
}
if (modelData.isModelStarted()) {
Slog.d(TAG, "Stopping previously started dangling model " + modelData.getHandle());
- if (mModule.stopRecognition(modelData.getHandle()) != STATUS_OK) {
+ if (mModule.stopRecognition(modelData.getHandle()) == STATUS_OK) {
modelData.setStopped();
modelData.setRequested(false);
} else {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
index f77d490..a976257 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
@@ -27,6 +27,7 @@
import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
import android.media.permission.Identity;
+import android.os.IBinder;
import com.android.server.voiceinteraction.VoiceInteractionManagerService;
@@ -46,10 +47,12 @@
int STATUS_ERROR = SoundTrigger.STATUS_ERROR;
int STATUS_OK = SoundTrigger.STATUS_OK;
- Session attachAsOriginator(@NonNull Identity originatorIdentity);
+ Session attachAsOriginator(@NonNull Identity originatorIdentity,
+ @NonNull IBinder client);
Session attachAsMiddleman(@NonNull Identity middlemanIdentity,
- @NonNull Identity originatorIdentity);
+ @NonNull Identity originatorIdentity,
+ @NonNull IBinder client);
/**
* Dumps service-wide information.
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 5999044..6c9f41c 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -226,33 +226,45 @@
class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
@Override
- public ISoundTriggerSession attachAsOriginator(Identity originatorIdentity) {
+ public ISoundTriggerSession attachAsOriginator(Identity originatorIdentity,
+ @NonNull IBinder client) {
try (SafeCloseable ignored = PermissionUtil.establishIdentityDirect(
originatorIdentity)) {
- return new SoundTriggerSessionStub(newSoundTriggerHelper());
+ return new SoundTriggerSessionStub(newSoundTriggerHelper(), client);
}
}
@Override
public ISoundTriggerSession attachAsMiddleman(Identity originatorIdentity,
- Identity middlemanIdentity) {
+ Identity middlemanIdentity,
+ @NonNull IBinder client) {
try (SafeCloseable ignored = PermissionUtil.establishIdentityIndirect(mContext,
SOUNDTRIGGER_DELEGATE_IDENTITY, middlemanIdentity,
originatorIdentity)) {
- return new SoundTriggerSessionStub(newSoundTriggerHelper());
+ return new SoundTriggerSessionStub(newSoundTriggerHelper(), client);
}
}
}
class SoundTriggerSessionStub extends ISoundTriggerSession.Stub {
private final SoundTriggerHelper mSoundTriggerHelper;
+ // Used to detect client death.
+ private final IBinder mClient;
private final TreeMap<UUID, SoundModel> mLoadedModels = new TreeMap<>();
private final Object mCallbacksLock = new Object();
private final TreeMap<UUID, IRecognitionStatusCallback> mCallbacks = new TreeMap<>();
SoundTriggerSessionStub(
- SoundTriggerHelper soundTriggerHelper) {
+ SoundTriggerHelper soundTriggerHelper, @NonNull IBinder client) {
mSoundTriggerHelper = soundTriggerHelper;
+ mClient = client;
+ try {
+ mClient.linkToDeath(() -> {
+ clientDied();
+ }, 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to register death listener.", e);
+ }
}
@Override
@@ -790,6 +802,13 @@
}
}
+ private void clientDied() {
+ Slog.w(TAG, "Client died, cleaning up session.");
+ sEventLogger.log(new SoundTriggerLogger.StringEvent(
+ "Client died, cleaning up session."));
+ mSoundTriggerHelper.detach();
+ }
+
/**
* Local end for a {@link SoundTriggerDetectionService}. Operations are queued up and
* executed when the service connects.
@@ -1457,10 +1476,19 @@
private class SessionImpl implements Session {
private final @NonNull SoundTriggerHelper mSoundTriggerHelper;
+ private final @NonNull IBinder mClient;
private SessionImpl(
- @NonNull SoundTriggerHelper soundTriggerHelper) {
+ @NonNull SoundTriggerHelper soundTriggerHelper, @NonNull IBinder client) {
mSoundTriggerHelper = soundTriggerHelper;
+ mClient = client;
+ try {
+ mClient.linkToDeath(() -> {
+ clientDied();
+ }, 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to register death listener.", e);
+ }
}
@Override
@@ -1507,22 +1535,31 @@
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mSoundTriggerHelper.dump(fd, pw, args);
}
+
+ private void clientDied() {
+ Slog.w(TAG, "Client died, cleaning up session.");
+ sEventLogger.log(new SoundTriggerLogger.StringEvent(
+ "Client died, cleaning up session."));
+ mSoundTriggerHelper.detach();
+ }
}
@Override
- public Session attachAsOriginator(@NonNull Identity originatorIdentity) {
+ public Session attachAsOriginator(@NonNull Identity originatorIdentity,
+ @NonNull IBinder client) {
try (SafeCloseable ignored = PermissionUtil.establishIdentityDirect(
originatorIdentity)) {
- return new SessionImpl(newSoundTriggerHelper());
+ return new SessionImpl(newSoundTriggerHelper(), client);
}
}
@Override
public Session attachAsMiddleman(@NonNull Identity middlemanIdentity,
- @NonNull Identity originatorIdentity) {
+ @NonNull Identity originatorIdentity,
+ @NonNull IBinder client) {
try (SafeCloseable ignored = PermissionUtil.establishIdentityIndirect(mContext,
SOUNDTRIGGER_DELEGATE_IDENTITY, middlemanIdentity, originatorIdentity)) {
- return new SessionImpl(newSoundTriggerHelper());
+ return new SessionImpl(newSoundTriggerHelper(), client);
}
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 547d253..2bcf3b5 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -257,12 +257,13 @@
@Override
public @NonNull IVoiceInteractionSoundTriggerSession createSoundTriggerSessionAsOriginator(
- @NonNull Identity originatorIdentity) {
+ @NonNull Identity originatorIdentity, IBinder client) {
Objects.requireNonNull(originatorIdentity);
try (SafeCloseable ignored = PermissionUtil.establishIdentityDirect(
originatorIdentity)) {
SoundTriggerSession session = new SoundTriggerSession(
- mSoundTriggerInternal.attachAsOriginator(IdentityContext.getNonNull()));
+ mSoundTriggerInternal.attachAsOriginator(IdentityContext.getNonNull(),
+ client));
synchronized (mSessions) {
mSessions.add(new WeakReference<>(session));
}
diff --git a/telephony/java/android/telephony/CarrierBandwidth.aidl b/telephony/java/android/telephony/CarrierBandwidth.aidl
new file mode 100644
index 0000000..d0861b8
--- /dev/null
+++ b/telephony/java/android/telephony/CarrierBandwidth.aidl
@@ -0,0 +1,17 @@
+/*
+ * Copyright 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 android.telephony;
+parcelable CarrierBandwidth;
\ No newline at end of file
diff --git a/telephony/java/android/telephony/CarrierBandwidth.java b/telephony/java/android/telephony/CarrierBandwidth.java
new file mode 100644
index 0000000..17747a3
--- /dev/null
+++ b/telephony/java/android/telephony/CarrierBandwidth.java
@@ -0,0 +1,208 @@
+/*
+ * 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 android.telephony;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Defines downlink and uplink capacity of a network in kbps
+ * @hide
+ */
+@SystemApi
+public final class CarrierBandwidth implements Parcelable {
+ /**
+ * Any field that is not reported shall be set to INVALID
+ */
+ public static final int INVALID = -1;
+
+ /**
+ * Estimated downlink capacity in kbps of the primary carrier.
+ * This bandwidth estimate shall be the estimated maximum sustainable link bandwidth.
+ * This will be {@link #INVALID} if the network is not connected
+ */
+ private int mPrimaryDownlinkCapacityKbps;
+
+ /**
+ * Estimated uplink capacity in kbps of the primary carrier.
+ * This bandwidth estimate shall be the estimated maximum sustainable link bandwidth.
+ * This will be {@link #INVALID} if the network is not connected
+ */
+ private int mPrimaryUplinkCapacityKbps;
+
+ /**
+ * Estimated downlink capacity in kbps of the secondary carrier in a dual connected network.
+ * This bandwidth estimate shall be the estimated maximum sustainable link bandwidth.
+ * This will be {@link #INVALID} if the network is not connected
+ */
+ private int mSecondaryDownlinkCapacityKbps;
+
+ /**
+ * Estimated uplink capacity in kbps of the secondary carrier in a dual connected network.
+ * This bandwidth estimate shall be the estimated maximum sustainable link bandwidth.
+ * This will be {@link #INVALID} if the network is not connected
+ */
+ private int mSecondaryUplinkCapacityKbps;
+
+ /** @hide **/
+ public CarrierBandwidth(Parcel in) {
+ mPrimaryDownlinkCapacityKbps = in.readInt();
+ mPrimaryUplinkCapacityKbps = in.readInt();
+ mSecondaryDownlinkCapacityKbps = in.readInt();
+ mSecondaryUplinkCapacityKbps = in.readInt();
+ }
+
+ /** @hide **/
+ public CarrierBandwidth() {
+ mPrimaryDownlinkCapacityKbps = INVALID;
+ mPrimaryUplinkCapacityKbps = INVALID;
+ mSecondaryDownlinkCapacityKbps = INVALID;
+ mSecondaryUplinkCapacityKbps = INVALID;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param primaryDownlinkCapacityKbps Estimated downlink capacity in kbps of
+ * the primary carrier.
+ * @param primaryUplinkCapacityKbps Estimated uplink capacity in kbps of
+ * the primary carrier.
+ * @param secondaryDownlinkCapacityKbps Estimated downlink capacity in kbps of
+ * the secondary carrier
+ * @param secondaryUplinkCapacityKbps Estimated uplink capacity in kbps of
+ * the secondary carrier
+ */
+ public CarrierBandwidth(int primaryDownlinkCapacityKbps, int primaryUplinkCapacityKbps,
+ int secondaryDownlinkCapacityKbps, int secondaryUplinkCapacityKbps) {
+ mPrimaryDownlinkCapacityKbps = primaryDownlinkCapacityKbps;
+ mPrimaryUplinkCapacityKbps = primaryUplinkCapacityKbps;
+ mSecondaryDownlinkCapacityKbps = secondaryDownlinkCapacityKbps;
+ mSecondaryUplinkCapacityKbps = secondaryUplinkCapacityKbps;
+ }
+
+ /**
+ * Retrieves the upstream bandwidth for the primary network in Kbps. This always only refers to
+ * the estimated first hop transport bandwidth.
+ * This will be INVALID if the network is not connected
+ *
+ * @return The estimated first hop upstream (device to network) bandwidth.
+ */
+ public int getPrimaryDownlinkCapacityKbps() {
+ return mPrimaryDownlinkCapacityKbps;
+ }
+
+ /**
+ * Retrieves the downstream bandwidth for the primary network in Kbps. This always only refers
+ * to the estimated first hop transport bandwidth.
+ * This will be INVALID if the network is not connected
+ *
+ * @return The estimated first hop downstream (network to device) bandwidth.
+ */
+ public int getPrimaryUplinkCapacityKbps() {
+ return mPrimaryUplinkCapacityKbps;
+ }
+
+ /**
+ * Retrieves the upstream bandwidth for the secondary network in Kbps. This always only refers
+ * to the estimated first hop transport bandwidth.
+ * This will be INVALID if the network is not connected
+ *
+ * @return The estimated first hop upstream (device to network) bandwidth.
+ */
+ public int getSecondaryDownlinkCapacityKbps() {
+ return mSecondaryDownlinkCapacityKbps;
+ }
+
+ /**
+ * Retrieves the downstream bandwidth for the secondary network in Kbps. This always only
+ * refers to the estimated first hop transport bandwidth.
+ * This will be INVALID if the network is not connected
+ *
+ * @return The estimated first hop downstream (network to device) bandwidth.
+ */
+ public int getSecondaryUplinkCapacityKbps() {
+ return mSecondaryUplinkCapacityKbps;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "CarrierBandwidth: {primaryDownlinkCapacityKbps=" + mPrimaryDownlinkCapacityKbps
+ + " primaryUplinkCapacityKbps=" + mPrimaryUplinkCapacityKbps
+ + " secondaryDownlinkCapacityKbps=" + mSecondaryDownlinkCapacityKbps
+ + " secondaryUplinkCapacityKbps=" + mSecondaryUplinkCapacityKbps
+ + "}";
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mPrimaryDownlinkCapacityKbps,
+ mPrimaryUplinkCapacityKbps,
+ mSecondaryDownlinkCapacityKbps,
+ mSecondaryUplinkCapacityKbps);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o == null || !(o instanceof CallQuality) || hashCode() != o.hashCode()) {
+ return false;
+ }
+ if (this == o) {
+ return true;
+ }
+ CarrierBandwidth s = (CarrierBandwidth) o;
+ return (mPrimaryDownlinkCapacityKbps == s.mPrimaryDownlinkCapacityKbps
+ && mPrimaryUplinkCapacityKbps == s.mPrimaryUplinkCapacityKbps
+ && mSecondaryDownlinkCapacityKbps == s.mSecondaryDownlinkCapacityKbps
+ && mSecondaryDownlinkCapacityKbps == s.mSecondaryDownlinkCapacityKbps);
+ }
+
+ /**
+ * {@link Parcelable#describeContents}
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * {@link Parcelable#writeToParcel}
+ * @hide
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mPrimaryDownlinkCapacityKbps);
+ dest.writeInt(mPrimaryUplinkCapacityKbps);
+ dest.writeInt(mSecondaryDownlinkCapacityKbps);
+ dest.writeInt(mSecondaryUplinkCapacityKbps);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<CarrierBandwidth> CREATOR =
+ new Parcelable.Creator() {
+ public CarrierBandwidth createFromParcel(Parcel in) {
+ return new CarrierBandwidth(in);
+ }
+
+ public CarrierBandwidth[] newArray(int size) {
+ return new CarrierBandwidth[size];
+ }
+ };
+}
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index f8a200a..8507d85 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.AccessNetworkConstants.TransportType;
@@ -635,7 +636,8 @@
.append(" cellIdentity=").append(mCellIdentity)
.append(" voiceSpecificInfo=").append(mVoiceSpecificInfo)
.append(" dataSpecificInfo=").append(mDataSpecificInfo)
- .append(" nrState=").append(nrStateToString(mNrState))
+ .append(" nrState=").append(Build.IS_DEBUGGABLE
+ ? nrStateToString(mNrState) : "****")
.append(" rRplmn=").append(mRplmn)
.append(" isUsingCarrierAggregation=").append(mIsUsingCarrierAggregation)
.append("}").toString();
diff --git a/telephony/java/android/telephony/PinResult.java b/telephony/java/android/telephony/PinResult.java
index c2a4f33..b8c1ffe 100644
--- a/telephony/java/android/telephony/PinResult.java
+++ b/telephony/java/android/telephony/PinResult.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -27,10 +28,16 @@
import java.util.Objects;
/**
- * Holds the result from a pin attempt.
+ * Holds the result from a PIN attempt.
+ *
+ * @see TelephonyManager#supplyIccLockPin
+ * @see TelephonyManager#supplyIccLockPuk
+ * @see TelephonyManager#setIccLockEnabled
+ * @see TelephonyManager#changeIccLockPin
*
* @hide
*/
+@SystemApi
public final class PinResult implements Parcelable {
/** @hide */
@IntDef({
@@ -64,24 +71,24 @@
private static final PinResult sFailedResult =
new PinResult(PinResult.PIN_RESULT_TYPE_FAILURE, -1);
- private final @PinResultType int mType;
+ private final @PinResultType int mResult;
private final int mAttemptsRemaining;
/**
- * Returns either success, incorrect or failure.
+ * Returns the result of the PIN attempt.
*
- * @see #PIN_RESULT_TYPE_SUCCESS
- * @see #PIN_RESULT_TYPE_INCORRECT
- * @see #PIN_RESULT_TYPE_FAILURE
- * @return The result type of the pin attempt.
+ * @return The result of the PIN attempt.
*/
- public @PinResultType int getType() {
- return mType;
+ public @PinResultType int getResult() {
+ return mResult;
}
/**
- * The number of pin attempts remaining.
+ * Returns the number of PIN attempts remaining.
+ * This will be set when {@link #getResult} is {@link #PIN_RESULT_TYPE_INCORRECT}.
+ * Indicates the number of attempts at entering the PIN before the SIM will be locked and
+ * require a PUK unlock to be performed.
*
* @return Number of attempts remaining.
*/
@@ -89,22 +96,32 @@
return mAttemptsRemaining;
}
+ /**
+ * Used to indicate a failed PIN attempt result.
+ *
+ * @return default PinResult for failure.
+ *
+ * @hide
+ */
@NonNull
public static PinResult getDefaultFailedResult() {
return sFailedResult;
}
/**
- * PinResult constructor
+ * PinResult constructor.
*
- * @param type The type of pin result.
+ * @param result The pin result value.
* @see #PIN_RESULT_TYPE_SUCCESS
* @see #PIN_RESULT_TYPE_INCORRECT
* @see #PIN_RESULT_TYPE_FAILURE
+ * @see #PIN_RESULT_TYPE_ABORTED
* @param attemptsRemaining Number of pin attempts remaining.
+ *
+ * @hide
*/
- public PinResult(@PinResultType int type, int attemptsRemaining) {
- mType = type;
+ public PinResult(@PinResultType int result, int attemptsRemaining) {
+ mResult = result;
mAttemptsRemaining = attemptsRemaining;
}
@@ -114,7 +131,7 @@
* @hide
*/
private PinResult(Parcel in) {
- mType = in.readInt();
+ mResult = in.readInt();
mAttemptsRemaining = in.readInt();
}
@@ -124,11 +141,11 @@
@NonNull
@Override
public String toString() {
- return "type: " + getType() + ", attempts remaining: " + getAttemptsRemaining();
+ return "result: " + getResult() + ", attempts remaining: " + getAttemptsRemaining();
}
/**
- * Required to be Parcelable
+ * Describe the contents of this object.
*/
@Override
public int describeContents() {
@@ -136,15 +153,17 @@
}
/**
- * Required to be Parcelable
+ * Write this object to a Parcel.
*/
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
- out.writeInt(mType);
+ out.writeInt(mResult);
out.writeInt(mAttemptsRemaining);
}
- /** Required to be Parcelable */
+ /**
+ * Parcel creator class.
+ */
public static final @NonNull Parcelable.Creator<PinResult> CREATOR = new Creator<PinResult>() {
public PinResult createFromParcel(Parcel in) {
return new PinResult(in);
@@ -156,7 +175,7 @@
@Override
public int hashCode() {
- return Objects.hash(mAttemptsRemaining, mType);
+ return Objects.hash(mAttemptsRemaining, mResult);
}
@Override
@@ -171,7 +190,7 @@
return false;
}
PinResult other = (PinResult) obj;
- return (mType == other.mType
+ return (mResult == other.mResult
&& mAttemptsRemaining == other.mAttemptsRemaining);
}
}
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 41b3ee6..dedb1af 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -1102,7 +1102,8 @@
.append(", isUsingCarrierAggregation=").append(isUsingCarrierAggregation())
.append(", mLteEarfcnRsrpBoost=").append(mLteEarfcnRsrpBoost)
.append(", mNetworkRegistrationInfos=").append(mNetworkRegistrationInfos)
- .append(", mNrFrequencyRange=").append(mNrFrequencyRange)
+ .append(", mNrFrequencyRange=").append(Build.IS_DEBUGGABLE
+ ? mNrFrequencyRange : FREQUENCY_RANGE_UNKNOWN)
.append(", mOperatorAlphaLongRaw=").append(mOperatorAlphaLongRaw)
.append(", mOperatorAlphaShortRaw=").append(mOperatorAlphaShortRaw)
.append(", mIsDataRoamingFromRegistration=")
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index e65b641..96f3ce0 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -8871,9 +8871,13 @@
return false;
}
- /** @hide */
+ /**
+ * @deprecated use {@link #supplyIccLockPin(String)} instead.
+ * @hide
+ */
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ @Deprecated
public int[] supplyPinReportResult(String pin) {
try {
ITelephony telephony = getITelephony();
@@ -8885,65 +8889,91 @@
return new int[0];
}
- /** @hide */
+ /**
+ * @deprecated use {@link #supplyIccLockPuk(String, String)} instead.
+ * @hide
+ */
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ @Deprecated
public int[] supplyPukReportResult(String puk, String pin) {
try {
ITelephony telephony = getITelephony();
if (telephony != null)
return telephony.supplyPukReportResultForSubscriber(getSubId(), puk, pin);
} catch (RemoteException e) {
- Log.e(TAG, "Error calling ITelephony#]", e);
+ Log.e(TAG, "Error calling ITelephony#supplyPukReportResultForSubscriber", e);
}
return new int[0];
}
/**
- * Used when the user attempts to enter their pin.
+ * Supplies a PIN to unlock the ICC and returns the corresponding {@link PinResult}.
+ * Used when the user enters their ICC unlock PIN to attempt an unlock.
*
- * @param pin The user entered pin.
- * @return The result of the pin.
+ * @param pin The user entered PIN.
+ * @return The result of the PIN.
+ * @throws SecurityException if the caller doesn't have the permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
+ * app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
* @hide
*/
- @Nullable
+ @SystemApi
+ @NonNull
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public PinResult supplyPinReportPinResult(@NonNull String pin) {
+ public PinResult supplyIccLockPin(@NonNull String pin) {
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
int[] result = telephony.supplyPinReportResultForSubscriber(getSubId(), pin);
return new PinResult(result[0], result[1]);
+ } else {
+ throw new IllegalStateException("telephony service is null.");
}
} catch (RemoteException e) {
- Log.e(TAG, "Error calling ITelephony#supplyPinReportResultForSubscriber", e);
+ Log.e(TAG, "Error calling ITelephony#supplyIccLockPin", e);
+ e.rethrowFromSystemServer();
}
- return null;
+ return PinResult.getDefaultFailedResult();
}
/**
- * Used when the user attempts to enter the puk or their pin.
+ * Supplies a PUK and PIN to unlock the ICC and returns the corresponding {@link PinResult}.
+ * Used when the user enters their ICC unlock PUK and PIN to attempt an unlock.
*
- * @param puk The product unblocking key.
- * @param pin The user entered pin.
- * @return The result of the pin.
+ * @param puk The product unlocking key.
+ * @param pin The user entered PIN.
+ * @return The result of the PUK and PIN.
+ * @throws SecurityException if the caller doesn't have the permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
+ * app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
* @hide
*/
- @Nullable
+ @SystemApi
+ @NonNull
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public PinResult supplyPukReportPinResult(@NonNull String puk, @NonNull String pin) {
+ public PinResult supplyIccLockPuk(@NonNull String puk, @NonNull String pin) {
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
int[] result = telephony.supplyPukReportResultForSubscriber(getSubId(), puk, pin);
return new PinResult(result[0], result[1]);
+ } else {
+ throw new IllegalStateException("telephony service is null.");
}
} catch (RemoteException e) {
- Log.e(TAG, "Error calling ITelephony#]", e);
+ Log.e(TAG, "Error calling ITelephony#supplyIccLockPuk", e);
+ e.rethrowFromSystemServer();
}
- return null;
+ return PinResult.getDefaultFailedResult();
}
/**
@@ -13628,18 +13658,22 @@
}
/**
- * The IccLock state or password was changed successfully.
+ * Indicates that the ICC PIN lock state or PIN was changed successfully.
* @hide
*/
public static final int CHANGE_ICC_LOCK_SUCCESS = Integer.MAX_VALUE;
/**
- * Check whether ICC pin lock is enabled.
- * This is a sync call which returns the cached pin enabled state.
+ * Check whether ICC PIN lock is enabled.
+ * This is a sync call which returns the cached PIN enabled state.
*
- * @return {@code true} if ICC lock enabled, {@code false} if ICC lock disabled.
- *
+ * @return {@code true} if ICC PIN lock enabled, {@code false} if disabled.
* @throws SecurityException if the caller doesn't have the permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+ * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
* @hide
*/
@@ -13651,81 +13685,124 @@
ITelephony telephony = getITelephony();
if (telephony != null) {
return telephony.isIccLockEnabled(getSubId());
+ } else {
+ throw new IllegalStateException("telephony service is null.");
}
} catch (RemoteException e) {
Log.e(TAG, "isIccLockEnabled RemoteException", e);
+ e.rethrowFromSystemServer();
}
return false;
}
/**
- * Set the ICC pin lock enabled or disabled.
+ * Enable or disable the ICC PIN lock.
*
- * If enable/disable ICC pin lock successfully, a value of {@link Integer#MAX_VALUE} is
- * returned.
- * If an incorrect old password is specified, the return value will indicate how many more
- * attempts the user can make to change the password before the SIM is locked.
- * Using PUK code to unlock SIM if enter the incorrect old password 3 times.
- *
- * @param enabled "true" for locked, "false" for unlocked.
- * @param password needed to change the ICC pin state, aka. Pin1
- * @return an integer representing the status of IccLock enabled or disabled in the following
- * three cases:
- * - {@link TelephonyManager#CHANGE_ICC_LOCK_SUCCESS} if enabled or disabled IccLock
- * successfully.
- * - Positive number and zero for remaining password attempts.
- * - Negative number for other failure cases (such like enabling/disabling PIN failed).
- *
+ * @param enabled "true" for locked, "false" for unlocked.
+ * @param pin needed to change the ICC PIN lock, aka. Pin1.
+ * @return the result of enabling or disabling the ICC PIN lock.
* @throws SecurityException if the caller doesn't have the permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
+ * app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
* @hide
*/
+ @SystemApi
+ @NonNull
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public int setIccLockEnabled(boolean enabled, @NonNull String password) {
- checkNotNull(password, "setIccLockEnabled password can't be null.");
+ public PinResult setIccLockEnabled(boolean enabled, @NonNull String pin) {
+ checkNotNull(pin, "setIccLockEnabled pin can't be null.");
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- return telephony.setIccLockEnabled(getSubId(), enabled, password);
+ int result = telephony.setIccLockEnabled(getSubId(), enabled, pin);
+ if (result == CHANGE_ICC_LOCK_SUCCESS) {
+ return new PinResult(PinResult.PIN_RESULT_TYPE_SUCCESS, 0);
+ } else if (result < 0) {
+ return PinResult.getDefaultFailedResult();
+ } else {
+ return new PinResult(PinResult.PIN_RESULT_TYPE_INCORRECT, result);
+ }
+ } else {
+ throw new IllegalStateException("telephony service is null.");
}
} catch (RemoteException e) {
Log.e(TAG, "setIccLockEnabled RemoteException", e);
+ e.rethrowFromSystemServer();
}
- return 0;
+ return PinResult.getDefaultFailedResult();
}
/**
- * Change the ICC password used in ICC pin lock.
+ * Change the ICC lock PIN.
*
- * If the password was changed successfully, a value of {@link Integer#MAX_VALUE} is returned.
- * If an incorrect old password is specified, the return value will indicate how many more
- * attempts the user can make to change the password before the SIM is locked.
- * Using PUK code to unlock SIM if enter the incorrect old password 3 times.
- *
- * @param oldPassword is the old password
- * @param newPassword is the new password
- * @return an integer representing the status of IccLock changed in the following three cases:
- * - {@link TelephonyManager#CHANGE_ICC_LOCK_SUCCESS} if changed IccLock successfully.
- * - Positive number and zero for remaining password attempts.
- * - Negative number for other failure cases (such like enabling/disabling PIN failed).
- *
+ * @param oldPin is the old PIN
+ * @param newPin is the new PIN
+ * @return The result of changing the ICC lock PIN.
* @throws SecurityException if the caller doesn't have the permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
+ * app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
* @hide
*/
+ @SystemApi
+ @NonNull
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public int changeIccLockPassword(@NonNull String oldPassword, @NonNull String newPassword) {
- checkNotNull(oldPassword, "changeIccLockPassword oldPassword can't be null.");
- checkNotNull(newPassword, "changeIccLockPassword newPassword can't be null.");
+ public PinResult changeIccLockPin(@NonNull String oldPin, @NonNull String newPin) {
+ checkNotNull(oldPin, "changeIccLockPin oldPin can't be null.");
+ checkNotNull(newPin, "changeIccLockPin newPin can't be null.");
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- return telephony.changeIccLockPassword(getSubId(), oldPassword, newPassword);
+ int result = telephony.changeIccLockPassword(getSubId(), oldPin, newPin);
+ if (result == CHANGE_ICC_LOCK_SUCCESS) {
+ return new PinResult(PinResult.PIN_RESULT_TYPE_SUCCESS, 0);
+ } else if (result < 0) {
+ return PinResult.getDefaultFailedResult();
+ } else {
+ return new PinResult(PinResult.PIN_RESULT_TYPE_INCORRECT, result);
+ }
+ } else {
+ throw new IllegalStateException("telephony service is null.");
}
} catch (RemoteException e) {
- Log.e(TAG, "changeIccLockPassword RemoteException", e);
+ Log.e(TAG, "changeIccLockPin RemoteException", e);
+ e.rethrowFromSystemServer();
}
- return 0;
+ return PinResult.getDefaultFailedResult();
+ }
+
+ /**
+ * Get carrier bandwidth. In case of Dual connected network this will report
+ * bandwidth per primary and secondary network.
+ * @return CarrierBandwidth with bandwidth of both primary and secondary carrier.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @NonNull
+ public CarrierBandwidth getCarrierBandwidth() {
+ try {
+ ITelephony service = getITelephony();
+ if (service != null) {
+ return service.getCarrierBandwidth(getSubId());
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "getCarrierBandwidth RemoteException", ex);
+ ex.rethrowFromSystemServer();
+ }
+
+ //Should not reach. Adding return statement to make compiler happy
+ return null;
}
/**
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 51aa9cc..0bd4851 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -31,6 +31,7 @@
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telephony.CallForwardingInfo;
+import android.telephony.CarrierBandwidth;
import android.telephony.CarrierRestrictionRules;
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
@@ -2240,4 +2241,10 @@
* @return true if dual connectivity is enabled else false
*/
boolean isNrDualConnectivityEnabled(int subId);
+
+ /**
+ * Get carrier bandwidth per primary and secondary carrier
+ * @return CarrierBandwidth with bandwidth of both primary and secondary carrier.
+ */
+ CarrierBandwidth getCarrierBandwidth(int subId);
}
diff --git a/wifi/jarjar-rules.txt b/wifi/jarjar-rules.txt
index d235c80..dc96df6 100644
--- a/wifi/jarjar-rules.txt
+++ b/wifi/jarjar-rules.txt
@@ -89,8 +89,6 @@
rule android.util.LocalLog* com.android.wifi.x.@0
rule android.util.Rational* com.android.wifi.x.@0
-rule android.os.BasicShellCommandHandler* com.android.wifi.x.@0
-
# Use our statically linked bouncy castle library
rule org.bouncycastle.** com.android.wifi.x.@0
# Use our statically linked protobuf library