Merge "Support selected item state in Bilingual Adapter."
diff --git a/Android.bp b/Android.bp
index eae0d73..30b38d3 100644
--- a/Android.bp
+++ b/Android.bp
@@ -72,7 +72,6 @@
":framework-keystore-sources",
":framework-identity-sources",
":framework-location-sources",
- ":framework-lowpan-sources",
":framework-mca-effect-sources",
":framework-mca-filterfw-sources",
":framework-mca-filterpacks-sources",
@@ -167,7 +166,6 @@
"identity/java",
"keystore/java",
"location/java",
- "lowpan/java",
"media/java",
"media/mca/effect/java",
"media/mca/filterfw/java",
@@ -277,7 +275,6 @@
":framework-keystore-sources",
":framework-identity-sources",
":framework-location-sources",
- ":framework-lowpan-sources",
":framework-mca-effect-sources",
":framework-mca-filterfw-sources",
":framework-mca-filterpacks-sources",
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index f8aa7e9..1d92778 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -25,4 +25,6 @@
hidden_api_txt_exclude_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/exclude.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT}
+ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/ktfmt_includes.txt ${PREUPLOAD_FILES}
+
ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py -f ${PREUPLOAD_FILES}
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java
index 3a23b54..bc8fc53 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java
@@ -59,9 +59,8 @@
Typeface.loadPreinstalledSystemFontMap();
}
- @ManualBenchmarkState.ManualBenchmarkTest(
- warmupDurationNs = WARMUP_DURATION_NS,
- targetTestDurationNs = TARGET_TEST_DURATION_NS)
+ // testSerializeFontMap uses the default targetTestDurationNs, which is much longer than
+ // TARGET_TEST_DURATION_NS, in order to stabilize test results.
@Test
public void testSerializeFontMap() throws Exception {
Map<String, Typeface> systemFontMap = Typeface.getSystemFontMap();
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
index e23860c..d4a1cd2 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
@@ -19,6 +19,8 @@
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static com.android.server.tare.EconomicPolicy.REGULATION_BASIC_INCOME;
+import static com.android.server.tare.EconomicPolicy.REGULATION_BG_RESTRICTED;
+import static com.android.server.tare.EconomicPolicy.REGULATION_BG_UNRESTRICTED;
import static com.android.server.tare.EconomicPolicy.REGULATION_BIRTHRIGHT;
import static com.android.server.tare.EconomicPolicy.REGULATION_DEMOTION;
import static com.android.server.tare.EconomicPolicy.REGULATION_PROMOTION;
@@ -510,12 +512,12 @@
}
final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
final long originalBalance = ledger.getCurrentBalance();
+ final long maxBalance = economicPolicy.getMaxSatiatedBalance(userId, pkgName);
if (transaction.delta > 0
- && originalBalance + transaction.delta > economicPolicy.getMaxSatiatedBalance()) {
+ && originalBalance + transaction.delta > maxBalance) {
// Set lower bound at 0 so we don't accidentally take away credits when we were trying
// to _give_ the app credits.
- final long newDelta =
- Math.max(0, economicPolicy.getMaxSatiatedBalance() - originalBalance);
+ final long newDelta = Math.max(0, maxBalance - originalBalance);
Slog.i(TAG, "Would result in becoming too rich. Decreasing transaction "
+ eventToString(transaction.eventId)
+ (transaction.tag == null ? "" : ":" + transaction.tag)
@@ -660,6 +662,47 @@
}
}
+ /**
+ * Reclaim all ARCs from an app that was just restricted.
+ */
+ @GuardedBy("mLock")
+ void onAppRestrictedLocked(final int userId, @NonNull final String pkgName) {
+ final long curBalance = getBalanceLocked(userId, pkgName);
+ final long minBalance = mIrs.getMinBalanceLocked(userId, pkgName);
+ if (curBalance <= minBalance) {
+ return;
+ }
+ if (DEBUG) {
+ Slog.i(TAG, "App restricted! Taking " + curBalance
+ + " from " + appToString(userId, pkgName));
+ }
+
+ final long now = getCurrentTimeMillis();
+ final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName);
+ recordTransactionLocked(userId, pkgName, ledger,
+ new Ledger.Transaction(now, now, REGULATION_BG_RESTRICTED, null, -curBalance, 0),
+ true);
+ }
+
+ /**
+ * Give an app that was just unrestricted some ARCs.
+ */
+ @GuardedBy("mLock")
+ void onAppUnrestrictedLocked(final int userId, @NonNull final String pkgName) {
+ final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName);
+ if (ledger.getCurrentBalance() > 0) {
+ Slog.wtf(TAG, "App " + pkgName + " had credits while it was restricted");
+ // App already got credits somehow. Move along.
+ return;
+ }
+
+ final long now = getCurrentTimeMillis();
+
+ recordTransactionLocked(userId, pkgName, ledger,
+ new Ledger.Transaction(now, now, REGULATION_BG_UNRESTRICTED, null,
+ mIrs.getMinBalanceLocked(userId, pkgName), 0), true);
+ }
+
/** Returns true if an app should be given credits in the general distributions. */
private boolean shouldGiveCredits(@NonNull InstalledPackageInfo packageInfo) {
// Skip apps that wouldn't be doing any work. Giving them ARCs would be wasteful.
@@ -668,7 +711,8 @@
}
final int userId = UserHandle.getUserId(packageInfo.uid);
// No point allocating ARCs to the system. It can do whatever it wants.
- return !mIrs.isSystem(userId, packageInfo.packageName);
+ return !mIrs.isSystem(userId, packageInfo.packageName)
+ && !mIrs.isPackageRestricted(userId, packageInfo.packageName);
}
void onCreditSupplyChanged() {
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
index aa66e92..e791e98 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
@@ -170,6 +170,9 @@
@Override
long getMinSatiatedBalance(final int userId, @NonNull final String pkgName) {
+ if (mIrs.isPackageRestricted(userId, pkgName)) {
+ return 0;
+ }
if (mIrs.isPackageExempted(userId, pkgName)) {
return mMinSatiatedBalanceExempted;
}
@@ -178,7 +181,10 @@
}
@Override
- long getMaxSatiatedBalance() {
+ long getMaxSatiatedBalance(int userId, @NonNull String pkgName) {
+ if (mIrs.isPackageRestricted(userId, pkgName)) {
+ return 0;
+ }
// TODO(230501287): adjust balance based on whether the app has the SCHEDULE_EXACT_ALARM
// permission granted. Apps without the permission granted shouldn't need a high balance
// since they won't be able to use exact alarms. Apps with the permission granted could
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
index 5d9cce8..625f99d 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
@@ -36,7 +36,6 @@
/** Lazily populated set of rewards covered by this policy. */
private final SparseArray<Reward> mRewards = new SparseArray<>();
private final int[] mCostModifiers;
- private long mMaxSatiatedBalance;
private long mInitialConsumptionLimit;
private long mHardConsumptionLimit;
@@ -80,16 +79,13 @@
}
private void updateLimits() {
- long maxSatiatedBalance = 0;
long initialConsumptionLimit = 0;
long hardConsumptionLimit = 0;
for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) {
final EconomicPolicy economicPolicy = mEnabledEconomicPolicies.valueAt(i);
- maxSatiatedBalance += economicPolicy.getMaxSatiatedBalance();
initialConsumptionLimit += economicPolicy.getInitialSatiatedConsumptionLimit();
hardConsumptionLimit += economicPolicy.getHardSatiatedConsumptionLimit();
}
- mMaxSatiatedBalance = maxSatiatedBalance;
mInitialConsumptionLimit = initialConsumptionLimit;
mHardConsumptionLimit = hardConsumptionLimit;
}
@@ -104,8 +100,12 @@
}
@Override
- long getMaxSatiatedBalance() {
- return mMaxSatiatedBalance;
+ long getMaxSatiatedBalance(int userId, @NonNull String pkgName) {
+ long max = 0;
+ for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) {
+ max += mEnabledEconomicPolicies.valueAt(i).getMaxSatiatedBalance(userId, pkgName);
+ }
+ return max;
}
@Override
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
index 564ffb9..2fb0c1a 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
@@ -67,6 +67,9 @@
static final int REGULATION_WEALTH_RECLAMATION = TYPE_REGULATION | 2;
static final int REGULATION_PROMOTION = TYPE_REGULATION | 3;
static final int REGULATION_DEMOTION = TYPE_REGULATION | 4;
+ /** App is fully restricted from running in the background. */
+ static final int REGULATION_BG_RESTRICTED = TYPE_REGULATION | 5;
+ static final int REGULATION_BG_UNRESTRICTED = TYPE_REGULATION | 6;
static final int REWARD_NOTIFICATION_SEEN = TYPE_REWARD | 0;
static final int REWARD_NOTIFICATION_INTERACTION = TYPE_REWARD | 1;
@@ -210,7 +213,7 @@
* exists to ensure that no single app accumulate all available resources and increases fairness
* for all apps.
*/
- abstract long getMaxSatiatedBalance();
+ abstract long getMaxSatiatedBalance(int userId, @NonNull String pkgName);
/**
* Returns the maximum number of cakes that should be consumed during a full 100% discharge
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 7a7d669..2b82722 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -29,6 +29,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AlarmManager;
+import android.app.AppOpsManager;
import android.app.tare.IEconomyManager;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
@@ -64,6 +65,8 @@
import android.util.SparseSetArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsService;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
@@ -119,6 +122,7 @@
private final PackageManager mPackageManager;
private final PackageManagerInternal mPackageManagerInternal;
+ private IAppOpsService mAppOpsService;
private IDeviceIdleController mDeviceIdleController;
private final Agent mAgent;
@@ -145,6 +149,12 @@
private final CopyOnWriteArraySet<TareStateChangeListener> mStateChangeListeners =
new CopyOnWriteArraySet<>();
+ /**
+ * List of packages that are fully restricted and shouldn't be allowed to run in the background.
+ */
+ @GuardedBy("mLock")
+ private final SparseSetArray<String> mRestrictedApps = new SparseSetArray<>();
+
/** List of packages that are "exempted" from battery restrictions. */
// TODO(144864180): include userID
@GuardedBy("mLock")
@@ -160,6 +170,30 @@
@GuardedBy("mLock")
private int mCurrentBatteryLevel;
+ private final IAppOpsCallback mApbListener = new IAppOpsCallback.Stub() {
+ @Override
+ public void opChanged(int op, int uid, String packageName) {
+ boolean restricted = false;
+ try {
+ restricted = mAppOpsService.checkOperation(
+ AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName)
+ != AppOpsManager.MODE_ALLOWED;
+ } catch (RemoteException e) {
+ // Shouldn't happen
+ }
+ final int userId = UserHandle.getUserId(uid);
+ synchronized (mLock) {
+ if (restricted) {
+ if (mRestrictedApps.add(userId, packageName)) {
+ mAgent.onAppRestrictedLocked(userId, packageName);
+ }
+ } else if (mRestrictedApps.remove(UserHandle.getUserId(uid), packageName)) {
+ mAgent.onAppUnrestrictedLocked(userId, packageName);
+ }
+ }
+ }
+ };
+
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Nullable
private String getPackageName(Intent intent) {
@@ -280,9 +314,11 @@
switch (phase) {
case PHASE_SYSTEM_SERVICES_READY:
- mConfigObserver.start();
+ mAppOpsService = IAppOpsService.Stub.asInterface(
+ ServiceManager.getService(Context.APP_OPS_SERVICE));
mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
+ mConfigObserver.start();
onBootPhaseSystemServicesReady();
break;
case PHASE_THIRD_PARTY_APPS_CAN_START:
@@ -365,6 +401,12 @@
}
}
+ boolean isPackageRestricted(final int userId, @NonNull String pkgName) {
+ synchronized (mLock) {
+ return mRestrictedApps.contains(userId, pkgName);
+ }
+ }
+
boolean isSystem(final int userId, @NonNull String pkgName) {
if ("android".equals(pkgName)) {
return true;
@@ -711,6 +753,13 @@
UsageStatsManagerInternal usmi = LocalServices.getService(UsageStatsManagerInternal.class);
usmi.registerListener(mSurveillanceAgent);
+
+ try {
+ mAppOpsService
+ .startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null, mApbListener);
+ } catch (RemoteException e) {
+ // shouldn't happen.
+ }
}
/** Perform long-running and/or heavy setup work. This should be called off the main thread. */
@@ -734,6 +783,9 @@
// Reset the consumption limit since several factors may have changed.
mScribe.setConsumptionLimitLocked(
mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
+ } else {
+ // Adjust the supply in case battery level changed while the device was off.
+ adjustCreditSupplyLocked(true);
}
}
scheduleUnusedWealthReclamationLocked();
@@ -815,6 +867,11 @@
UsageStatsManagerInternal usmi =
LocalServices.getService(UsageStatsManagerInternal.class);
usmi.unregisterListener(mSurveillanceAgent);
+ try {
+ mAppOpsService.stopWatchingMode(mApbListener);
+ } catch (RemoteException e) {
+ // shouldn't happen.
+ }
}
synchronized (mPackageToUidCache) {
mPackageToUidCache.clear();
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
index 03c5fdd..cbb88c0 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
@@ -172,6 +172,9 @@
@Override
long getMinSatiatedBalance(final int userId, @NonNull final String pkgName) {
+ if (mIrs.isPackageRestricted(userId, pkgName)) {
+ return 0;
+ }
if (mIrs.isPackageExempted(userId, pkgName)) {
return mMinSatiatedBalanceExempted;
}
@@ -180,7 +183,10 @@
}
@Override
- long getMaxSatiatedBalance() {
+ long getMaxSatiatedBalance(int userId, @NonNull String pkgName) {
+ if (mIrs.isPackageRestricted(userId, pkgName)) {
+ return 0;
+ }
return mMaxSatiatedBalance;
}
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
index ed915cd..2cae83f 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
@@ -19,6 +19,7 @@
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static com.android.server.tare.TareUtils.appToString;
+import static com.android.server.tare.TareUtils.cakeToString;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -136,9 +137,15 @@
@GuardedBy("mIrs.getLock()")
void adjustRemainingConsumableCakesLocked(long delta) {
- if (delta != 0) {
- // No point doing any work if the change is 0.
- mRemainingConsumableCakes += delta;
+ final long staleCakes = mRemainingConsumableCakes;
+ mRemainingConsumableCakes += delta;
+ if (mRemainingConsumableCakes < 0) {
+ Slog.w(TAG, "Overdrew consumable cakes by " + cakeToString(-mRemainingConsumableCakes));
+ // A negative value would interfere with allowing free actions, so set the minimum as 0.
+ mRemainingConsumableCakes = 0;
+ }
+ if (mRemainingConsumableCakes != staleCakes) {
+ // No point doing any work if there was no functional change.
postWrite();
}
}
diff --git a/cmds/idmap2/include/idmap2/FabricatedOverlay.h b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
index 65916d7..2fc4d43 100644
--- a/cmds/idmap2/include/idmap2/FabricatedOverlay.h
+++ b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
@@ -66,18 +66,21 @@
private:
struct SerializedData {
- std::unique_ptr<uint8_t[]> data;
- size_t data_size;
- uint32_t crc;
- };
+ std::unique_ptr<uint8_t[]> pb_data;
+ size_t pb_data_size;
+ uint32_t pb_crc;
+ std::string sp_data;
+ };
Result<SerializedData*> InitializeData() const;
Result<uint32_t> GetCrc() const;
explicit FabricatedOverlay(pb::FabricatedOverlay&& overlay,
+ std::string&& string_pool_data_,
std::optional<uint32_t> crc_from_disk = {});
pb::FabricatedOverlay overlay_pb_;
+ std::string string_pool_data_;
std::optional<uint32_t> crc_from_disk_;
mutable std::optional<SerializedData> data_;
diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
index 4d49674..5bbe085 100644
--- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -17,8 +17,9 @@
#include "idmap2/FabricatedOverlay.h"
#include <androidfw/ResourceUtils.h>
+#include <androidfw/StringPool.h>
#include <google/protobuf/io/coded_stream.h>
-#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <utils/ByteOrder.h>
#include <zlib.h>
@@ -30,6 +31,8 @@
namespace android::idmap2 {
+constexpr auto kBufferSize = 1024;
+
namespace {
bool Read32(std::istream& stream, uint32_t* out) {
uint32_t value;
@@ -47,8 +50,11 @@
} // namespace
FabricatedOverlay::FabricatedOverlay(pb::FabricatedOverlay&& overlay,
+ std::string&& string_pool_data,
std::optional<uint32_t> crc_from_disk)
- : overlay_pb_(std::forward<pb::FabricatedOverlay>(overlay)), crc_from_disk_(crc_from_disk) {
+ : overlay_pb_(std::forward<pb::FabricatedOverlay>(overlay)),
+ string_pool_data_(std::move(string_pool_data)),
+ crc_from_disk_(crc_from_disk) {
}
FabricatedOverlay::Builder::Builder(const std::string& package_name, const std::string& name,
@@ -76,7 +82,11 @@
}
Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() {
- std::map<std::string, std::map<std::string, std::map<std::string, TargetValue>>> entries;
+ using EntryMap = std::map<std::string, TargetValue>;
+ using TypeMap = std::map<std::string, EntryMap>;
+ using PackageMap = std::map<std::string, TypeMap>;
+ PackageMap package_map;
+ android::StringPool string_pool;
for (const auto& res_entry : entries_) {
StringPiece package_substr;
StringPiece type_name;
@@ -96,11 +106,10 @@
return Error("resource name '%s' missing entry name", res_entry.resource_name.c_str());
}
- auto package = entries.find(package_name);
- if (package == entries.end()) {
- package = entries
- .insert(std::make_pair(
- package_name, std::map<std::string, std::map<std::string, TargetValue>>()))
+ auto package = package_map.find(package_name);
+ if (package == package_map.end()) {
+ package = package_map
+ .insert(std::make_pair(package_name, TypeMap()))
.first;
}
@@ -108,7 +117,7 @@
if (type == package->second.end()) {
type =
package->second
- .insert(std::make_pair(type_name.to_string(), std::map<std::string, TargetValue>()))
+ .insert(std::make_pair(type_name.to_string(), EntryMap()))
.first;
}
@@ -127,25 +136,32 @@
overlay_pb.set_target_package_name(target_package_name_);
overlay_pb.set_target_overlayable(target_overlayable_);
- for (const auto& package : entries) {
+ for (auto& package : package_map) {
auto package_pb = overlay_pb.add_packages();
package_pb->set_name(package.first);
- for (const auto& type : package.second) {
+ for (auto& type : package.second) {
auto type_pb = package_pb->add_types();
type_pb->set_name(type.first);
- for (const auto& entry : type.second) {
+ for (auto& entry : type.second) {
auto entry_pb = type_pb->add_entries();
entry_pb->set_name(entry.first);
pb::ResourceValue* value = entry_pb->mutable_res_value();
value->set_data_type(entry.second.data_type);
- value->set_data_value(entry.second.data_value);
+ if (entry.second.data_type == Res_value::TYPE_STRING) {
+ auto ref = string_pool.MakeRef(entry.second.data_string_value);
+ value->set_data_value(ref.index());
+ } else {
+ value->set_data_value(entry.second.data_value);
+ }
}
}
}
- return FabricatedOverlay(std::move(overlay_pb));
+ android::BigBuffer string_buffer(kBufferSize);
+ android::StringPool::FlattenUtf8(&string_buffer, string_pool, nullptr);
+ return FabricatedOverlay(std::move(overlay_pb), string_buffer.to_string());
}
Result<FabricatedOverlay> FabricatedOverlay::FromBinaryStream(std::istream& stream) {
@@ -163,48 +179,67 @@
return Error("Failed to read fabricated overlay version.");
}
- if (version != 1) {
+ if (version != 1 && version != 2) {
return Error("Invalid fabricated overlay version '%u'.", version);
}
uint32_t crc;
if (!Read32(stream, &crc)) {
- return Error("Failed to read fabricated overlay version.");
+ return Error("Failed to read fabricated overlay crc.");
}
pb::FabricatedOverlay overlay{};
- if (!overlay.ParseFromIstream(&stream)) {
- return Error("Failed read fabricated overlay proto.");
+ std::string sp_data;
+ if (version == 2) {
+ uint32_t sp_size;
+ if (!Read32(stream, &sp_size)) {
+ return Error("Failed read string pool size.");
+ }
+ std::string buf(sp_size, '\0');
+ if (!stream.read(buf.data(), sp_size)) {
+ return Error("Failed to read string pool.");
+ }
+ sp_data = buf;
+
+ if (!overlay.ParseFromIstream(&stream)) {
+ return Error("Failed read fabricated overlay proto.");
+ }
+ } else {
+ if (!overlay.ParseFromIstream(&stream)) {
+ return Error("Failed read fabricated overlay proto.");
+ }
}
// If the proto version is the latest version, then the contents of the proto must be the same
// when the proto is re-serialized; otherwise, the crc must be calculated because migrating the
// proto to the latest version will likely change the contents of the fabricated overlay.
- return FabricatedOverlay(std::move(overlay), version == kFabricatedOverlayCurrentVersion
+ return FabricatedOverlay(std::move(overlay), std::move(sp_data),
+ version == kFabricatedOverlayCurrentVersion
? std::optional<uint32_t>(crc)
: std::nullopt);
}
Result<FabricatedOverlay::SerializedData*> FabricatedOverlay::InitializeData() const {
if (!data_.has_value()) {
- auto size = overlay_pb_.ByteSizeLong();
- auto data = std::unique_ptr<uint8_t[]>(new uint8_t[size]);
+ auto pb_size = overlay_pb_.ByteSizeLong();
+ auto pb_data = std::unique_ptr<uint8_t[]>(new uint8_t[pb_size]);
// Ensure serialization is deterministic
- google::protobuf::io::ArrayOutputStream array_stream(data.get(), size);
+ google::protobuf::io::ArrayOutputStream array_stream(pb_data.get(), pb_size);
google::protobuf::io::CodedOutputStream output_stream(&array_stream);
output_stream.SetSerializationDeterministic(true);
overlay_pb_.SerializeWithCachedSizes(&output_stream);
- if (output_stream.HadError() || size != output_stream.ByteCount()) {
+ if (output_stream.HadError() || pb_size != output_stream.ByteCount()) {
return Error("Failed to serialize fabricated overlay.");
}
// Calculate the crc using the proto data and the version.
- uint32_t crc = crc32(0L, Z_NULL, 0);
- crc = crc32(crc, reinterpret_cast<const uint8_t*>(&kFabricatedOverlayCurrentVersion),
+ uint32_t pb_crc = crc32(0L, Z_NULL, 0);
+ pb_crc = crc32(pb_crc, reinterpret_cast<const uint8_t*>(&kFabricatedOverlayCurrentVersion),
sizeof(uint32_t));
- crc = crc32(crc, data.get(), size);
- data_ = SerializedData{std::move(data), size, crc};
+ pb_crc = crc32(pb_crc, pb_data.get(), pb_size);
+
+ data_ = SerializedData{std::move(pb_data), pb_size, pb_crc, string_pool_data_};
}
return &(*data_);
}
@@ -216,7 +251,7 @@
if (!data) {
return data.GetError();
}
- return (*data)->crc;
+ return (*data)->pb_crc;
}
Result<Unit> FabricatedOverlay::ToBinaryStream(std::ostream& stream) const {
@@ -227,8 +262,13 @@
Write32(stream, kFabricatedOverlayMagic);
Write32(stream, kFabricatedOverlayCurrentVersion);
- Write32(stream, (*data)->crc);
- stream.write(reinterpret_cast<const char*>((*data)->data.get()), (*data)->data_size);
+ Write32(stream, (*data)->pb_crc);
+ Write32(stream, (*data)->sp_data.length());
+ stream.write((*data)->sp_data.data(), (*data)->sp_data.length());
+ if (stream.bad()) {
+ return Error("Failed to write string pool data.");
+ }
+ stream.write(reinterpret_cast<const char*>((*data)->pb_data.get()), (*data)->pb_data_size);
if (stream.bad()) {
return Error("Failed to write serialized fabricated overlay.");
}
@@ -295,6 +335,14 @@
}
}
}
+ const uint32_t string_pool_data_length = overlay_.string_pool_data_.length();
+ result.string_pool_data = OverlayData::InlineStringPoolData{
+ .data = std::unique_ptr<uint8_t[]>(new uint8_t[string_pool_data_length]),
+ .data_length = string_pool_data_length,
+ .string_pool_offset = 0,
+ };
+ memcpy(result.string_pool_data->data.get(), overlay_.string_pool_data_.data(),
+ string_pool_data_length);
return result;
}
diff --git a/cmds/idmap2/tests/FabricatedOverlayTests.cpp b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
index 468ea0c..91331ca 100644
--- a/cmds/idmap2/tests/FabricatedOverlayTests.cpp
+++ b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
@@ -46,6 +46,7 @@
.SetResourceValue("com.example.target:integer/int1", Res_value::TYPE_INT_DEC, 1U)
.SetResourceValue("com.example.target.split:integer/int2", Res_value::TYPE_INT_DEC, 2U)
.SetResourceValue("string/int3", Res_value::TYPE_REFERENCE, 0x7f010000)
+ .SetResourceValue("com.example.target:string/string1", Res_value::TYPE_STRING, "foobar")
.Build();
ASSERT_TRUE(overlay);
auto container = FabricatedOverlayContainer::FromOverlay(std::move(*overlay));
@@ -59,8 +60,9 @@
auto pairs = container->GetOverlayData(*info);
ASSERT_TRUE(pairs);
- EXPECT_FALSE(pairs->string_pool_data.has_value());
- ASSERT_EQ(3U, pairs->pairs.size());
+ ASSERT_EQ(4U, pairs->pairs.size());
+ auto string_pool = ResStringPool(pairs->string_pool_data->data.get(),
+ pairs->string_pool_data->data_length, false);
auto& it = pairs->pairs[0];
ASSERT_EQ("com.example.target:integer/int1", it.resource_name);
@@ -77,6 +79,13 @@
ASSERT_EQ(Res_value::TYPE_REFERENCE, entry->data_type);
it = pairs->pairs[2];
+ ASSERT_EQ("com.example.target:string/string1", it.resource_name);
+ entry = std::get_if<TargetValue>(&it.value);
+ ASSERT_NE(nullptr, entry);
+ ASSERT_EQ(Res_value::TYPE_STRING, entry->data_type);
+ ASSERT_EQ(std::string("foobar"), string_pool.string8At(entry->data_value).value_or(""));
+
+ it = pairs->pairs[3];
ASSERT_EQ("com.example.target.split:integer/int2", it.resource_name);
entry = std::get_if<TargetValue>(&it.value);
ASSERT_NE(nullptr, entry);
@@ -104,6 +113,7 @@
FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
.SetOverlayable("TestResources")
.SetResourceValue("com.example.target:integer/int1", Res_value::TYPE_INT_DEC, 1U)
+ .SetResourceValue("com.example.target:string/string1", Res_value::TYPE_STRING, "foobar")
.Build();
ASSERT_TRUE(overlay);
TemporaryFile tf;
@@ -126,7 +136,9 @@
auto pairs = (*container)->GetOverlayData(*info);
ASSERT_TRUE(pairs) << pairs.GetErrorMessage();
- EXPECT_EQ(1U, pairs->pairs.size());
+ EXPECT_EQ(2U, pairs->pairs.size());
+ auto string_pool = ResStringPool(pairs->string_pool_data->data.get(),
+ pairs->string_pool_data->data_length, false);
auto& it = pairs->pairs[0];
ASSERT_EQ("com.example.target:integer/int1", it.resource_name);
@@ -134,6 +146,13 @@
ASSERT_NE(nullptr, entry);
EXPECT_EQ(1U, entry->data_value);
EXPECT_EQ(Res_value::TYPE_INT_DEC, entry->data_type);
+
+ it = pairs->pairs[1];
+ ASSERT_EQ("com.example.target:string/string1", it.resource_name);
+ entry = std::get_if<TargetValue>(&it.value);
+ ASSERT_NE(nullptr, entry);
+ ASSERT_EQ(Res_value::TYPE_STRING, entry->data_type);
+ ASSERT_EQ(std::string("foobar"), string_pool.string8At(entry->data_value).value_or(""));
}
} // namespace android::idmap2
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index 738b9cf..a3799f9 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -263,6 +263,7 @@
.SetOverlayable("TestResources")
.SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U)
.SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000)
+ .SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar")
.Build();
ASSERT_TRUE(frro);
@@ -288,12 +289,19 @@
ASSERT_EQ(data->GetTargetEntries().size(), 0U);
ASSERT_EQ(data->GetOverlayEntries().size(), 0U);
+ auto string_pool_data = data->GetStringPoolData();
+ auto string_pool = ResStringPool(string_pool_data.data(), string_pool_data.size(), false);
+
+
const auto& target_inline_entries = data->GetTargetInlineEntries();
- ASSERT_EQ(target_inline_entries.size(), 2U);
+ ASSERT_EQ(target_inline_entries.size(), 3U);
ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::integer::int1,
Res_value::TYPE_INT_DEC, 2U);
ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[1], R::target::string::str1,
Res_value::TYPE_REFERENCE, 0x7f010000);
+ ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[2], R::target::string::str2,
+ Res_value::TYPE_STRING,
+ (uint32_t) (string_pool.indexOfString(u"foobar", 6)).value_or(-1));
}
TEST(IdmapTests, FailCreateIdmapInvalidName) {
diff --git a/cmds/idmap2/tests/R.h b/cmds/idmap2/tests/R.h
index 89219c9..ad998b9 100644
--- a/cmds/idmap2/tests/R.h
+++ b/cmds/idmap2/tests/R.h
@@ -41,6 +41,7 @@
constexpr ResourceId policy_system = 0x7f02000c;
constexpr ResourceId policy_system_vendor = 0x7f02000d;
constexpr ResourceId str1 = 0x7f02000e;
+ constexpr ResourceId str2 = 0x7f02000f;
constexpr ResourceId str3 = 0x7f020010;
constexpr ResourceId str4 = 0x7f020011;
} // namespace string
diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp
index 32b3d13..c05abcf 100644
--- a/cmds/idmap2/tests/ResourceMappingTests.cpp
+++ b/cmds/idmap2/tests/ResourceMappingTests.cpp
@@ -196,6 +196,7 @@
.SetOverlayable("TestResources")
.SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U)
.SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000)
+ .SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar")
.Build();
ASSERT_TRUE(frro);
@@ -209,9 +210,14 @@
ASSERT_TRUE(resources) << resources.GetErrorMessage();
auto& res = *resources;
- ASSERT_EQ(res.GetTargetToOverlayMap().size(), 2U);
+ auto string_pool_data = res.GetStringPoolData();
+ auto string_pool = ResStringPool(string_pool_data.data(), string_pool_data.size(), false);
+
+ ASSERT_EQ(res.GetTargetToOverlayMap().size(), 3U);
ASSERT_EQ(res.GetOverlayToTargetMap().size(), 0U);
ASSERT_RESULT(MappingExists(res, R::target::string::str1, Res_value::TYPE_REFERENCE, 0x7f010000));
+ ASSERT_RESULT(MappingExists(res, R::target::string::str2, Res_value::TYPE_STRING,
+ (uint32_t) (string_pool.indexOfString(u"foobar", 6)).value_or(-1)));
ASSERT_RESULT(MappingExists(res, R::target::integer::int1, Res_value::TYPE_INT_DEC, 2U));
}
diff --git a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
index b1b432b..6fd2bf2 100644
--- a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
+++ b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
@@ -380,7 +380,7 @@
Tracer.trace();
Display display = getAutomatorBridge().getDefaultDisplay();
Point p = new Point();
- display.getSize(p);
+ display.getRealSize(p);
return p.x;
}
@@ -394,7 +394,7 @@
Tracer.trace();
Display display = getAutomatorBridge().getDefaultDisplay();
Point p = new Point();
- display.getSize(p);
+ display.getRealSize(p);
return p.y;
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 4734e8a..32bdec0 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -14006,49 +14006,49 @@
public final class Bitmap implements android.os.Parcelable {
method @NonNull public android.graphics.Bitmap asShared();
- method @WorkerThread public boolean compress(android.graphics.Bitmap.CompressFormat, int, java.io.OutputStream);
- method public android.graphics.Bitmap copy(android.graphics.Bitmap.Config, boolean);
- method public void copyPixelsFromBuffer(java.nio.Buffer);
- method public void copyPixelsToBuffer(java.nio.Buffer);
- method public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Bitmap);
- method public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Bitmap, int, int, int, int);
- method public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Bitmap, int, int, int, int, @Nullable android.graphics.Matrix, boolean);
- method public static android.graphics.Bitmap createBitmap(int, int, @NonNull android.graphics.Bitmap.Config);
- method public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, int, int, @NonNull android.graphics.Bitmap.Config);
- method public static android.graphics.Bitmap createBitmap(int, int, @NonNull android.graphics.Bitmap.Config, boolean);
- method public static android.graphics.Bitmap createBitmap(int, int, @NonNull android.graphics.Bitmap.Config, boolean, @NonNull android.graphics.ColorSpace);
- method public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, int, int, @NonNull android.graphics.Bitmap.Config, boolean);
- method public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, int, int, @NonNull android.graphics.Bitmap.Config, boolean, @NonNull android.graphics.ColorSpace);
- method public static android.graphics.Bitmap createBitmap(@ColorInt @NonNull int[], int, int, int, int, @NonNull android.graphics.Bitmap.Config);
- method public static android.graphics.Bitmap createBitmap(@NonNull android.util.DisplayMetrics, @ColorInt @NonNull int[], int, int, int, int, @NonNull android.graphics.Bitmap.Config);
- method public static android.graphics.Bitmap createBitmap(@ColorInt @NonNull int[], int, int, android.graphics.Bitmap.Config);
- method public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, @ColorInt @NonNull int[], int, int, @NonNull android.graphics.Bitmap.Config);
+ method @WorkerThread public boolean compress(@NonNull android.graphics.Bitmap.CompressFormat, int, @NonNull java.io.OutputStream);
+ method public android.graphics.Bitmap copy(@NonNull android.graphics.Bitmap.Config, boolean);
+ method public void copyPixelsFromBuffer(@NonNull java.nio.Buffer);
+ method public void copyPixelsToBuffer(@NonNull java.nio.Buffer);
+ method @NonNull public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Bitmap);
+ method @NonNull public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Bitmap, int, int, int, int);
+ method @NonNull public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Bitmap, int, int, int, int, @Nullable android.graphics.Matrix, boolean);
+ method @NonNull public static android.graphics.Bitmap createBitmap(int, int, @NonNull android.graphics.Bitmap.Config);
+ method @NonNull public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, int, int, @NonNull android.graphics.Bitmap.Config);
+ method @NonNull public static android.graphics.Bitmap createBitmap(int, int, @NonNull android.graphics.Bitmap.Config, boolean);
+ method @NonNull public static android.graphics.Bitmap createBitmap(int, int, @NonNull android.graphics.Bitmap.Config, boolean, @NonNull android.graphics.ColorSpace);
+ method @NonNull public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, int, int, @NonNull android.graphics.Bitmap.Config, boolean);
+ method @NonNull public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, int, int, @NonNull android.graphics.Bitmap.Config, boolean, @NonNull android.graphics.ColorSpace);
+ method @NonNull public static android.graphics.Bitmap createBitmap(@ColorInt @NonNull int[], int, int, int, int, @NonNull android.graphics.Bitmap.Config);
+ method @NonNull public static android.graphics.Bitmap createBitmap(@NonNull android.util.DisplayMetrics, @ColorInt @NonNull int[], int, int, int, int, @NonNull android.graphics.Bitmap.Config);
+ method @NonNull public static android.graphics.Bitmap createBitmap(@ColorInt @NonNull int[], int, int, android.graphics.Bitmap.Config);
+ method @NonNull public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, @ColorInt @NonNull int[], int, int, @NonNull android.graphics.Bitmap.Config);
method @NonNull public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Picture);
method @NonNull public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Picture, int, int, @NonNull android.graphics.Bitmap.Config);
- method public static android.graphics.Bitmap createScaledBitmap(@NonNull android.graphics.Bitmap, int, int, boolean);
+ method @NonNull public static android.graphics.Bitmap createScaledBitmap(@NonNull android.graphics.Bitmap, int, int, boolean);
method public int describeContents();
method public void eraseColor(@ColorInt int);
method public void eraseColor(@ColorLong long);
- method @CheckResult public android.graphics.Bitmap extractAlpha();
- method @CheckResult public android.graphics.Bitmap extractAlpha(android.graphics.Paint, int[]);
+ method @CheckResult @NonNull public android.graphics.Bitmap extractAlpha();
+ method @CheckResult @NonNull public android.graphics.Bitmap extractAlpha(@Nullable android.graphics.Paint, int[]);
method public int getAllocationByteCount();
method public int getByteCount();
method @NonNull public android.graphics.Color getColor(int, int);
method @Nullable public android.graphics.ColorSpace getColorSpace();
- method public android.graphics.Bitmap.Config getConfig();
+ method @NonNull public android.graphics.Bitmap.Config getConfig();
method public int getDensity();
method public int getGenerationId();
method @NonNull public android.hardware.HardwareBuffer getHardwareBuffer();
method public int getHeight();
- method public byte[] getNinePatchChunk();
+ method @Nullable public byte[] getNinePatchChunk();
method @ColorInt public int getPixel(int, int);
- method public void getPixels(@ColorInt int[], int, int, int, int, int, int);
+ method public void getPixels(@ColorInt @NonNull int[], int, int, int, int, int, int);
method public int getRowBytes();
- method public int getScaledHeight(android.graphics.Canvas);
- method public int getScaledHeight(android.util.DisplayMetrics);
+ method public int getScaledHeight(@NonNull android.graphics.Canvas);
+ method public int getScaledHeight(@NonNull android.util.DisplayMetrics);
method public int getScaledHeight(int);
- method public int getScaledWidth(android.graphics.Canvas);
- method public int getScaledWidth(android.util.DisplayMetrics);
+ method public int getScaledWidth(@NonNull android.graphics.Canvas);
+ method public int getScaledWidth(@NonNull android.util.DisplayMetrics);
method public int getScaledWidth(int);
method public int getWidth();
method public boolean hasAlpha();
@@ -14057,21 +14057,21 @@
method public boolean isPremultiplied();
method public boolean isRecycled();
method public void prepareToDraw();
- method public void reconfigure(int, int, android.graphics.Bitmap.Config);
+ method public void reconfigure(int, int, @NonNull android.graphics.Bitmap.Config);
method public void recycle();
- method public boolean sameAs(android.graphics.Bitmap);
+ method @WorkerThread public boolean sameAs(@Nullable android.graphics.Bitmap);
method public void setColorSpace(@NonNull android.graphics.ColorSpace);
- method public void setConfig(android.graphics.Bitmap.Config);
+ method public void setConfig(@NonNull android.graphics.Bitmap.Config);
method public void setDensity(int);
method public void setHasAlpha(boolean);
method public void setHasMipMap(boolean);
method public void setHeight(int);
method public void setPixel(int, int, @ColorInt int);
- method public void setPixels(@ColorInt int[], int, int, int, int, int, int);
+ method public void setPixels(@ColorInt @NonNull int[], int, int, int, int, int, int);
method public void setPremultiplied(boolean);
method public void setWidth(int);
method @Nullable public static android.graphics.Bitmap wrapHardwareBuffer(@NonNull android.hardware.HardwareBuffer, @Nullable android.graphics.ColorSpace);
- method public void writeToParcel(android.os.Parcel, int);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.graphics.Bitmap> CREATOR;
field public static final int DENSITY_NONE = 0; // 0x0
}
@@ -41342,6 +41342,8 @@
field public static final String KEY_CARRIER_INSTANT_LETTERING_ESCAPED_CHARS_STRING = "carrier_instant_lettering_escaped_chars_string";
field public static final String KEY_CARRIER_INSTANT_LETTERING_INVALID_CHARS_STRING = "carrier_instant_lettering_invalid_chars_string";
field public static final String KEY_CARRIER_INSTANT_LETTERING_LENGTH_LIMIT_INT = "carrier_instant_lettering_length_limit_int";
+ field public static final String KEY_CARRIER_METERED_APN_TYPES_STRINGS = "carrier_metered_apn_types_strings";
+ field public static final String KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS = "carrier_metered_roaming_apn_types_strings";
field public static final String KEY_CARRIER_NAME_OVERRIDE_BOOL = "carrier_name_override_bool";
field public static final String KEY_CARRIER_NAME_STRING = "carrier_name_string";
field public static final String KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY = "carrier_nr_availabilities_int_array";
@@ -49957,7 +49959,6 @@
method public void invalidate();
method public void invalidateDrawable(@NonNull android.graphics.drawable.Drawable);
method public void invalidateOutline();
- method public boolean isAccessibilityDataPrivate();
method public boolean isAccessibilityFocused();
method public boolean isAccessibilityHeading();
method public boolean isActivated();
@@ -50135,7 +50136,6 @@
method public void scrollTo(int, int);
method public void sendAccessibilityEvent(int);
method public void sendAccessibilityEventUnchecked(android.view.accessibility.AccessibilityEvent);
- method public void setAccessibilityDataPrivate(int);
method public void setAccessibilityDelegate(@Nullable android.view.View.AccessibilityDelegate);
method public void setAccessibilityHeading(boolean);
method public void setAccessibilityLiveRegion(int);
@@ -50316,9 +50316,6 @@
method @CallSuper protected boolean verifyDrawable(@NonNull android.graphics.drawable.Drawable);
method @Deprecated public boolean willNotCacheDrawing();
method public boolean willNotDraw();
- field public static final int ACCESSIBILITY_DATA_PRIVATE_AUTO = 0; // 0x0
- field public static final int ACCESSIBILITY_DATA_PRIVATE_NO = 2; // 0x2
- field public static final int ACCESSIBILITY_DATA_PRIVATE_YES = 1; // 0x1
field public static final int ACCESSIBILITY_LIVE_REGION_ASSERTIVE = 2; // 0x2
field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
@@ -51771,11 +51768,9 @@
method public int getSpeechStateChangeTypes();
method public int getWindowChanges();
method public void initFromParcel(android.os.Parcel);
- method public boolean isAccessibilityDataPrivate();
method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain(int);
method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain(android.view.accessibility.AccessibilityEvent);
method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain();
- method public void setAccessibilityDataPrivate(boolean);
method public void setAction(int);
method public void setContentChangeTypes(int);
method public void setEventTime(long);
@@ -51866,7 +51861,6 @@
method public static boolean isAccessibilityButtonSupported();
method public boolean isAudioDescriptionRequested();
method public boolean isEnabled();
- method public boolean isRequestFromAccessibilityTool();
method public boolean isTouchExplorationEnabled();
method public void removeAccessibilityRequestPreparer(android.view.accessibility.AccessibilityRequestPreparer);
method public boolean removeAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index c87ea2a..d758dd5 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -90,7 +90,6 @@
public class AccessibilityServiceInfo implements android.os.Parcelable {
method @NonNull public android.content.ComponentName getComponentName();
- method public void setAccessibilityTool(boolean);
}
}
@@ -132,6 +131,7 @@
method public static void resumeAppSwitches() throws android.os.RemoteException;
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void setStopUserOnSwitch(int);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean startUserInBackgroundOnSecondaryDisplay(int, int);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int, boolean);
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle();
@@ -1876,6 +1876,7 @@
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
method public static boolean isSplitSystemUser();
+ method public static boolean isUsersOnSecondaryDisplaysEnabled();
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException;
}
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 8f6bfd3..2e89ce8 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -784,7 +784,6 @@
mNonInteractiveUiTimeout = other.mNonInteractiveUiTimeout;
mInteractiveUiTimeout = other.mInteractiveUiTimeout;
flags = other.flags;
- mIsAccessibilityTool = other.mIsAccessibilityTool;
}
private boolean isRequestAccessibilityButtonChangeEnabled(IPlatformCompat platformCompat) {
@@ -1113,26 +1112,6 @@
}
/**
- * Sets whether the service is used to assist users with disabilities.
- *
- * <p>
- * This property is normally provided in the service's {@link #mResolveInfo ResolveInfo}.
- * </p>
- *
- * <p>
- * This method is helpful for unit testing. However, this property is not dynamically
- * configurable by a standard {@link AccessibilityService} so it's not possible to update the
- * copy held by the system with this method.
- * </p>
- *
- * @hide
- */
- @TestApi
- public void setAccessibilityTool(boolean isAccessibilityTool) {
- mIsAccessibilityTool = isAccessibilityTool;
- }
-
- /**
* Indicates if the service is used to assist users with disabilities.
*
* @return {@code true} if the property is set to true.
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 449729e..182b0a3 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4336,13 +4336,42 @@
@RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.CREATE_USERS})
public boolean switchUser(@NonNull UserHandle user) {
- if (user == null) {
- throw new IllegalArgumentException("UserHandle cannot be null.");
- }
+ Preconditions.checkNotNull(user, "UserHandle cannot be null.");
+
return switchUser(user.getIdentifier());
}
/**
+ * Starts the given user in background and associate the user with the given display.
+ *
+ * <p>This method will allow the user to launch activities on that display, and it's typically
+ * used only on automotive builds when the vehicle has multiple displays (you can verify if it's
+ * supported by calling {@link UserManager#isBackgroundUsersOnSecondaryDisplaysSupported()}).
+ *
+ * @return whether the user was started.
+ *
+ * @throws UnsupportedOperationException if the device does not support background users on
+ * secondary displays.
+ * @throws IllegalArgumentException if the display does not exist.
+ * @throws IllegalStateException if the user cannot be started on that display (for example, if
+ * there's already a user using that display or if the user is already associated with other
+ * display).
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS})
+ public boolean startUserInBackgroundOnSecondaryDisplay(@UserIdInt int userId,
+ int displayId) {
+ try {
+ return getService().startUserInBackgroundOnSecondaryDisplay(userId, displayId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Gets the message that is shown when a user is switched from.
*
* @hide
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 9210958..419b8e1 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -47,6 +47,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.BiFunction;
/**
* Activity manager local system service interface.
@@ -623,6 +624,11 @@
* broadcast my be sent to; any app Ids < {@link android.os.Process#FIRST_APPLICATION_UID} are
* automatically allowlisted.
*
+ * @param filterExtrasForReceiver A function to filter intent extras for the given receiver by
+ * using the rules of package visibility. Returns extras with legitimate package info that the
+ * receiver is able to access, or {@code null} if none of the packages is visible to the
+ * receiver.
+ *
* @see com.android.server.am.ActivityManagerService#broadcastIntentWithFeature(
* IApplicationThread, String, Intent, String, IIntentReceiver, int, String, Bundle,
* String[], int, Bundle, boolean, boolean, int)
@@ -630,7 +636,9 @@
public abstract int broadcastIntent(Intent intent,
IIntentReceiver resultTo,
String[] requiredPermissions, boolean serialized,
- int userId, int[] appIdAllowList, @Nullable Bundle bOptions);
+ int userId, int[] appIdAllowList,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+ @Nullable Bundle bOptions);
/**
* Add uid to the ActivityManagerService PendingStartActivityUids list.
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 8367441..bd999fc 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -760,4 +760,15 @@
* </p>
*/
int getBackgroundRestrictionExemptionReason(int uid);
+
+ // Start (?) of T transactions
+ /**
+ * Similar to {@link #startUserInBackgroundWithListener(int userId, IProgressListener unlockProgressListener),
+ * but setting the user as the visible user of that display (i.e., allowing the user and its
+ * running profiles to launch activities on that display).
+ *
+ * <p>Typically used only by automotive builds when the vehicle has multiple displays.
+ */
+ boolean startUserInBackgroundOnSecondaryDisplay(int userid, int displayId);
+
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 36e1eee..4da957c 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -147,8 +147,6 @@
import android.net.PacProxyManager;
import android.net.TetheringManager;
import android.net.VpnManager;
-import android.net.lowpan.ILowpanManager;
-import android.net.lowpan.LowpanManager;
import android.net.vcn.IVcnManagementService;
import android.net.vcn.VcnManager;
import android.net.wifi.WifiFrameworkInitializer;
@@ -779,15 +777,6 @@
ctx.mMainThread.getHandler());
}});
- registerService(Context.LOWPAN_SERVICE, LowpanManager.class,
- new CachedServiceFetcher<LowpanManager>() {
- @Override
- public LowpanManager createService(ContextImpl ctx) throws ServiceNotFoundException {
- IBinder b = ServiceManager.getServiceOrThrow(Context.LOWPAN_SERVICE);
- ILowpanManager service = ILowpanManager.Stub.asInterface(b);
- return new LowpanManager(ctx.getOuterContext(), service);
- }});
-
registerService(Context.WIFI_NL80211_SERVICE, WifiNl80211Manager.class,
new CachedServiceFetcher<WifiNl80211Manager>() {
@Override
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 8d57e32..a045157 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -550,7 +550,6 @@
info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
| AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
| AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS);
- info.setAccessibilityTool(true);
try {
// Calling out with a lock held is fine since if the system
// process is gone the client calling in will be killed.
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 7247ef7..7092e43 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -29,7 +29,6 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.CryptoObject;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.os.Binder;
@@ -675,45 +674,6 @@
}
/**
- * Forwards BiometricStateListener to FaceService.
- *
- * @param listener new BiometricStateListener being added
- * @hide
- */
- public void registerBiometricStateListener(@NonNull BiometricStateListener listener) {
- try {
- mService.registerBiometricStateListener(listener);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Adds a callback that gets called when the service registers all of the face
- * authenticators (HALs).
- *
- * If the face authenticators are already registered when the callback is added, the
- * callback is invoked immediately.
- *
- * The callback is automatically removed after it's invoked.
- *
- * @hide
- */
- @RequiresPermission(USE_BIOMETRIC_INTERNAL)
- public void addAuthenticatorsRegisteredCallback(
- IFaceAuthenticatorsRegisteredCallback callback) {
- if (mService != null) {
- try {
- mService.addAuthenticatorsRegisteredCallback(callback);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- } else {
- Slog.w(TAG, "addAuthenticatorsRegisteredCallback(): Service not connected!");
- }
- }
-
- /**
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
diff --git a/core/java/android/hardware/face/IFaceAuthenticatorsRegisteredCallback.aidl b/core/java/android/hardware/face/IFaceAuthenticatorsRegisteredCallback.aidl
deleted file mode 100644
index 78f978d2..0000000
--- a/core/java/android/hardware/face/IFaceAuthenticatorsRegisteredCallback.aidl
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2022 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.hardware.face;
-
-import android.hardware.face.FaceSensorPropertiesInternal;
-import java.util.List;
-
-/**
- * Callback to notify FaceManager that FaceService has registered all of the
- * face authenticators (HALs).
- * See {@link android.hardware.face.IFaceService#registerAuthenticators}.
- *
- * @hide
- */
-oneway interface IFaceAuthenticatorsRegisteredCallback {
- /**
- * Notifies FaceManager that all of the face authenticators have been registered.
- *
- * @param sensors A consolidated list of sensor properties for all of the authenticators.
- */
- void onAllAuthenticatorsRegistered(in List<FaceSensorPropertiesInternal> sensors);
-}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 9b56f43..369248e 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -17,11 +17,9 @@
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
-import android.hardware.biometrics.IBiometricStateListener;
import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
-import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
import android.hardware.face.IFaceServiceReceiver;
import android.hardware.face.Face;
import android.hardware.face.FaceSensorPropertiesInternal;
@@ -165,11 +163,4 @@
// hidlSensors must be non-null and empty. See AuthService.java
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
void registerAuthenticators(in List<FaceSensorPropertiesInternal> hidlSensors);
-
- // Adds a callback which gets called when the service registers all of the face
- // authenticators. The callback is automatically removed after it's invoked.
- void addAuthenticatorsRegisteredCallback(IFaceAuthenticatorsRegisteredCallback callback);
-
- // Registers BiometricStateListener.
- void registerBiometricStateListener(IBiometricStateListener listener);
}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 1ba9a04..cc7ed18 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -202,10 +202,8 @@
void setSidefpsController(in ISidefpsController controller);
// Registers BiometricStateListener.
- @EnforcePermission("USE_BIOMETRIC_INTERNAL")
void registerBiometricStateListener(IBiometricStateListener listener);
// Sends a power button pressed event to all listeners.
- @EnforcePermission("USE_BIOMETRIC_INTERNAL")
oneway void onPowerPressed();
}
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 14082f3..b16360c 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -1463,6 +1463,18 @@
public static final native int killProcessGroup(int uid, int pid);
/**
+ * Freeze the cgroup for the given UID.
+ * This cgroup may contain child cgroups which will also be frozen. If this cgroup or its
+ * children contain processes with Binder interfaces, those interfaces should be frozen before
+ * the cgroup to avoid blocking synchronous callers indefinitely.
+ *
+ * @param uid The UID to be frozen
+ * @param freeze true = freeze; false = unfreeze
+ * @hide
+ */
+ public static final native void freezeCgroupUid(int uid, boolean freeze);
+
+ /**
* Remove all process groups. Expected to be called when ActivityManager
* is restarted.
* @hide
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 16352b8..99cf737 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2841,6 +2841,7 @@
/**
* @hide
*/
+ @TestApi
public static boolean isUsersOnSecondaryDisplaysEnabled() {
return SystemProperties.getBoolean("fw.users_on_secondary_displays",
Resources.getSystem()
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a7c7273..baf3eeca 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7822,6 +7822,16 @@
"accessibility_display_magnification_auto_update";
/**
+ * Accessibility Window Magnification Allow diagonal scrolling value. The value is boolean.
+ * 1 : on, 0 : off
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING =
+ "accessibility_allow_diagonal_scrolling";
+
+
+ /**
* Setting that specifies what mode the soft keyboard is in (default or hidden). Can be
* modified from an AccessibilityService using the SoftKeyboardController.
*
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index 3e0deeb..53ae657 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -35,6 +35,8 @@
void testDream(int userId, in ComponentName componentName);
@UnsupportedAppUsage
boolean isDreaming();
+ @UnsupportedAppUsage
+ boolean isDreamingOrInPreview();
void finishSelf(in IBinder token, boolean immediate);
void startDozing(in IBinder token, int screenState, int screenBrightness);
void stopDozing(in IBinder token);
diff --git a/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java b/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
index f028ed3..ad73a53 100644
--- a/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
+++ b/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
@@ -69,7 +69,7 @@
if (mToolbarCache.indexOfKey(callingUid) < 0) {
RemoteSelectionToolbar toolbar = new RemoteSelectionToolbar(this,
- widgetToken, showInfo.getHostInputToken(),
+ widgetToken, showInfo,
callbackWrapper, this::transferTouch);
mToolbarCache.put(callingUid, new Pair<>(widgetToken, toolbar));
}
diff --git a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
index d75fbc0..95bcda5 100644
--- a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
+++ b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
@@ -22,7 +22,6 @@
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
-import android.content.res.TypedArray;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.AnimatedVectorDrawable;
@@ -162,15 +161,14 @@
private final Rect mTempContentRectForRoot = new Rect();
private final int[] mTempCoords = new int[2];
- RemoteSelectionToolbar(Context context, long selectionToolbarToken, IBinder hostInputToken,
+ RemoteSelectionToolbar(Context context, long selectionToolbarToken, ShowInfo showInfo,
SelectionToolbarRenderService.RemoteCallbackWrapper callbackWrapper,
SelectionToolbarRenderService.TransferTouchListener transferTouchListener) {
- mContext = applyDefaultTheme(context);
+ mContext = applyDefaultTheme(context, showInfo.isIsLightTheme());
mSelectionToolbarToken = selectionToolbarToken;
mCallbackWrapper = callbackWrapper;
mTransferTouchListener = transferTouchListener;
- mHostInputToken = hostInputToken;
-
+ mHostInputToken = showInfo.getHostInputToken();
mContentContainer = createContentContainer(mContext);
mMarginHorizontal = mContext.getResources()
.getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin);
@@ -1359,12 +1357,9 @@
/**
* Returns a re-themed context with controlled look and feel for views.
*/
- private static Context applyDefaultTheme(Context originalContext) {
- TypedArray a = originalContext.obtainStyledAttributes(new int[]{R.attr.isLightTheme});
- boolean isLightTheme = a.getBoolean(0, true);
+ private static Context applyDefaultTheme(Context originalContext, boolean isLightTheme) {
int themeId =
isLightTheme ? R.style.Theme_DeviceDefault_Light : R.style.Theme_DeviceDefault;
- a.recycle();
return new ContextThemeWrapper(originalContext, themeId);
}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index eae951f..b73ff901 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -115,6 +115,11 @@
public static final String SETTINGS_ACCESSIBILITY_SIMPLE_CURSOR =
"settings_accessibility_simple_cursor";
+ /**
+ * Enable new language and keyboard settings UI
+ * @hide
+ */
+ public static final String SETTINGS_NEW_KEYBOARD_UI = "settings_new_keyboard_ui";
private static final Map<String, String> DEFAULT_FLAGS;
@@ -148,6 +153,7 @@
DEFAULT_FLAGS.put(SETTINGS_GUEST_MODE_UX_CHANGES, "true");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_CLEAR_CALLING, "false");
DEFAULT_FLAGS.put(SETTINGS_ACCESSIBILITY_SIMPLE_CURSOR, "false");
+ DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "false");
}
private static final Set<String> PERSISTENT_FLAGS;
@@ -161,6 +167,7 @@
PERSISTENT_FLAGS.add(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME);
PERSISTENT_FLAGS.add(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE);
PERSISTENT_FLAGS.add(SETTINGS_AUTO_TEXT_WRAPPING);
+ PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_UI);
}
/**
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 52dc342..a6f63e8 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -89,7 +89,9 @@
// Callbacks should have the same configuration of the flags below to allow satisfying a pending
// node request on prefetch
- private static final int FLAGS_AFFECTING_REPORTED_DATA = AccessibilityNodeInfo.FLAG_REPORT_MASK;
+ private static final int FLAGS_AFFECTING_REPORTED_DATA =
+ AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+ | AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
new ArrayList<AccessibilityNodeInfo>();
@@ -165,11 +167,6 @@
return (view != null) && (view.getWindowVisibility() == View.VISIBLE && view.isShown());
}
- private boolean isVisibleToAccessibilityService(View view) {
- return view != null && (!view.isAccessibilityDataPrivate()
- || mA11yManager.isRequestFromAccessibilityTool());
- }
-
public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
long accessibilityNodeId, Region interactiveRegion, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
@@ -361,7 +358,7 @@
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- setAccessibilityFetchFlags(flags);
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
requestedView = findViewByAccessibilityId(accessibilityViewId);
if (requestedView != null && isShown(requestedView)) {
requestedNode = populateAccessibilityNodeInfoForView(
@@ -374,7 +371,7 @@
mPrefetcher.prefetchAccessibilityNodeInfos(requestedView,
requestedNode == null ? null : new AccessibilityNodeInfo(requestedNode),
infos);
- resetAccessibilityFetchFlags();
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
}
}
} finally {
@@ -399,7 +396,7 @@
}
mPrefetcher.prefetchAccessibilityNodeInfos(requestedView,
requestedNode == null ? null : new AccessibilityNodeInfo(requestedNode), infos);
- resetAccessibilityFetchFlags();
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
updateInfosForViewPort(infos, spec, matrixValues, interactiveRegion);
final SatisfiedFindAccessibilityNodeByAccessibilityIdRequest satisfiedRequest =
getSatisfiedRequestInPrefetch(requestedNode == null ? null : requestedNode, infos,
@@ -481,7 +478,7 @@
|| viewId == null) {
return;
}
- setAccessibilityFetchFlags(flags);
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
final View root = findViewByAccessibilityId(accessibilityViewId);
if (root != null) {
final int resolvedViewId = root.getContext().getResources()
@@ -497,7 +494,7 @@
mAddNodeInfosForViewId.reset();
}
} finally {
- resetAccessibilityFetchFlags();
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
updateInfosForViewportAndReturnFindNodeResult(
infos, callback, interactionId, spec, matrixValues, interactiveRegion);
}
@@ -545,7 +542,7 @@
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- setAccessibilityFetchFlags(flags);
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
final View root = findViewByAccessibilityId(accessibilityViewId);
if (root != null && isShown(root)) {
AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
@@ -564,7 +561,7 @@
final int viewCount = foundViews.size();
for (int i = 0; i < viewCount; i++) {
View foundView = foundViews.get(i);
- if (isShown(foundView) && isVisibleToAccessibilityService(foundView)) {
+ if (isShown(foundView)) {
provider = foundView.getAccessibilityNodeProvider();
if (provider != null) {
List<AccessibilityNodeInfo> infosFromProvider =
@@ -582,7 +579,7 @@
}
}
} finally {
- resetAccessibilityFetchFlags();
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
updateInfosForViewportAndReturnFindNodeResult(
infos, callback, interactionId, spec, matrixValues, interactiveRegion);
}
@@ -630,7 +627,7 @@
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- setAccessibilityFetchFlags(flags);
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
final View root = findViewByAccessibilityId(accessibilityViewId);
if (root != null && isShown(root)) {
switch (focusType) {
@@ -645,9 +642,6 @@
if (!isShown(host)) {
break;
}
- if (!isVisibleToAccessibilityService(host)) {
- break;
- }
// If the host has a provider ask this provider to search for the
// focus instead fetching all provider nodes to do the search here.
AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
@@ -668,9 +662,6 @@
if (!isShown(target)) {
break;
}
- if (!isVisibleToAccessibilityService(target)) {
- break;
- }
AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
if (provider != null) {
focused = provider.findFocus(focusType);
@@ -684,7 +675,7 @@
}
}
} finally {
- resetAccessibilityFetchFlags();
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
updateInfoForViewportAndReturnFindNodeResult(
focused, callback, interactionId, spec, matrixValues, interactiveRegion);
}
@@ -731,7 +722,7 @@
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- setAccessibilityFetchFlags(flags);
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
final View root = findViewByAccessibilityId(accessibilityViewId);
if (root != null && isShown(root)) {
View nextView = root.focusSearch(direction);
@@ -740,7 +731,7 @@
}
}
} finally {
- resetAccessibilityFetchFlags();
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
updateInfoForViewportAndReturnFindNodeResult(
next, callback, interactionId, spec, matrixValues, interactiveRegion);
}
@@ -787,9 +778,9 @@
mViewRootImpl.mStopped || mViewRootImpl.mPausedForTransition) {
return;
}
- setAccessibilityFetchFlags(flags);
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
final View target = findViewByAccessibilityId(accessibilityViewId);
- if (target != null && isShown(target) && isVisibleToAccessibilityService(target)) {
+ if (target != null && isShown(target)) {
mA11yManager.notifyPerformingAction(action);
if (action == R.id.accessibilityActionClickOnClickableSpan) {
// Handle this hidden action separately
@@ -808,7 +799,7 @@
}
} finally {
try {
- resetAccessibilityFetchFlags();
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
callback.setPerformAccessibilityActionResult(succeeded, interactionId);
} catch (RemoteException re) {
/* ignore - the other side will time out */
@@ -832,9 +823,9 @@
return;
}
try {
- setAccessibilityFetchFlags(
- AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS);
- final View root = getRootView();
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags =
+ AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+ final View root = mViewRootImpl.mView;
if (root != null && isShown(root)) {
final View host = mViewRootImpl.mAccessibilityFocusedHost;
// If there is no accessibility focus host or it is not a descendant
@@ -858,7 +849,7 @@
}
}
} finally {
- resetAccessibilityFetchFlags();
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
}
}
@@ -878,7 +869,7 @@
|| mViewRootImpl.mStopped || mViewRootImpl.mPausedForTransition) {
return;
}
- final View root = getRootView();
+ final View root = mViewRootImpl.mView;
if (root != null && isShown(root)) {
// trigger ACTION_OUTSIDE to notify windows
final long now = SystemClock.uptimeMillis();
@@ -891,30 +882,12 @@
private View findViewByAccessibilityId(int accessibilityId) {
if (accessibilityId == AccessibilityNodeInfo.ROOT_ITEM_ID) {
- return getRootView();
+ return mViewRootImpl.mView;
} else {
return AccessibilityNodeIdManager.getInstance().findView(accessibilityId);
}
}
- private View getRootView() {
- if (!isVisibleToAccessibilityService(mViewRootImpl.mView)) {
- return null;
- }
- return mViewRootImpl.mView;
- }
-
- private void setAccessibilityFetchFlags(int flags) {
- mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
- mA11yManager.setRequestFromAccessibilityTool(
- (flags & AccessibilityNodeInfo.FLAG_SERVICE_IS_ACCESSIBILITY_TOOL) != 0);
- }
-
- private void resetAccessibilityFetchFlags() {
- mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
- mA11yManager.setRequestFromAccessibilityTool(false);
- }
-
// The boundInScreen includes magnification effect, so we need to normalize it before
// determine the visibility.
private void adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info,
@@ -1733,7 +1706,7 @@
@Override
public boolean test(View view) {
- if (view.getId() == mViewId && isShown(view) && isVisibleToAccessibilityService(view)) {
+ if (view.getId() == mViewId && isShown(view)) {
mInfos.add(view.createAccessibilityNodeInfo());
}
return false;
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 8f9c5fe..a664278 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -723,9 +723,9 @@
private void releaseSurfaces(boolean releaseSurfacePackage) {
mSurfaceAlpha = 1f;
- mSurface.destroy();
synchronized (mSurfaceControlLock) {
+ mSurface.destroy();
if (mBlastBufferQueue != null) {
mBlastBufferQueue.destroy();
mBlastBufferQueue = null;
@@ -774,99 +774,105 @@
Transaction surfaceUpdateTransaction) {
boolean realSizeChanged = false;
- mDrawingStopped = !mVisible;
+ mSurfaceLock.lock();
+ try {
+ mDrawingStopped = !mVisible;
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
- + "Cur surface: " + mSurface);
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "Cur surface: " + mSurface);
- // If we are creating the surface control or the parent surface has not
- // changed, then set relative z. Otherwise allow the parent
- // SurfaceChangedCallback to update the relative z. This is needed so that
- // we do not change the relative z before the server is ready to swap the
- // parent surface.
- if (creating) {
- updateRelativeZ(surfaceUpdateTransaction);
- if (mSurfacePackage != null) {
- reparentSurfacePackage(surfaceUpdateTransaction, mSurfacePackage);
+ // If we are creating the surface control or the parent surface has not
+ // changed, then set relative z. Otherwise allow the parent
+ // SurfaceChangedCallback to update the relative z. This is needed so that
+ // we do not change the relative z before the server is ready to swap the
+ // parent surface.
+ if (creating) {
+ updateRelativeZ(surfaceUpdateTransaction);
+ if (mSurfacePackage != null) {
+ reparentSurfacePackage(surfaceUpdateTransaction, mSurfacePackage);
+ }
}
- }
- mParentSurfaceSequenceId = viewRoot.getSurfaceSequenceId();
+ mParentSurfaceSequenceId = viewRoot.getSurfaceSequenceId();
- if (mViewVisibility) {
- surfaceUpdateTransaction.show(mSurfaceControl);
- } else {
- surfaceUpdateTransaction.hide(mSurfaceControl);
- }
-
-
-
- updateBackgroundVisibility(surfaceUpdateTransaction);
- updateBackgroundColor(surfaceUpdateTransaction);
- if (mUseAlpha) {
- float alpha = getFixedAlpha();
- surfaceUpdateTransaction.setAlpha(mSurfaceControl, alpha);
- mSurfaceAlpha = alpha;
- }
-
- surfaceUpdateTransaction.setCornerRadius(mSurfaceControl, mCornerRadius);
- if ((sizeChanged || hintChanged) && !creating) {
- setBufferSize(surfaceUpdateTransaction);
- }
- if (sizeChanged || creating || !isHardwareAccelerated()) {
- // Set a window crop when creating the surface or changing its size to
- // crop the buffer to the surface size since the buffer producer may
- // use SCALING_MODE_SCALE and submit a larger size than the surface
- // size.
- if (mClipSurfaceToBounds && mClipBounds != null) {
- surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mClipBounds);
+ if (mViewVisibility) {
+ surfaceUpdateTransaction.show(mSurfaceControl);
} else {
- surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mSurfaceWidth,
- mSurfaceHeight);
+ surfaceUpdateTransaction.hide(mSurfaceControl);
}
- surfaceUpdateTransaction.setDesintationFrame(mBlastSurfaceControl, mSurfaceWidth,
- mSurfaceHeight);
- if (isHardwareAccelerated()) {
- // This will consume the passed in transaction and the transaction will be
- // applied on a render worker thread.
- replacePositionUpdateListener(mSurfaceWidth, mSurfaceHeight);
+
+ updateBackgroundVisibility(surfaceUpdateTransaction);
+ updateBackgroundColor(surfaceUpdateTransaction);
+ if (mUseAlpha) {
+ float alpha = getFixedAlpha();
+ surfaceUpdateTransaction.setAlpha(mSurfaceControl, alpha);
+ mSurfaceAlpha = alpha;
+ }
+
+ surfaceUpdateTransaction.setCornerRadius(mSurfaceControl, mCornerRadius);
+ if ((sizeChanged || hintChanged) && !creating) {
+ setBufferSize(surfaceUpdateTransaction);
+ }
+ if (sizeChanged || creating || !isHardwareAccelerated()) {
+
+ // Set a window crop when creating the surface or changing its size to
+ // crop the buffer to the surface size since the buffer producer may
+ // use SCALING_MODE_SCALE and submit a larger size than the surface
+ // size.
+ if (mClipSurfaceToBounds && mClipBounds != null) {
+ surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mClipBounds);
+ } else {
+ surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mSurfaceWidth,
+ mSurfaceHeight);
+ }
+
+ surfaceUpdateTransaction.setDesintationFrame(mBlastSurfaceControl, mSurfaceWidth,
+ mSurfaceHeight);
+
+ if (isHardwareAccelerated()) {
+ // This will consume the passed in transaction and the transaction will be
+ // applied on a render worker thread.
+ replacePositionUpdateListener(mSurfaceWidth, mSurfaceHeight);
+ } else {
+ onSetSurfacePositionAndScale(surfaceUpdateTransaction, mSurfaceControl,
+ mScreenRect.left /*positionLeft*/,
+ mScreenRect.top /*positionTop*/,
+ mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/,
+ mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/);
+ }
+ if (DEBUG_POSITION) {
+ Log.d(TAG, String.format(
+ "%d performSurfaceTransaction %s "
+ + "position = [%d, %d, %d, %d] surfaceSize = %dx%d",
+ System.identityHashCode(this),
+ isHardwareAccelerated() ? "RenderWorker" : "UI Thread",
+ mScreenRect.left, mScreenRect.top, mScreenRect.right,
+ mScreenRect.bottom, mSurfaceWidth, mSurfaceHeight));
+ }
+ }
+ applyTransactionOnVriDraw(surfaceUpdateTransaction);
+ updateEmbeddedAccessibilityMatrix(false);
+
+ mSurfaceFrame.left = 0;
+ mSurfaceFrame.top = 0;
+ if (translator == null) {
+ mSurfaceFrame.right = mSurfaceWidth;
+ mSurfaceFrame.bottom = mSurfaceHeight;
} else {
- onSetSurfacePositionAndScale(surfaceUpdateTransaction, mSurfaceControl,
- mScreenRect.left /*positionLeft*/,
- mScreenRect.top /*positionTop*/,
- mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/,
- mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/);
+ float appInvertedScale = translator.applicationInvertedScale;
+ mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
+ mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
}
- if (DEBUG_POSITION) {
- Log.d(TAG, String.format(
- "%d performSurfaceTransaction %s "
- + "position = [%d, %d, %d, %d] surfaceSize = %dx%d",
- System.identityHashCode(this),
- isHardwareAccelerated() ? "RenderWorker" : "UI Thread",
- mScreenRect.left, mScreenRect.top, mScreenRect.right,
- mScreenRect.bottom, mSurfaceWidth, mSurfaceHeight));
- }
+ final int surfaceWidth = mSurfaceFrame.right;
+ final int surfaceHeight = mSurfaceFrame.bottom;
+ realSizeChanged = mLastSurfaceWidth != surfaceWidth
+ || mLastSurfaceHeight != surfaceHeight;
+ mLastSurfaceWidth = surfaceWidth;
+ mLastSurfaceHeight = surfaceHeight;
+ } finally {
+ mSurfaceLock.unlock();
}
- applyTransactionOnVriDraw(surfaceUpdateTransaction);
- updateEmbeddedAccessibilityMatrix(false);
- mSurfaceFrame.left = 0;
- mSurfaceFrame.top = 0;
- if (translator == null) {
- mSurfaceFrame.right = mSurfaceWidth;
- mSurfaceFrame.bottom = mSurfaceHeight;
- } else {
- float appInvertedScale = translator.applicationInvertedScale;
- mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
- mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
- }
- final int surfaceWidth = mSurfaceFrame.right;
- final int surfaceHeight = mSurfaceFrame.bottom;
- realSizeChanged = mLastSurfaceWidth != surfaceWidth
- || mLastSurfaceHeight != surfaceHeight;
- mLastSurfaceWidth = surfaceWidth;
- mLastSurfaceHeight = surfaceHeight;
-
return realSizeChanged;
}
@@ -1133,30 +1139,21 @@
* Surface for compatibility reasons.
*/
private void copySurface(boolean surfaceControlCreated, boolean bufferSizeChanged) {
- // Some legacy applications use the underlying native {@link Surface} object
- // as a key to whether anything has changed. In these cases, updates to the
- // existing {@link Surface} will be ignored when the size changes.
- // Therefore, we must explicitly recreate the {@link Surface} in these
- // cases.
- boolean needsWorkaround = bufferSizeChanged &&
- getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O;
- if (!surfaceControlCreated && !needsWorkaround) {
- return;
- }
- mSurfaceLock.lock();
- try {
- if (surfaceControlCreated) {
- mSurface.copyFrom(mBlastBufferQueue);
- }
+ if (surfaceControlCreated) {
+ mSurface.copyFrom(mBlastBufferQueue);
+ }
- if (needsWorkaround) {
- if (mBlastBufferQueue != null) {
- mSurface.transferFrom(mBlastBufferQueue.createSurfaceWithHandle());
- }
- }
- } finally {
- mSurfaceLock.unlock();
- }
+ if (bufferSizeChanged && getContext().getApplicationInfo().targetSdkVersion
+ < Build.VERSION_CODES.O) {
+ // Some legacy applications use the underlying native {@link Surface} object
+ // as a key to whether anything has changed. In these cases, updates to the
+ // existing {@link Surface} will be ignored when the size changes.
+ // Therefore, we must explicitly recreate the {@link Surface} in these
+ // cases.
+ if (mBlastBufferQueue != null) {
+ mSurface.transferFrom(mBlastBufferQueue.createSurfaceWithHandle());
+ }
+ }
}
private void setBufferSize(Transaction transaction) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 84edb3a..4893777 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3085,45 +3085,6 @@
static final int IMPORTANT_FOR_ACCESSIBILITY_DEFAULT = IMPORTANT_FOR_ACCESSIBILITY_AUTO;
/**
- * Automatically determine whether the view should only allow interactions from
- * {@link android.accessibilityservice.AccessibilityService}s with the
- * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property
- * set to true.
- *
- * <p>
- * Accessibility interactions from services without {@code isAccessibilityTool} set to true are
- * disallowed for any of the following conditions:
- * <li>this view's window sets {@link WindowManager.LayoutParams#FLAG_SECURE}.</li>
- * <li>this view sets {@link #getFilterTouchesWhenObscured()}.</li>
- * <li>any parent of this view returns true from {@link #isAccessibilityDataPrivate()}.</li>
- * </p>
- */
- public static final int ACCESSIBILITY_DATA_PRIVATE_AUTO = 0x00000000;
-
- /**
- * Only allow interactions from {@link android.accessibilityservice.AccessibilityService}s
- * with the {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool}
- * property set to true.
- */
- public static final int ACCESSIBILITY_DATA_PRIVATE_YES = 0x00000001;
-
- /**
- * Allow interactions from all {@link android.accessibilityservice.AccessibilityService}s,
- * regardless of their
- * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property.
- */
- public static final int ACCESSIBILITY_DATA_PRIVATE_NO = 0x00000002;
-
- /** @hide */
- @IntDef(prefix = { "ACCESSIBILITY_DATA_PRIVATE_" }, value = {
- ACCESSIBILITY_DATA_PRIVATE_AUTO,
- ACCESSIBILITY_DATA_PRIVATE_YES,
- ACCESSIBILITY_DATA_PRIVATE_NO,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface AccessibilityDataPrivate {}
-
- /**
* Mask for obtaining the bits which specify how to determine
* whether a view is important for accessibility.
*/
@@ -4566,14 +4527,6 @@
private CharSequence mAccessibilityPaneTitle;
/**
- * Describes whether this view should only allow interactions from
- * {@link android.accessibilityservice.AccessibilityService}s with the
- * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property
- * set to true.
- */
- private int mAccessibilityDataPrivate = ACCESSIBILITY_DATA_PRIVATE_AUTO;
-
- /**
* Specifies the id of a view for which this view serves as a label for
* accessibility purposes.
*/
@@ -5966,10 +5919,6 @@
setImportantForAccessibility(a.getInt(attr,
IMPORTANT_FOR_ACCESSIBILITY_DEFAULT));
break;
- case R.styleable.View_accessibilityDataPrivate:
- setAccessibilityDataPrivate(a.getInt(attr,
- ACCESSIBILITY_DATA_PRIVATE_AUTO));
- break;
case R.styleable.View_accessibilityLiveRegion:
setAccessibilityLiveRegion(a.getInt(attr, ACCESSIBILITY_LIVE_REGION_DEFAULT));
break;
@@ -8569,11 +8518,6 @@
* is responsible for handling this call.
* </p>
* <p>
- * If this view sets {@link #isAccessibilityDataPrivate()} then this view should only append
- * sensitive information to an event that also sets
- * {@link AccessibilityEvent#isAccessibilityDataPrivate()}.
- * </p>
- * <p>
* <em>Note:</em> Accessibility events of certain types are not dispatched for
* populating the event text via this method. For details refer to {@link AccessibilityEvent}.
* </p>
@@ -10475,7 +10419,7 @@
}
if ((mAttachInfo.mAccessibilityFetchFlags
- & AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS) != 0
+ & AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS) != 0
&& Resources.resourceHasPackage(mID)) {
try {
String viewId = getResources().getResourceName(mID);
@@ -14458,75 +14402,14 @@
@UnsupportedAppUsage
public boolean includeForAccessibility() {
if (mAttachInfo != null) {
- if (isAccessibilityDataPrivate() && !AccessibilityManager.getInstance(
- mContext).isRequestFromAccessibilityTool()) {
- return false;
- }
-
return (mAttachInfo.mAccessibilityFetchFlags
- & AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS) != 0
+ & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0
|| isImportantForAccessibility();
}
return false;
}
/**
- * Whether this view should restrict accessibility service access only to services that have the
- * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property
- * set to true.
- *
- * <p>
- * See default behavior provided by {@link #ACCESSIBILITY_DATA_PRIVATE_AUTO}. Otherwise,
- * returns true for {@link #ACCESSIBILITY_DATA_PRIVATE_YES} or false for {@link
- * #ACCESSIBILITY_DATA_PRIVATE_NO}.
- * </p>
- *
- * @return True if this view should restrict accessibility service access to services that have
- * the isAccessibilityTool property.
- */
- @ViewDebug.ExportedProperty(category = "accessibility")
- public boolean isAccessibilityDataPrivate() {
- if (mAccessibilityDataPrivate == ACCESSIBILITY_DATA_PRIVATE_YES) {
- return true;
- }
- if (mAccessibilityDataPrivate == ACCESSIBILITY_DATA_PRIVATE_NO) {
- return false;
- }
-
- // Views inside FLAG_SECURE windows default to accessibilityDataPrivate.
- if (mAttachInfo != null && mAttachInfo.mWindowSecure) {
- return true;
- }
- // Views that set filterTouchesWhenObscured default to accessibilityDataPrivate.
- if (getFilterTouchesWhenObscured()) {
- return true;
- }
-
- // Descendants of an accessibilityDataPrivate View are also accessibilityDataPrivate.
- ViewParent parent = mParent;
- while (parent instanceof View) {
- if (((View) parent).isAccessibilityDataPrivate()) {
- return true;
- }
- parent = parent.getParent();
- }
-
- // Otherwise, default to not accessibilityDataPrivate.
- return false;
- }
-
- /**
- * Specifies whether this view should only allow interactions from
- * {@link android.accessibilityservice.AccessibilityService}s with the
- * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property
- * set to true.
- */
- public void setAccessibilityDataPrivate(
- @AccessibilityDataPrivate int accessibilityDataPrivate) {
- mAccessibilityDataPrivate = accessibilityDataPrivate;
- }
-
- /**
* Returns whether the View is considered actionable from
* accessibility perspective. Such view are important for
* accessibility.
@@ -30213,11 +30096,6 @@
int mWindowVisibility;
/**
- * Indicates whether the view's window sets {@link WindowManager.LayoutParams#FLAG_SECURE}.
- */
- boolean mWindowSecure;
-
- /**
* Indicates the time at which drawing started to occur.
*/
@UnsupportedAppUsage
@@ -30394,8 +30272,8 @@
/**
* Flags related to accessibility processing.
*
- * @see AccessibilityNodeInfo#FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS
- * @see AccessibilityNodeInfo#FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS
+ * @see AccessibilityNodeInfo#FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+ * @see AccessibilityNodeInfo#FLAG_REPORT_VIEW_IDS
*/
int mAccessibilityFetchFlags;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 12bc169..674f0a2 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2842,7 +2842,6 @@
// However, windows are now always 32 bits by default, so choose 32 bits
mAttachInfo.mUse32BitDrawingCache = true;
mAttachInfo.mWindowVisibility = viewVisibility;
- mAttachInfo.mWindowSecure = (lp.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0;
mAttachInfo.mRecomputeGlobalAttributes = false;
mLastConfigurationFromResources.setTo(config);
mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 2db0dcb..cd0dd1d 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -46,17 +46,15 @@
* </p>
* <p>
* The main purpose of an accessibility event is to communicate changes in the UI to an
- * {@link android.accessibilityservice.AccessibilityService}. If needed, the service may then
- * inspect the user interface by examining the View hierarchy through the event's
- * {@link #getSource() source}, as represented by a tree of {@link AccessibilityNodeInfo}s (snapshot
- * of a View state) which can be used for exploring the window content. Note that the privilege for
- * accessing an event's source, thus the window content, has to be explicitly requested. For more
+ * {@link android.accessibilityservice.AccessibilityService}. The service may then inspect,
+ * if needed the user interface by examining the View hierarchy, as represented by a tree of
+ * {@link AccessibilityNodeInfo}s (snapshot of a View state)
+ * which can be used for exploring the window content. Note that the privilege for accessing
+ * an event's source, thus the window content, has to be explicitly requested. For more
* details refer to {@link android.accessibilityservice.AccessibilityService}. If an
* accessibility service has not requested to retrieve the window content the event will
- * not contain reference to its source. <strong>Note: </strong> for events of type
- * {@link #TYPE_NOTIFICATION_STATE_CHANGED} the source is never available, and Views that set
- * {@link android.view.View#isAccessibilityDataPrivate()} may not populate all event properties on
- * events sent from higher up in the view hierarchy.
+ * not contain reference to its source. Also for events of type
+ * {@link #TYPE_NOTIFICATION_STATE_CHANGED} the source is never available.
* </p>
* <p>
* This class represents various semantically different accessibility event
@@ -1094,47 +1092,6 @@
}
/**
- * Whether the event should only be delivered to an
- * {@link android.accessibilityservice.AccessibilityService} with the
- * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property
- * set to true.
- *
- * <p>
- * Initial value matches the {@link android.view.View#isAccessibilityDataPrivate} property from
- * the event's source node, if present, or false by default.
- * </p>
- *
- * @return True if the event should be delivered only to isAccessibilityTool services, false
- * otherwise.
- * @see #setAccessibilityDataPrivate
- */
- @Override
- public boolean isAccessibilityDataPrivate() {
- return super.isAccessibilityDataPrivate();
- }
-
- /**
- * Sets whether the event should only be delivered to an
- * {@link android.accessibilityservice.AccessibilityService} with the
- * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property
- * set to true.
- *
- * <p>
- * This will be set automatically based on the event's source (if present). If creating and
- * sending an event directly through {@link AccessibilityManager} (where an event may have
- * no source) then this method must be called explicitly if you want non-default behavior.
- * </p>
- *
- * @param accessibilityDataPrivate True if the event should be delivered only to
- * isAccessibilityTool services, false otherwise.
- * @throws IllegalStateException If called from an AccessibilityService.
- */
- @Override
- public void setAccessibilityDataPrivate(boolean accessibilityDataPrivate) {
- super.setAccessibilityDataPrivate(accessibilityDataPrivate);
- }
-
- /**
* Gets the bit mask of the speech state signaled by a {@link #TYPE_SPEECH_STATE_CHANGE} event
*
* @see #SPEECH_STATE_SPEAKING_START
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index e89f836..9e3195a 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -276,8 +276,6 @@
private final ArrayMap<AudioDescriptionRequestedChangeListener, Executor>
mAudioDescriptionRequestedChangeListeners = new ArrayMap<>();
- private boolean mRequestFromAccessibilityTool;
-
/**
* Map from a view's accessibility id to the list of request preparers set for that view
*/
@@ -985,39 +983,6 @@
}
/**
- * Whether the current accessibility request comes from an
- * {@link AccessibilityService} with the {@link AccessibilityServiceInfo#isAccessibilityTool}
- * property set to true.
- *
- * <p>
- * You can use this method inside {@link AccessibilityNodeProvider} to decide how to populate
- * your nodes.
- * </p>
- *
- * <p>
- * <strong>Note:</strong> The return value is valid only when an {@link AccessibilityNodeInfo}
- * request is in progress, can change from one request to another, and has no meaning when a
- * request is not in progress.
- * </p>
- *
- * @return True if the current request is from a tool that sets isAccessibilityTool.
- */
- public boolean isRequestFromAccessibilityTool() {
- return mRequestFromAccessibilityTool;
- }
-
- /**
- * Specifies whether the current accessibility request comes from an
- * {@link AccessibilityService} with the {@link AccessibilityServiceInfo#isAccessibilityTool}
- * property set to true.
- *
- * @hide
- */
- public void setRequestFromAccessibilityTool(boolean requestFromAccessibilityTool) {
- mRequestFromAccessibilityTool = requestFromAccessibilityTool;
- }
-
- /**
* Registers a {@link AccessibilityRequestPreparer}.
*/
public void addAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 15718c4..953f261 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -217,29 +217,14 @@
@Retention(RetentionPolicy.SOURCE)
public @interface PrefetchingStrategy {}
- /**
- * @see AccessibilityServiceInfo#FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
- * @hide
- */
- public static final int FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000080;
-
- /**
- * @see AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS
- * @hide
- */
- public static final int FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS = 0x00000100;
-
- /**
- * @see AccessibilityServiceInfo#isAccessibilityTool()
- * @hide
- */
- public static final int FLAG_SERVICE_IS_ACCESSIBILITY_TOOL = 0x00000200;
+ /** @hide */
+ public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000080;
/** @hide */
- public static final int FLAG_REPORT_MASK =
- FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS
- | FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS
- | FLAG_SERVICE_IS_ACCESSIBILITY_TOOL;
+ public static final int FLAG_REPORT_VIEW_IDS = 0x00000100;
+
+ /** @hide */
+ public static final int FLAG_REPORT_MASK = 0x00000180;
// Actions.
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index 789c740..036316e 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -72,7 +72,6 @@
private static final int PROPERTY_FULL_SCREEN = 0x00000080;
private static final int PROPERTY_SCROLLABLE = 0x00000100;
private static final int PROPERTY_IMPORTANT_FOR_ACCESSIBILITY = 0x00000200;
- private static final int PROPERTY_ACCESSIBILITY_DATA_PRIVATE = 0x00000400;
private static final int GET_SOURCE_PREFETCH_FLAGS =
AccessibilityNodeInfo.FLAG_PREFETCH_ANCESTORS
@@ -160,8 +159,6 @@
important = root.isImportantForAccessibility();
rootViewId = root.getAccessibilityViewId();
mSourceWindowId = root.getAccessibilityWindowId();
- setBooleanProperty(PROPERTY_ACCESSIBILITY_DATA_PRIVATE,
- root.isAccessibilityDataPrivate());
}
setBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY, important);
mSourceNodeId = AccessibilityNodeInfo.makeNodeId(rootViewId, virtualDescendantId);
@@ -391,23 +388,6 @@
}
/**
- * @see AccessibilityEvent#isAccessibilityDataPrivate
- * @hide
- */
- boolean isAccessibilityDataPrivate() {
- return getBooleanProperty(PROPERTY_ACCESSIBILITY_DATA_PRIVATE);
- }
-
- /**
- * @see AccessibilityEvent#setAccessibilityDataPrivate
- * @hide
- */
- void setAccessibilityDataPrivate(boolean accessibilityDataPrivate) {
- enforceNotSealed();
- setBooleanProperty(PROPERTY_ACCESSIBILITY_DATA_PRIVATE, accessibilityDataPrivate);
- }
-
- /**
* Gets the number of items that can be visited.
*
* @return The number of items.
@@ -961,8 +941,6 @@
appendUnless(false, PROPERTY_CHECKED, builder);
appendUnless(false, PROPERTY_FULL_SCREEN, builder);
appendUnless(false, PROPERTY_SCROLLABLE, builder);
- appendUnless(false, PROPERTY_IMPORTANT_FOR_ACCESSIBILITY, builder);
- appendUnless(false, PROPERTY_ACCESSIBILITY_DATA_PRIVATE, builder);
append(builder, "BeforeText", mBeforeText);
append(builder, "FromIndex", mFromIndex);
@@ -996,8 +974,6 @@
case PROPERTY_SCROLLABLE: return "Scrollable";
case PROPERTY_IMPORTANT_FOR_ACCESSIBILITY:
return "ImportantForAccessibility";
- case PROPERTY_ACCESSIBILITY_DATA_PRIVATE:
- return "AccessibilityDataPrivate";
default: return Integer.toHexString(prop);
}
}
diff --git a/core/java/android/view/selectiontoolbar/ShowInfo.java b/core/java/android/view/selectiontoolbar/ShowInfo.java
index d9adef2..28b4480 100644
--- a/core/java/android/view/selectiontoolbar/ShowInfo.java
+++ b/core/java/android/view/selectiontoolbar/ShowInfo.java
@@ -75,6 +75,11 @@
@NonNull
private final IBinder mHostInputToken;
+ /**
+ * If the host application uses light theme.
+ */
+ private final boolean mIsLightTheme;
+
// Code below generated by codegen v1.0.23.
@@ -109,6 +114,8 @@
* @param hostInputToken
* The host application's input token, this allows the remote render service to transfer
* the touch focus to the host application.
+ * @param isLightTheme
+ * If the host application uses light theme.
*/
@DataClass.Generated.Member
public ShowInfo(
@@ -118,7 +125,8 @@
@NonNull Rect contentRect,
int suggestedWidth,
@NonNull Rect viewPortOnScreen,
- @NonNull IBinder hostInputToken) {
+ @NonNull IBinder hostInputToken,
+ boolean isLightTheme) {
this.mWidgetToken = widgetToken;
this.mLayoutRequired = layoutRequired;
this.mMenuItems = menuItems;
@@ -134,6 +142,7 @@
this.mHostInputToken = hostInputToken;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mHostInputToken);
+ this.mIsLightTheme = isLightTheme;
// onConstructed(); // You can define this method to get a callback
}
@@ -196,6 +205,14 @@
return mHostInputToken;
}
+ /**
+ * If the host application uses light theme.
+ */
+ @DataClass.Generated.Member
+ public boolean isIsLightTheme() {
+ return mIsLightTheme;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -209,7 +226,8 @@
"contentRect = " + mContentRect + ", " +
"suggestedWidth = " + mSuggestedWidth + ", " +
"viewPortOnScreen = " + mViewPortOnScreen + ", " +
- "hostInputToken = " + mHostInputToken +
+ "hostInputToken = " + mHostInputToken + ", " +
+ "isLightTheme = " + mIsLightTheme +
" }";
}
@@ -232,7 +250,8 @@
&& java.util.Objects.equals(mContentRect, that.mContentRect)
&& mSuggestedWidth == that.mSuggestedWidth
&& java.util.Objects.equals(mViewPortOnScreen, that.mViewPortOnScreen)
- && java.util.Objects.equals(mHostInputToken, that.mHostInputToken);
+ && java.util.Objects.equals(mHostInputToken, that.mHostInputToken)
+ && mIsLightTheme == that.mIsLightTheme;
}
@Override
@@ -249,6 +268,7 @@
_hash = 31 * _hash + mSuggestedWidth;
_hash = 31 * _hash + java.util.Objects.hashCode(mViewPortOnScreen);
_hash = 31 * _hash + java.util.Objects.hashCode(mHostInputToken);
+ _hash = 31 * _hash + Boolean.hashCode(mIsLightTheme);
return _hash;
}
@@ -258,9 +278,10 @@
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
- byte flg = 0;
+ int flg = 0;
if (mLayoutRequired) flg |= 0x2;
- dest.writeByte(flg);
+ if (mIsLightTheme) flg |= 0x80;
+ dest.writeInt(flg);
dest.writeLong(mWidgetToken);
dest.writeParcelableList(mMenuItems, flags);
dest.writeTypedObject(mContentRect, flags);
@@ -280,8 +301,9 @@
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
- byte flg = in.readByte();
+ int flg = in.readInt();
boolean layoutRequired = (flg & 0x2) != 0;
+ boolean isLightTheme = (flg & 0x80) != 0;
long widgetToken = in.readLong();
List<ToolbarMenuItem> menuItems = new java.util.ArrayList<>();
in.readParcelableList(menuItems, ToolbarMenuItem.class.getClassLoader(), android.view.selectiontoolbar.ToolbarMenuItem.class);
@@ -305,6 +327,7 @@
this.mHostInputToken = hostInputToken;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mHostInputToken);
+ this.mIsLightTheme = isLightTheme;
// onConstructed(); // You can define this method to get a callback
}
@@ -324,10 +347,10 @@
};
@DataClass.Generated(
- time = 1643186262604L,
+ time = 1645108384245L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/ShowInfo.java",
- inputSignatures = "private final long mWidgetToken\nprivate final boolean mLayoutRequired\nprivate final @android.annotation.NonNull java.util.List<android.view.selectiontoolbar.ToolbarMenuItem> mMenuItems\nprivate final @android.annotation.NonNull android.graphics.Rect mContentRect\nprivate final int mSuggestedWidth\nprivate final @android.annotation.NonNull android.graphics.Rect mViewPortOnScreen\nprivate final @android.annotation.NonNull android.os.IBinder mHostInputToken\nclass ShowInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
+ inputSignatures = "private final long mWidgetToken\nprivate final boolean mLayoutRequired\nprivate final @android.annotation.NonNull java.util.List<android.view.selectiontoolbar.ToolbarMenuItem> mMenuItems\nprivate final @android.annotation.NonNull android.graphics.Rect mContentRect\nprivate final int mSuggestedWidth\nprivate final @android.annotation.NonNull android.graphics.Rect mViewPortOnScreen\nprivate final @android.annotation.NonNull android.os.IBinder mHostInputToken\nprivate final boolean mIsLightTheme\nclass ShowInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/translation/TranslationManager.java b/core/java/android/view/translation/TranslationManager.java
index 55c0726..5aad823 100644
--- a/core/java/android/view/translation/TranslationManager.java
+++ b/core/java/android/view/translation/TranslationManager.java
@@ -40,11 +40,11 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.SyncResultReceiver;
+import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
-import java.util.Random;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;
@@ -92,7 +92,8 @@
private final Map<Consumer<TranslationCapability>, IRemoteCallback> mCapabilityCallbacks =
new ArrayMap<>();
- private static final Random ID_GENERATOR = new Random();
+ // TODO(b/158778794): make the session ids truly globally unique across processes
+ private static final SecureRandom ID_GENERATOR = new SecureRandom();
private final Object mLock = new Object();
@NonNull
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 3f87ec2..1c7c582 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4887,20 +4887,28 @@
}
/**
- * Set the line break style for text wrapping.
+ * Sets the line-break style for text wrapping.
*
- * The line break style to indicates the line break strategies can be used when
- * calculating the text wrapping. The line break style affects rule-based breaking. It
- * specifies the strictness of line-breaking rules.
- * There are several types for the line break style:
- * {@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE},
- * {@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL} and
- * {@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}. The default values of the line break style
- * is {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, indicating no breaking rule is specified.
- * See <a href="https://www.w3.org/TR/css-text-3/#line-break-property">
- * the line-break property</a>
+ * <p>Line-break style specifies the line-break strategies that can be used
+ * for text wrapping. The line-break style affects rule-based line breaking
+ * by specifying the strictness of line-breaking rules.</p>
*
- * @param lineBreakStyle the line break style for the text.
+ * <p>The following are types of line-break styles:</p>
+ * <ul>
+ * <li>{@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE}</li>
+ * <li>{@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL}</li>
+ * <li>{@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}</li>
+ * </ul>
+ *
+ * <p>The default line-break style is
+ * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, which specifies that no
+ * line-breaking rules are used.</p>
+ *
+ * <p>See the
+ * <a href="https://www.w3.org/TR/css-text-3/#line-break-property" class="external">
+ * line-break property</a> for more information.</p>
+ *
+ * @param lineBreakStyle The line break style for the text.
*/
public void setLineBreakStyle(@LineBreakConfig.LineBreakStyle int lineBreakStyle) {
if (mLineBreakStyle != lineBreakStyle) {
@@ -4914,17 +4922,22 @@
}
/**
- * Set the line break word style for text wrapping.
+ * Sets the line-break word style for text wrapping.
*
- * The line break word style affects dictionary-based breaking and provide phrase-based
- * breaking opportunities. The type for the line break word style is
- * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE}. The default values of the line break
- * word style is {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE}, indicating no breaking rule
- * is specified.
- * See <a href="https://www.w3.org/TR/css-text-3/#word-break-property">
- * the word-break property</a>
+ * <p>The line-break word style affects dictionary-based line breaking by
+ * providing phrase-based line-breaking opportunities. Use
+ * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE} to specify
+ * phrase-based line breaking.</p>
*
- * @param lineBreakWordStyle the line break word style for the tet
+ * <p>The default line-break word style is
+ * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE}, which specifies that
+ * no line-breaking word style is used.</p>
+ *
+ * <p>See the
+ * <a href="https://www.w3.org/TR/css-text-3/#word-break-property" class="external">
+ * word-break property</a> for more information.</p>
+ *
+ * @param lineBreakWordStyle The line break word style for the text.
*/
public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
mUserSpeficiedLineBreakwordStyle = true;
@@ -4939,18 +4952,18 @@
}
/**
- * Get the current line break style for text wrapping.
+ * Gets the current line-break style for text wrapping.
*
- * @return the current line break style to be used for text wrapping.
+ * @return The line-break style to be used for text wrapping.
*/
public @LineBreakConfig.LineBreakStyle int getLineBreakStyle() {
return mLineBreakStyle;
}
/**
- * Get the current line word break style for text wrapping.
+ * Gets the current line-break word style for text wrapping.
*
- * @return the current line break word style to be used for text wrapping.
+ * @return The line-break word style to be used for text wrapping.
*/
public @LineBreakConfig.LineBreakWordStyle int getLineBreakWordStyle() {
return mLineBreakWordStyle;
@@ -12076,13 +12089,6 @@
public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
super.onPopulateAccessibilityEventInternal(event);
- if (this.isAccessibilityDataPrivate() && !event.isAccessibilityDataPrivate()) {
- // This view's accessibility data is private, but another view that generated this event
- // is not, so don't append this view's text to the event in order to prevent sharing
- // this view's contents with non-accessibility-tool services.
- return;
- }
-
final CharSequence text = getTextForAccessibility();
if (!TextUtils.isEmpty(text)) {
event.getText().add(text);
diff --git a/core/java/com/android/internal/widget/LocalImageResolver.java b/core/java/com/android/internal/widget/LocalImageResolver.java
index b866723..b11ea29 100644
--- a/core/java/com/android/internal/widget/LocalImageResolver.java
+++ b/core/java/com/android/internal/widget/LocalImageResolver.java
@@ -25,6 +25,7 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.net.Uri;
+import android.text.TextUtils;
import android.util.Log;
import android.util.Size;
@@ -108,6 +109,12 @@
}
break;
case Icon.TYPE_RESOURCE:
+ if (!(TextUtils.isEmpty(icon.getResPackage())
+ || context.getPackageName().equals(icon.getResPackage()))) {
+ // We can't properly resolve icons from other packages here, so fall back.
+ return icon.loadDrawable(context);
+ }
+
Drawable result = resolveImage(icon.getResId(), context, maxWidth, maxHeight);
if (result != null) {
return tintDrawable(icon, result);
diff --git a/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java
index 8c2eb10..8787c39 100644
--- a/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java
+++ b/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java
@@ -23,6 +23,7 @@
import android.annotation.Nullable;
import android.annotation.UiThread;
import android.content.Context;
+import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -107,6 +108,7 @@
private int mSuggestedWidth;
private final Rect mScreenViewPort = new Rect();
private boolean mWidthChanged = true;
+ private final boolean mIsLightTheme;
private final int[] mCoordsOnScreen = new int[2];
private final int[] mCoordsOnWindow = new int[2];
@@ -116,9 +118,17 @@
mPopupWindow = createPopupWindow(context);
mSelectionToolbarManager = context.getSystemService(SelectionToolbarManager.class);
mSelectionToolbarCallback = new SelectionToolbarCallbackImpl(this);
+ mIsLightTheme = isLightTheme(context);
mFloatingToolbarToken = NO_TOOLBAR_ID;
}
+ private boolean isLightTheme(Context context) {
+ TypedArray a = context.obtainStyledAttributes(new int[]{R.attr.isLightTheme});
+ boolean isLightTheme = a.getBoolean(0, true);
+ a.recycle();
+ return isLightTheme;
+ }
+
@UiThread
@Override
public void show(List<MenuItem> menuItems,
@@ -155,7 +165,7 @@
contentRect,
suggestWidth,
mScreenViewPort,
- mParent.getViewRootImpl().getInputToken());
+ mParent.getViewRootImpl().getInputToken(), mIsLightTheme);
if (DEBUG) {
Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
"RemoteFloatingToolbarPopup.show() for " + showInfo);
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index ff97ab0..671e634 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -68,7 +68,7 @@
### Graphics ###
per-file android_graphics_* = file:/graphics/java/android/graphics/OWNERS
-per-file android_hardware_HardwareBuffer.cpp = file:/graphics/java/android/graphics/OWNERS
+per-file *HardwareBuffer* = file:/graphics/java/android/graphics/OWNERS
per-file android_hardware_SyncFence.cpp = file:/graphics/java/android/graphics/OWNERS
per-file android_os_GraphicsEnvironment.cpp = file:platform/frameworks/native:/opengl/OWNERS
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index b9d5ee4..9501c8d 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -1252,6 +1252,20 @@
return fd;
}
+void android_os_Process_freezeCgroupUID(JNIEnv* env, jobject clazz, jint uid, jboolean freeze) {
+ bool success = true;
+
+ if (freeze) {
+ success = SetUserProfiles(uid, {"Frozen"});
+ } else {
+ success = SetUserProfiles(uid, {"Unfrozen"});
+ }
+
+ if (!success) {
+ jniThrowRuntimeException(env, "Could not apply user profile");
+ }
+}
+
static const JNINativeMethod methods[] = {
{"getUidForName", "(Ljava/lang/String;)I", (void*)android_os_Process_getUidForName},
{"getGidForName", "(Ljava/lang/String;)I", (void*)android_os_Process_getGidForName},
@@ -1293,6 +1307,7 @@
{"killProcessGroup", "(II)I", (void*)android_os_Process_killProcessGroup},
{"removeAllProcessGroups", "()V", (void*)android_os_Process_removeAllProcessGroups},
{"nativePidFdOpen", "(II)I", (void*)android_os_Process_nativePidFdOpen},
+ {"freezeCgroupUid", "(IZ)V", (void*)android_os_Process_freezeCgroupUID},
};
int register_android_os_Process(JNIEnv* env)
diff --git a/core/proto/android/server/peopleservice.proto b/core/proto/android/server/peopleservice.proto
index c465233..a96ec41 100644
--- a/core/proto/android/server/peopleservice.proto
+++ b/core/proto/android/server/peopleservice.proto
@@ -62,7 +62,10 @@
// The timestamp of the last event in millis.
optional int64 last_event_timestamp = 9;
- // Next tag: 10
+ // The timestamp this conversation was created in millis.
+ optional int64 creation_timestamp = 10;
+
+ // Next tag: 11
}
// On disk data of events.
diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto
index 2a625b027..25a1f68 100644
--- a/core/proto/android/server/vibrator/vibratormanagerservice.proto
+++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto
@@ -86,7 +86,7 @@
optional int32 flags = 3;
}
-// Next id: 8
+// Next Tag: 9
message VibrationProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
optional int64 start_time = 1;
@@ -94,11 +94,43 @@
optional CombinedVibrationEffectProto effect = 3;
optional CombinedVibrationEffectProto original_effect = 4;
optional VibrationAttributesProto attributes = 5;
- optional int32 status = 6;
optional int64 duration_ms = 7;
+ optional Status status = 8;
+ reserved 6; // prev int32 status
+
+ // Also used by VibrationReported from frameworks/proto_logging/stats/atoms.proto.
+ // Next Tag: 26
+ enum Status {
+ UNKNOWN = 0;
+ RUNNING = 1;
+ FINISHED = 2;
+ FINISHED_UNEXPECTED = 3; // Didn't terminate in the usual way.
+ FORWARDED_TO_INPUT_DEVICES = 4;
+ CANCELLED_BINDER_DIED = 5;
+ CANCELLED_BY_SCREEN_OFF = 6;
+ CANCELLED_BY_SETTINGS_UPDATE = 7;
+ CANCELLED_BY_USER = 8;
+ CANCELLED_BY_UNKNOWN_REASON = 9;
+ CANCELLED_SUPERSEDED = 10;
+ IGNORED_ERROR_APP_OPS = 11;
+ IGNORED_ERROR_CANCELLING = 12;
+ IGNORED_ERROR_SCHEDULING = 13;
+ IGNORED_ERROR_TOKEN= 14;
+ IGNORED_APP_OPS = 15;
+ IGNORED_BACKGROUND = 16;
+ IGNORED_UNKNOWN_VIBRATION = 17;
+ IGNORED_UNSUPPORTED = 18;
+ IGNORED_FOR_EXTERNAL = 19;
+ IGNORED_FOR_HIGHER_IMPORTANCE = 20;
+ IGNORED_FOR_ONGOING = 21;
+ IGNORED_FOR_POWER = 22;
+ IGNORED_FOR_RINGER_MODE = 23;
+ IGNORED_FOR_SETTINGS = 24;
+ IGNORED_SUPERSEDED = 25;
+ }
}
-// Next id: 25
+// Next Tag: 25
message VibratorManagerServiceDumpProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
repeated int32 vibrator_ids = 1;
diff --git a/core/res/res/color/system_bar_background_semi_transparent.xml b/core/res/res/color/system_bar_background_semi_transparent.xml
new file mode 100644
index 0000000..839d58a
--- /dev/null
+++ b/core/res/res/color/system_bar_background_semi_transparent.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@color/system_neutral2_900" android:alpha="0.5" />
+</selector>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index d43a6c5..c7153fc 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5542,22 +5542,22 @@
ignores some hyphen character related typographic features, e.g. kerning. -->
<enum name="fullFast" value="4" />
</attr>
- <!-- Indicates the line break strategies can be used when calculating the text wrapping. -->
+ <!-- Specifies the line-break strategies for text wrapping. -->
<attr name="lineBreakStyle">
- <!-- No line break style specific. -->
+ <!-- No line-break rules are used for line breaking. -->
<enum name="none" value="0" />
- <!-- Use the least restrictive rule for line-breaking. -->
+ <!-- The least restrictive line-break rules are used for line breaking. -->
<enum name="loose" value="1" />
- <!-- Indicates breaking text with the most comment set of line-breaking rules. -->
+ <!-- The most common line-break rules are used for line breaking. -->
<enum name="normal" value="2" />
- <!-- Indicates breaking text with the most strictest line-breaking rules. -->
+ <!-- The most strict line-break rules are used for line breaking. -->
<enum name="strict" value="3" />
</attr>
- <!-- Specify the phrase-based line break can be used when calculating the text wrapping.-->
+ <!-- Specifies the line-break word strategies for text wrapping.-->
<attr name="lineBreakWordStyle">
- <!-- No line break word style specific. -->
+ <!-- No line-break word style is used for line breaking. -->
<enum name="none" value="0" />
- <!-- Specify the phrase based breaking. -->
+ <!-- Line breaking is based on phrases, which results in text wrapping only on meaningful words. -->
<enum name="phrase" value="1" />
</attr>
<!-- Specify the type of auto-size. Note that this feature is not supported by EditText,
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index ddac406..77d7c43 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -211,9 +211,6 @@
<color name="Red_700">#ffc53929</color>
<color name="Red_800">#ffb93221</color>
- <!-- Status bar color for semi transparent mode. -->
- <color name="system_bar_background_semi_transparent">#66000000</color> <!-- 40% black -->
-
<color name="resize_shadow_start_color">#2a000000</color>
<color name="resize_shadow_end_color">#00000000</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 9faf5e8..9890614 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2000,6 +2000,18 @@
are controlled together (aliasing is true) or not. -->
<bool name="config_alias_ring_notif_stream_types">true</bool>
+ <!-- The number of volume steps for the notification stream -->
+ <integer name="config_audio_notif_vol_steps">7</integer>
+
+ <!-- The default volume for the notification stream -->
+ <integer name="config_audio_notif_vol_default">5</integer>
+
+ <!-- The number of volume steps for the ring stream -->
+ <integer name="config_audio_ring_vol_steps">7</integer>
+
+ <!-- The default volume for the ring stream -->
+ <integer name="config_audio_ring_vol_default">5</integer>
+
<!-- Flag indicating whether platform level volume adjustments are enabled for remote sessions
on grouped devices. -->
<bool name="config_volumeAdjustmentForRemoteGroupSessions">true</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f1643b6..01c8e96 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -278,6 +278,10 @@
<java-symbol type="bool" name="action_bar_embed_tabs" />
<java-symbol type="bool" name="action_bar_expanded_action_views_exclusive" />
<java-symbol type="bool" name="config_alias_ring_notif_stream_types" />
+ <java-symbol type="integer" name="config_audio_notif_vol_default" />
+ <java-symbol type="integer" name="config_audio_notif_vol_steps" />
+ <java-symbol type="integer" name="config_audio_ring_vol_default" />
+ <java-symbol type="integer" name="config_audio_ring_vol_steps" />
<java-symbol type="bool" name="config_avoidGfxAccel" />
<java-symbol type="bool" name="config_bluetooth_address_validation" />
<java-symbol type="integer" name="config_chooser_max_targets_per_row" />
diff --git a/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java b/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
index c63d18b..0cee526 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
@@ -270,4 +270,13 @@
assertThat(bd.getBitmap().getHeight()).isEqualTo(originalHeight);
}
+
+ @Test
+ public void resolveImage_iconWithOtherPackageResource_usesPackageContextDefinition()
+ throws IOException {
+ Icon icon = Icon.createWithResource("this_is_invalid", R.drawable.test32x24);
+ Drawable d = LocalImageResolver.resolveImage(icon, mContext);
+ // This drawable must not be loaded - if it was, the code ignored the package specification.
+ assertThat(d).isNull();
+ }
}
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 857af11..318cd32 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -277,7 +277,7 @@
* @see #setHeight(int)
* @see #setConfig(Config)
*/
- public void reconfigure(int width, int height, Config config) {
+ public void reconfigure(int width, int height, @NonNull Config config) {
checkRecycled("Can't call reconfigure() on a recycled bitmap");
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("width and height must be > 0");
@@ -336,7 +336,7 @@
* @see #setWidth(int)
* @see #setHeight(int)
*/
- public void setConfig(Config config) {
+ public void setConfig(@NonNull Config config) {
reconfigure(getWidth(), getHeight(), config);
}
@@ -590,7 +590,7 @@
* in the buffer.</p>
* @throws IllegalStateException if the bitmap's config is {@link Config#HARDWARE}
*/
- public void copyPixelsToBuffer(Buffer dst) {
+ public void copyPixelsToBuffer(@NonNull Buffer dst) {
checkHardware("unable to copyPixelsToBuffer, "
+ "pixel access is not supported on Config#HARDWARE bitmaps");
int elements = dst.remaining();
@@ -632,7 +632,7 @@
* first rewind the buffer.</p>
* @throws IllegalStateException if the bitmap's config is {@link Config#HARDWARE}
*/
- public void copyPixelsFromBuffer(Buffer src) {
+ public void copyPixelsFromBuffer(@NonNull Buffer src) {
checkRecycled("copyPixelsFromBuffer called on recycled bitmap");
checkHardware("unable to copyPixelsFromBuffer, Config#HARDWARE bitmaps are immutable");
@@ -686,7 +686,7 @@
* @return the new bitmap, or null if the copy could not be made.
* @throws IllegalArgumentException if config is {@link Config#HARDWARE} and isMutable is true
*/
- public Bitmap copy(Config config, boolean isMutable) {
+ public Bitmap copy(@NonNull Config config, boolean isMutable) {
checkRecycled("Can't copy a recycled bitmap");
if (config == Config.HARDWARE && isMutable) {
throw new IllegalArgumentException("Hardware bitmaps are always immutable");
@@ -791,6 +791,7 @@
* @return The new scaled bitmap or the source bitmap if no scaling is required.
* @throws IllegalArgumentException if width is <= 0, or height is <= 0
*/
+ @NonNull
public static Bitmap createScaledBitmap(@NonNull Bitmap src, int dstWidth, int dstHeight,
boolean filter) {
Matrix m = new Matrix();
@@ -810,6 +811,7 @@
* be the same object as source, or a copy may have been made. It is
* initialized with the same density and color space as the original bitmap.
*/
+ @NonNull
public static Bitmap createBitmap(@NonNull Bitmap src) {
return createBitmap(src, 0, 0, src.getWidth(), src.getHeight());
}
@@ -830,6 +832,7 @@
* outside of the dimensions of the source bitmap, or width is <= 0,
* or height is <= 0
*/
+ @NonNull
public static Bitmap createBitmap(@NonNull Bitmap source, int x, int y, int width, int height) {
return createBitmap(source, x, y, width, height, null, false);
}
@@ -865,6 +868,7 @@
* outside of the dimensions of the source bitmap, or width is <= 0,
* or height is <= 0, or if the source bitmap has already been recycled
*/
+ @NonNull
public static Bitmap createBitmap(@NonNull Bitmap source, int x, int y, int width, int height,
@Nullable Matrix m, boolean filter) {
@@ -985,6 +989,7 @@
* @throws IllegalArgumentException if the width or height are <= 0, or if
* Config is Config.HARDWARE, because hardware bitmaps are always immutable
*/
+ @NonNull
public static Bitmap createBitmap(int width, int height, @NonNull Config config) {
return createBitmap(width, height, config, true);
}
@@ -1003,6 +1008,7 @@
* @throws IllegalArgumentException if the width or height are <= 0, or if
* Config is Config.HARDWARE, because hardware bitmaps are always immutable
*/
+ @NonNull
public static Bitmap createBitmap(@Nullable DisplayMetrics display, int width,
int height, @NonNull Config config) {
return createBitmap(display, width, height, config, true);
@@ -1023,6 +1029,7 @@
* @throws IllegalArgumentException if the width or height are <= 0, or if
* Config is Config.HARDWARE, because hardware bitmaps are always immutable
*/
+ @NonNull
public static Bitmap createBitmap(int width, int height,
@NonNull Config config, boolean hasAlpha) {
return createBitmap(null, width, height, config, hasAlpha);
@@ -1050,6 +1057,7 @@
* {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}, or if
* the color space is null
*/
+ @NonNull
public static Bitmap createBitmap(int width, int height, @NonNull Config config,
boolean hasAlpha, @NonNull ColorSpace colorSpace) {
return createBitmap(null, width, height, config, hasAlpha, colorSpace);
@@ -1073,6 +1081,7 @@
* @throws IllegalArgumentException if the width or height are <= 0, or if
* Config is Config.HARDWARE, because hardware bitmaps are always immutable
*/
+ @NonNull
public static Bitmap createBitmap(@Nullable DisplayMetrics display, int width, int height,
@NonNull Config config, boolean hasAlpha) {
return createBitmap(display, width, height, config, hasAlpha,
@@ -1105,6 +1114,7 @@
* {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}, or if
* the color space is null
*/
+ @NonNull
public static Bitmap createBitmap(@Nullable DisplayMetrics display, int width, int height,
@NonNull Config config, boolean hasAlpha, @NonNull ColorSpace colorSpace) {
if (width <= 0 || height <= 0) {
@@ -1152,6 +1162,7 @@
* @throws IllegalArgumentException if the width or height are <= 0, or if
* the color array's length is less than the number of pixels.
*/
+ @NonNull
public static Bitmap createBitmap(@NonNull @ColorInt int[] colors, int offset, int stride,
int width, int height, @NonNull Config config) {
return createBitmap(null, colors, offset, stride, width, height, config);
@@ -1179,6 +1190,7 @@
* @throws IllegalArgumentException if the width or height are <= 0, or if
* the color array's length is less than the number of pixels.
*/
+ @NonNull
public static Bitmap createBitmap(@NonNull DisplayMetrics display,
@NonNull @ColorInt int[] colors, int offset, int stride,
int width, int height, @NonNull Config config) {
@@ -1221,6 +1233,7 @@
* @throws IllegalArgumentException if the width or height are <= 0, or if
* the color array's length is less than the number of pixels.
*/
+ @NonNull
public static Bitmap createBitmap(@NonNull @ColorInt int[] colors,
int width, int height, Config config) {
return createBitmap(null, colors, 0, width, width, height, config);
@@ -1245,6 +1258,7 @@
* @throws IllegalArgumentException if the width or height are <= 0, or if
* the color array's length is less than the number of pixels.
*/
+ @NonNull
public static Bitmap createBitmap(@Nullable DisplayMetrics display,
@NonNull @ColorInt int colors[], int width, int height, @NonNull Config config) {
return createBitmap(display, colors, 0, width, width, height, config);
@@ -1262,7 +1276,8 @@
* @return An immutable bitmap with a HARDWARE config whose contents are created
* from the recorded drawing commands in the Picture source.
*/
- public static @NonNull Bitmap createBitmap(@NonNull Picture source) {
+ @NonNull
+ public static Bitmap createBitmap(@NonNull Picture source) {
return createBitmap(source, source.getWidth(), source.getHeight(), Config.HARDWARE);
}
@@ -1283,7 +1298,8 @@
*
* @return An immutable bitmap with a configuration specified by the config parameter
*/
- public static @NonNull Bitmap createBitmap(@NonNull Picture source, int width, int height,
+ @NonNull
+ public static Bitmap createBitmap(@NonNull Picture source, int width, int height,
@NonNull Config config) {
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("width & height must be > 0");
@@ -1330,6 +1346,7 @@
* Returns an optional array of private data, used by the UI system for
* some bitmaps. Not intended to be called by applications.
*/
+ @Nullable
public byte[] getNinePatchChunk() {
return mNinePatchChunk;
}
@@ -1431,7 +1448,8 @@
* @return true if successfully compressed to the specified stream.
*/
@WorkerThread
- public boolean compress(CompressFormat format, int quality, OutputStream stream) {
+ public boolean compress(@NonNull CompressFormat format, int quality,
+ @NonNull OutputStream stream) {
checkRecycled("Can't compress a recycled bitmap");
// do explicit check before calling the native method
if (stream == null) {
@@ -1548,7 +1566,7 @@
* Convenience for calling {@link #getScaledWidth(int)} with the target
* density of the given {@link Canvas}.
*/
- public int getScaledWidth(Canvas canvas) {
+ public int getScaledWidth(@NonNull Canvas canvas) {
return scaleFromDensity(getWidth(), mDensity, canvas.mDensity);
}
@@ -1556,7 +1574,7 @@
* Convenience for calling {@link #getScaledHeight(int)} with the target
* density of the given {@link Canvas}.
*/
- public int getScaledHeight(Canvas canvas) {
+ public int getScaledHeight(@NonNull Canvas canvas) {
return scaleFromDensity(getHeight(), mDensity, canvas.mDensity);
}
@@ -1564,7 +1582,7 @@
* Convenience for calling {@link #getScaledWidth(int)} with the target
* density of the given {@link DisplayMetrics}.
*/
- public int getScaledWidth(DisplayMetrics metrics) {
+ public int getScaledWidth(@NonNull DisplayMetrics metrics) {
return scaleFromDensity(getWidth(), mDensity, metrics.densityDpi);
}
@@ -1572,7 +1590,7 @@
* Convenience for calling {@link #getScaledHeight(int)} with the target
* density of the given {@link DisplayMetrics}.
*/
- public int getScaledHeight(DisplayMetrics metrics) {
+ public int getScaledHeight(@NonNull DisplayMetrics metrics) {
return scaleFromDensity(getHeight(), mDensity, metrics.densityDpi);
}
@@ -1682,6 +1700,7 @@
* If the bitmap's internal config is in one of the public formats, return
* that config, otherwise return null.
*/
+ @NonNull
public final Config getConfig() {
if (mRecycled) {
Log.w(TAG, "Called getConfig() on a recycle()'d bitmap! This is undefined behavior!");
@@ -1967,7 +1986,7 @@
* to receive the specified number of pixels.
* @throws IllegalStateException if the bitmap's config is {@link Config#HARDWARE}
*/
- public void getPixels(@ColorInt int[] pixels, int offset, int stride,
+ public void getPixels(@NonNull @ColorInt int[] pixels, int offset, int stride,
int x, int y, int width, int height) {
checkRecycled("Can't call getPixels() on a recycled bitmap");
checkHardware("unable to getPixels(), "
@@ -2084,7 +2103,7 @@
* @throws ArrayIndexOutOfBoundsException if the pixels array is too small
* to receive the specified number of pixels.
*/
- public void setPixels(@ColorInt int[] pixels, int offset, int stride,
+ public void setPixels(@NonNull @ColorInt int[] pixels, int offset, int stride,
int x, int y, int width, int height) {
checkRecycled("Can't call setPixels() on a recycled bitmap");
if (!isMutable()) {
@@ -2098,7 +2117,7 @@
x, y, width, height);
}
- public static final @android.annotation.NonNull Parcelable.Creator<Bitmap> CREATOR
+ public static final @NonNull Parcelable.Creator<Bitmap> CREATOR
= new Parcelable.Creator<Bitmap>() {
/**
* Rebuilds a bitmap previously stored with writeToParcel().
@@ -2134,7 +2153,7 @@
* by the final pixel format
* @param p Parcel object to write the bitmap data into
*/
- public void writeToParcel(Parcel p, int flags) {
+ public void writeToParcel(@NonNull Parcel p, int flags) {
checkRecycled("Can't parcel a recycled bitmap");
noteHardwareBitmapSlowCall();
if (!nativeWriteToParcel(mNativePtr, mDensity, p)) {
@@ -2150,6 +2169,7 @@
* @return new bitmap containing the alpha channel of the original bitmap.
*/
@CheckResult
+ @NonNull
public Bitmap extractAlpha() {
return extractAlpha(null, null);
}
@@ -2180,7 +2200,8 @@
* paint that is passed to the draw call.
*/
@CheckResult
- public Bitmap extractAlpha(Paint paint, int[] offsetXY) {
+ @NonNull
+ public Bitmap extractAlpha(@Nullable Paint paint, int[] offsetXY) {
checkRecycled("Can't extractAlpha on a recycled bitmap");
long nativePaint = paint != null ? paint.getNativeInstance() : 0;
noteHardwareBitmapSlowCall();
@@ -2197,12 +2218,12 @@
* and pixel data as this bitmap. If any of those differ, return false.
* If other is null, return false.
*/
- public boolean sameAs(Bitmap other) {
+ @WorkerThread
+ public boolean sameAs(@Nullable Bitmap other) {
+ StrictMode.noteSlowCall("sameAs compares pixel data, not expected to be fast");
checkRecycled("Can't call sameAs on a recycled bitmap!");
- noteHardwareBitmapSlowCall();
if (this == other) return true;
if (other == null) return false;
- other.noteHardwareBitmapSlowCall();
if (other.isRecycled()) {
throw new IllegalArgumentException("Can't compare to a recycled bitmap!");
}
@@ -2247,7 +2268,8 @@
* @throws IllegalStateException if the bitmap's config is not {@link Config#HARDWARE}
* or if the bitmap has been recycled.
*/
- public @NonNull HardwareBuffer getHardwareBuffer() {
+ @NonNull
+ public HardwareBuffer getHardwareBuffer() {
checkRecycled("Can't getHardwareBuffer from a recycled bitmap");
HardwareBuffer hardwareBuffer = mHardwareBuffer == null ? null : mHardwareBuffer.get();
if (hardwareBuffer == null || hardwareBuffer.isClosed()) {
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index 7ad9aec..48aecd6 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -24,29 +24,32 @@
import java.util.Objects;
/**
- * Indicates the strategies can be used when calculating the text wrapping.
+ * Specifies the line-break strategies for text wrapping.
*
- * See <a href="https://www.w3.org/TR/css-text-3/#line-break-property">the line-break property</a>
+ * <p>See the
+ * <a href="https://www.w3.org/TR/css-text-3/#line-break-property" class="external">
+ * line-break property</a> for more information.</p>
*/
public final class LineBreakConfig {
/**
- * No line break style specified.
+ * No line-break rules are used for line breaking.
*/
public static final int LINE_BREAK_STYLE_NONE = 0;
/**
- * Use the least restrictive rule for line-breaking. This is usually used for short lines.
+ * The least restrictive line-break rules are used for line breaking. This
+ * setting is typically used for short lines.
*/
public static final int LINE_BREAK_STYLE_LOOSE = 1;
/**
- * Indicate breaking text with the most comment set of line-breaking rules.
+ * The most common line-break rules are used for line breaking.
*/
public static final int LINE_BREAK_STYLE_NORMAL = 2;
/**
- * Indicates breaking text with the most strictest line-breaking rules.
+ * The most strict line-break rules are used for line breaking.
*/
public static final int LINE_BREAK_STYLE_STRICT = 3;
@@ -59,15 +62,17 @@
public @interface LineBreakStyle {}
/**
- * No line break word style specified.
+ * No line-break word style is used for line breaking.
*/
public static final int LINE_BREAK_WORD_STYLE_NONE = 0;
/**
- * Indicates the line breaking is based on the phrased. This makes text wrapping only on
- * meaningful words. The support of the text wrapping word style varies depending on the
- * locales. If the locale does not support the phrase based text wrapping,
- * there will be no effect.
+ * Line breaking is based on phrases, which results in text wrapping only on
+ * meaningful words.
+ *
+ * <p>Support for this line-break word style depends on locale. If the
+ * current locale does not support phrase-based text wrapping, this setting
+ * has no effect.</p>
*/
public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1;
@@ -79,7 +84,7 @@
public @interface LineBreakWordStyle {}
/**
- * A builder for creating {@link LineBreakConfig}.
+ * A builder for creating a {@code LineBreakConfig} instance.
*/
public static final class Builder {
// The line break style for the LineBreakConfig.
@@ -95,16 +100,16 @@
private boolean mAutoPhraseBreaking = false;
/**
- * Builder constructor with line break parameters.
+ * Builder constructor.
*/
public Builder() {
}
/**
- * Set the line break style.
+ * Sets the line-break style.
*
- * @param lineBreakStyle the new line break style.
- * @return this Builder
+ * @param lineBreakStyle The new line-break style.
+ * @return This {@code Builder}.
*/
public @NonNull Builder setLineBreakStyle(@LineBreakStyle int lineBreakStyle) {
mLineBreakStyle = lineBreakStyle;
@@ -112,10 +117,10 @@
}
/**
- * Set the line break word style.
+ * Sets the line-break word style.
*
- * @param lineBreakWordStyle the new line break word style.
- * @return this Builder
+ * @param lineBreakWordStyle The new line-break word style.
+ * @return This {@code Builder}.
*/
public @NonNull Builder setLineBreakWordStyle(@LineBreakWordStyle int lineBreakWordStyle) {
mLineBreakWordStyle = lineBreakWordStyle;
@@ -123,7 +128,7 @@
}
/**
- * Enable or disable the automation of {@link LINE_BREAK_WORD_STYLE_PHRASE}.
+ * Enables or disables the automation of {@link LINE_BREAK_WORD_STYLE_PHRASE}.
*
* @hide
*/
@@ -133,9 +138,9 @@
}
/**
- * Build the {@link LineBreakConfig}
+ * Builds a {@link LineBreakConfig} instance.
*
- * @return the LineBreakConfig instance.
+ * @return The {@code LineBreakConfig} instance.
*/
public @NonNull LineBreakConfig build() {
return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle, mAutoPhraseBreaking);
@@ -143,11 +148,12 @@
}
/**
- * Create the LineBreakConfig instance.
+ * Creates a {@code LineBreakConfig} instance with the provided line break
+ * parameters.
*
- * @param lineBreakStyle the line break style for text wrapping.
- * @param lineBreakWordStyle the line break word style for text wrapping.
- * @return the {@link LineBreakConfig} instance.
+ * @param lineBreakStyle The line-break style for text wrapping.
+ * @param lineBreakWordStyle The line-break word style for text wrapping.
+ * @return The {@code LineBreakConfig} instance.
* @hide
*/
public static @NonNull LineBreakConfig getLineBreakConfig(@LineBreakStyle int lineBreakStyle,
@@ -185,8 +191,10 @@
private final boolean mAutoPhraseBreaking;
/**
- * Constructor with the line break parameters.
- * Use the {@link LineBreakConfig.Builder} to create the LineBreakConfig instance.
+ * Constructor with line-break parameters.
+ *
+ * <p>Use {@link LineBreakConfig.Builder} to create the
+ * {@code LineBreakConfig} instance.</p>
*/
private LineBreakConfig(@LineBreakStyle int lineBreakStyle,
@LineBreakWordStyle int lineBreakWordStyle, boolean autoPhraseBreaking) {
@@ -196,18 +204,18 @@
}
/**
- * Get the line break style.
+ * Gets the current line-break style.
*
- * @return The current line break style to be used for the text wrapping.
+ * @return The line-break style to be used for text wrapping.
*/
public @LineBreakStyle int getLineBreakStyle() {
return mLineBreakStyle;
}
/**
- * Get the line break word style.
+ * Gets the current line-break word style.
*
- * @return The current line break word style to be used for the text wrapping.
+ * @return The line-break word style to be used for text wrapping.
*/
public @LineBreakWordStyle int getLineBreakWordStyle() {
return mLineBreakWordStyle;
diff --git a/ktfmt_includes.txt b/ktfmt_includes.txt
new file mode 100644
index 0000000..96da8c9
--- /dev/null
+++ b/ktfmt_includes.txt
@@ -0,0 +1,9 @@
+packages/SystemUI/compose/
+packages/SystemUI/screenshot/
+packages/SystemUI/src/com/android/systemui/people/data
+packages/SystemUI/src/com/android/systemui/people/ui
+packages/SystemUI/src/com/android/systemui/keyguard/data
+packages/SystemUI/src/com/android/systemui/keyguard/dagger
+packages/SystemUI/src/com/android/systemui/keyguard/domain
+packages/SystemUI/src/com/android/systemui/keyguard/shared
+packages/SystemUI/src/com/android/systemui/keyguard/ui
\ No newline at end of file
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 8771ceb..de26b549 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
@@ -1094,13 +1094,16 @@
}
void updateNotNotifyingEntry(Bubble b, BubbleEntry entry, boolean showInShade) {
+ boolean showInShadeBefore = b.showInShade();
boolean isBubbleSelected = Objects.equals(b, mBubbleData.getSelectedBubble());
boolean isBubbleExpandedAndSelected = isStackExpanded() && isBubbleSelected;
b.setEntry(entry);
boolean suppress = isBubbleExpandedAndSelected || !showInShade || !b.showInShade();
b.setSuppressNotification(suppress);
b.setShowDot(!isBubbleExpandedAndSelected);
- mImpl.mCachedState.updateBubbleSuppressedState(b);
+ if (showInShadeBefore != b.showInShade()) {
+ mImpl.mCachedState.updateBubbleSuppressedState(b);
+ }
}
@VisibleForTesting
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index ff3c083..497a6f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -105,6 +105,10 @@
MATCH_PARENT));
((LayoutParams) mDropZoneView1.getLayoutParams()).weight = 1;
((LayoutParams) mDropZoneView2.getLayoutParams()).weight = 1;
+ int orientation = getResources().getConfiguration().orientation;
+ setOrientation(orientation == Configuration.ORIENTATION_LANDSCAPE
+ ? LinearLayout.HORIZONTAL
+ : LinearLayout.VERTICAL);
updateContainerMargins(getResources().getConfiguration().orientation);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
index 0e32663..7096a64 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
@@ -111,9 +111,6 @@
private final TaskSnapshot mSnapshot;
private final Rect mSourceRectHint;
- private float mTaskSnapshotScaleX;
- private float mTaskSnapshotScaleY;
-
public PipSnapshotOverlay(TaskSnapshot snapshot, Rect sourceRectHint) {
mSnapshot = snapshot;
mSourceRectHint = new Rect(sourceRectHint);
@@ -125,16 +122,16 @@
@Override
public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) {
- mTaskSnapshotScaleX = (float) mSnapshot.getTaskSize().x
+ final float taskSnapshotScaleX = (float) mSnapshot.getTaskSize().x
/ mSnapshot.getHardwareBuffer().getWidth();
- mTaskSnapshotScaleY = (float) mSnapshot.getTaskSize().y
+ final float taskSnapshotScaleY = (float) mSnapshot.getTaskSize().y
/ mSnapshot.getHardwareBuffer().getHeight();
tx.show(mLeash);
tx.setLayer(mLeash, Integer.MAX_VALUE);
tx.setBuffer(mLeash, mSnapshot.getHardwareBuffer());
// Relocate the content to parentLeash's coordinates.
tx.setPosition(mLeash, -mSourceRectHint.left, -mSourceRectHint.top);
- tx.setScale(mLeash, mTaskSnapshotScaleX, mTaskSnapshotScaleY);
+ tx.setScale(mLeash, taskSnapshotScaleX, taskSnapshotScaleY);
tx.reparent(mLeash, parentLeash);
tx.apply();
}
@@ -146,20 +143,6 @@
@Override
public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
- // Work around to make sure the snapshot overlay is aligned with PiP window before
- // the atomicTx is committed along with the final WindowContainerTransaction.
- final SurfaceControl.Transaction nonAtomicTx = new SurfaceControl.Transaction();
- final float scaleX = (float) destinationBounds.width()
- / mSourceRectHint.width();
- final float scaleY = (float) destinationBounds.height()
- / mSourceRectHint.height();
- final float scale = Math.max(
- scaleX * mTaskSnapshotScaleX, scaleY * mTaskSnapshotScaleY);
- nonAtomicTx.setScale(mLeash, scale, scale);
- nonAtomicTx.setPosition(mLeash,
- -scale * mSourceRectHint.left / mTaskSnapshotScaleX,
- -scale * mSourceRectHint.top / mTaskSnapshotScaleY);
- nonAtomicTx.apply();
atomicTx.remove(mLeash);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
index a0e2201..7619646 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
@@ -288,8 +288,10 @@
if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
mTargetViewContainer.getViewTreeObserver().addOnPreDrawListener(this);
- mTargetViewContainer.show();
}
+ // always invoke show, since the target might still be VISIBLE while playing hide animation,
+ // so we want to ensure it will show back again
+ mTargetViewContainer.show();
}
/** Animates the magnetic dismiss target out and then sets it to GONE. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
index 681d964..7fd03a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
@@ -56,7 +56,7 @@
return false;
}
final int taskId = new Integer(args[1]);
- final int sideStagePosition = args.length > 3
+ final int sideStagePosition = args.length > 2
? new Integer(args[2]) : SPLIT_POSITION_BOTTOM_OR_RIGHT;
mController.moveToSideStage(taskId, sideStagePosition);
return true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 4bc8e91..7e83d2f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1374,21 +1374,13 @@
}
}
} else if (isSideStage && hasChildren && !mMainStage.isActive()) {
- if (mFocusingTaskInfo != null && !isValidToEnterSplitScreen(mFocusingTaskInfo)) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- mSideStage.removeAllTasks(wct, true);
- wct.reorder(mRootTaskInfo.token, false /* onTop */);
- mTaskOrganizer.applyTransaction(wct);
- Slog.i(TAG, "cancel entering split screen, reason = "
- + exitReasonToString(EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW));
- } else {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- mSplitLayout.init();
- prepareEnterSplitScreen(wct);
- mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t ->
- updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */));
- }
+ // TODO (b/238697912) : Add the validation to prevent entering non-recovered status
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mSplitLayout.init();
+ prepareEnterSplitScreen(wct);
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t ->
+ updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */));
}
if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) {
mShouldUpdateRecents = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 08eb2c9..6c65966 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -64,6 +64,7 @@
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
@@ -203,14 +204,24 @@
}
@VisibleForTesting
- static boolean isRotationSeamless(@NonNull TransitionInfo info,
- DisplayController displayController) {
+ static int getRotationAnimationHint(@NonNull TransitionInfo.Change displayChange,
+ @NonNull TransitionInfo info, @NonNull DisplayController displayController) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- "Display is changing, check if it should be seamless.");
- boolean checkedDisplayLayout = false;
- boolean hasTask = false;
- boolean displayExplicitSeamless = false;
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ "Display is changing, resolve the animation hint.");
+ // The explicit request of display has the highest priority.
+ if (displayChange.getRotationAnimation() == ROTATION_ANIMATION_SEAMLESS) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ " display requests explicit seamless");
+ return ROTATION_ANIMATION_SEAMLESS;
+ }
+
+ boolean allTasksSeamless = false;
+ boolean rejectSeamless = false;
+ ActivityManager.RunningTaskInfo topTaskInfo = null;
+ int animationHint = ROTATION_ANIMATION_ROTATE;
+ // Traverse in top-to-bottom order so that the first task is top-most.
+ final int size = info.getChanges().size();
+ for (int i = 0; i < size; ++i) {
final TransitionInfo.Change change = info.getChanges().get(i);
// Only look at changing things. showing/hiding don't need to rotate.
@@ -223,95 +234,69 @@
if ((change.getFlags() & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
" display has system alert windows, so not seamless.");
- return false;
+ rejectSeamless = true;
}
- displayExplicitSeamless =
- change.getRotationAnimation() == ROTATION_ANIMATION_SEAMLESS;
} else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
if (change.getRotationAnimation() != ROTATION_ANIMATION_SEAMLESS) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
" wallpaper is participating but isn't seamless.");
- return false;
+ rejectSeamless = true;
}
} else if (change.getTaskInfo() != null) {
- hasTask = true;
+ final int anim = change.getRotationAnimation();
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ final boolean isTopTask = topTaskInfo == null;
+ if (isTopTask) {
+ topTaskInfo = taskInfo;
+ if (anim != ROTATION_ANIMATION_UNSPECIFIED
+ && anim != ROTATION_ANIMATION_SEAMLESS) {
+ animationHint = anim;
+ }
+ }
// We only enable seamless rotation if all the visible task windows requested it.
- if (change.getRotationAnimation() != ROTATION_ANIMATION_SEAMLESS) {
+ if (anim != ROTATION_ANIMATION_SEAMLESS) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
" task %s isn't requesting seamless, so not seamless.",
- change.getTaskInfo().taskId);
- return false;
- }
-
- // This is the only way to get display-id currently, so we will check display
- // capabilities here
- if (!checkedDisplayLayout) {
- // only need to check display once.
- checkedDisplayLayout = true;
- final DisplayLayout displayLayout = displayController.getDisplayLayout(
- change.getTaskInfo().displayId);
- // For the upside down rotation we don't rotate seamlessly as the navigation
- // bar moves position. Note most apps (using orientation:sensor or user as
- // opposed to fullSensor) will not enter the reverse portrait orientation, so
- // actually the orientation won't change at all.
- int upsideDownRotation = displayLayout.getUpsideDownRotation();
- if (change.getStartRotation() == upsideDownRotation
- || change.getEndRotation() == upsideDownRotation) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- " rotation involves upside-down portrait, so not seamless.");
- return false;
- }
-
- // If the navigation bar can't change sides, then it will jump when we change
- // orientations and we don't rotate seamlessly - unless that is allowed, eg.
- // with gesture navigation where the navbar is low-profile enough that this
- // isn't very noticeable.
- if (!displayLayout.allowSeamlessRotationDespiteNavBarMoving()
- && (!(displayLayout.navigationBarCanMove()
- && (change.getStartAbsBounds().width()
- != change.getStartAbsBounds().height())))) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- " nav bar changes sides, so not seamless.");
- return false;
- }
+ taskInfo.taskId);
+ allTasksSeamless = false;
+ } else if (isTopTask) {
+ allTasksSeamless = true;
}
}
}
- // ROTATION_ANIMATION_SEAMLESS can only be requested by task or display.
- if (hasTask || displayExplicitSeamless) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Rotation IS seamless.");
- return true;
+ if (!allTasksSeamless || rejectSeamless) {
+ return animationHint;
}
- return false;
- }
- /**
- * Gets the rotation animation for the topmost task. Assumes that seamless is checked
- * elsewhere, so it will default SEAMLESS to ROTATE.
- */
- private int getRotationAnimation(@NonNull TransitionInfo info) {
- // Traverse in top-to-bottom order so that the first task is top-most
- for (int i = 0; i < info.getChanges().size(); ++i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
-
- // Only look at changing things. showing/hiding don't need to rotate.
- if (change.getMode() != TRANSIT_CHANGE) continue;
-
- // This container isn't rotating, so we can ignore it.
- if (change.getEndRotation() == change.getStartRotation()) continue;
-
- if (change.getTaskInfo() != null) {
- final int anim = change.getRotationAnimation();
- if (anim == ROTATION_ANIMATION_UNSPECIFIED
- // Fallback animation for seamless should also be default.
- || anim == ROTATION_ANIMATION_SEAMLESS) {
- return ROTATION_ANIMATION_ROTATE;
- }
- return anim;
- }
+ // This is the only way to get display-id currently, so check display capabilities here.
+ final DisplayLayout displayLayout = displayController.getDisplayLayout(
+ topTaskInfo.displayId);
+ // For the upside down rotation we don't rotate seamlessly as the navigation bar moves
+ // position. Note most apps (using orientation:sensor or user as opposed to fullSensor)
+ // will not enter the reverse portrait orientation, so actually the orientation won't
+ // change at all.
+ final int upsideDownRotation = displayLayout.getUpsideDownRotation();
+ if (displayChange.getStartRotation() == upsideDownRotation
+ || displayChange.getEndRotation() == upsideDownRotation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ " rotation involves upside-down portrait, so not seamless.");
+ return animationHint;
}
- return ROTATION_ANIMATION_ROTATE;
+
+ // If the navigation bar can't change sides, then it will jump when we change orientations
+ // and we don't rotate seamlessly - unless that is allowed, e.g. with gesture navigation
+ // where the navbar is low-profile enough that this isn't very noticeable.
+ if (!displayLayout.allowSeamlessRotationDespiteNavBarMoving()
+ && (!(displayLayout.navigationBarCanMove()
+ && (displayChange.getStartAbsBounds().width()
+ != displayChange.getStartAbsBounds().height())))) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ " nav bar changes sides, so not seamless.");
+ return animationHint;
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Rotation IS seamless.");
+ return ROTATION_ANIMATION_SEAMLESS;
}
@Override
@@ -354,8 +339,8 @@
if (change.getMode() == TRANSIT_CHANGE && (change.getFlags() & FLAG_IS_DISPLAY) != 0) {
if (info.getType() == TRANSIT_CHANGE) {
- isSeamlessDisplayChange = isRotationSeamless(info, mDisplayController);
- final int anim = getRotationAnimation(info);
+ final int anim = getRotationAnimationHint(change, info, mDisplayController);
+ isSeamlessDisplayChange = anim == ROTATION_ANIMATION_SEAMLESS;
if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) {
startRotationAnimation(startTransaction, change, info, anim, animations,
onAnimFinish);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index 45b69f1..6388ca1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -86,8 +86,6 @@
private final float[] mTmpFloats = new float[9];
/** The leash of the changing window container. */
private final SurfaceControl mSurfaceControl;
- private final Rect mStartBounds = new Rect();
- private final Rect mEndBounds = new Rect();
private final int mAnimHint;
private final int mStartWidth;
@@ -105,8 +103,7 @@
*/
private SurfaceControl mBackColorSurface;
/** The leash using to animate screenshot layer. */
- private SurfaceControl mAnimLeash;
- private Transaction mTransaction;
+ private final SurfaceControl mAnimLeash;
// The current active animation to move from the old to the new rotated
// state. Which animation is run here will depend on the old and new
@@ -134,9 +131,6 @@
mStartRotation = change.getStartRotation();
mEndRotation = change.getEndRotation();
- mStartBounds.set(change.getStartAbsBounds());
- mEndBounds.set(change.getEndAbsBounds());
-
mAnimLeash = new SurfaceControl.Builder(session)
.setParent(rootLeash)
.setEffectLayer()
@@ -169,6 +163,8 @@
t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE);
t.show(mAnimLeash);
+ // Crop the real content in case it contains a larger child layer, e.g. wallpaper.
+ t.setCrop(mSurfaceControl, new Rect(0, 0, mEndWidth, mEndHeight));
final ColorSpace colorSpace = screenshotBuffer.getColorSpace();
final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
@@ -306,7 +302,6 @@
mRotateEnterAnimation.restrictDuration(MAX_ANIMATION_DURATION);
mRotateEnterAnimation.scaleCurrentDuration(animationScale);
- mTransaction = mTransactionPool.acquire();
if (customRotate) {
mRotateAlphaAnimation.initialize(mEndWidth, mEndHeight, mStartWidth, mStartHeight);
mRotateAlphaAnimation.restrictDuration(MAX_ANIMATION_DURATION);
@@ -386,22 +381,16 @@
}
public void kill() {
- Transaction t = mTransaction != null ? mTransaction : mTransactionPool.acquire();
+ final Transaction t = mTransactionPool.acquire();
if (mAnimLeash.isValid()) {
t.remove(mAnimLeash);
}
- if (mScreenshotLayer != null) {
- if (mScreenshotLayer.isValid()) {
- t.remove(mScreenshotLayer);
- }
- mScreenshotLayer = null;
+ if (mScreenshotLayer != null && mScreenshotLayer.isValid()) {
+ t.remove(mScreenshotLayer);
}
- if (mBackColorSurface != null) {
- if (mBackColorSurface.isValid()) {
- t.remove(mBackColorSurface);
- }
- mBackColorSurface = null;
+ if (mBackColorSurface != null && mBackColorSurface.isValid()) {
+ t.remove(mBackColorSurface);
}
t.apply();
mTransactionPool.release(t);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index b142039..c6492be 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -22,6 +22,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
import static android.view.WindowManager.TRANSIT_CHANGE;
@@ -553,64 +554,77 @@
final @Surface.Rotation int upsideDown = displays
.getDisplayLayout(DEFAULT_DISPLAY).getUpsideDownRotation();
+ TransitionInfo.Change displayChange = new ChangeBuilder(TRANSIT_CHANGE)
+ .setFlags(FLAG_IS_DISPLAY).setRotate().build();
+ // Set non-square display so nav bar won't be allowed to move.
+ displayChange.getStartAbsBounds().set(0, 0, 1000, 2000);
final TransitionInfo normalDispRotate = new TransitionInfoBuilder(TRANSIT_CHANGE)
- .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate()
- .build())
+ .addChange(displayChange)
.addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo).setRotate().build())
.build();
- assertFalse(DefaultTransitionHandler.isRotationSeamless(normalDispRotate, displays));
+ assertEquals(ROTATION_ANIMATION_ROTATE, DefaultTransitionHandler.getRotationAnimationHint(
+ displayChange, normalDispRotate, displays));
// Seamless if all tasks are seamless
final TransitionInfo rotateSeamless = new TransitionInfoBuilder(TRANSIT_CHANGE)
- .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate()
- .build())
+ .addChange(displayChange)
.addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
.setRotate(ROTATION_ANIMATION_SEAMLESS).build())
.build();
- assertTrue(DefaultTransitionHandler.isRotationSeamless(rotateSeamless, displays));
+ assertEquals(ROTATION_ANIMATION_SEAMLESS, DefaultTransitionHandler.getRotationAnimationHint(
+ displayChange, rotateSeamless, displays));
// Not seamless if there is PiP (or any other non-seamless task)
final TransitionInfo pipDispRotate = new TransitionInfoBuilder(TRANSIT_CHANGE)
- .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate()
- .build())
+ .addChange(displayChange)
.addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
.setRotate(ROTATION_ANIMATION_SEAMLESS).build())
.addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfoPip)
.setRotate().build())
.build();
- assertFalse(DefaultTransitionHandler.isRotationSeamless(pipDispRotate, displays));
-
- // Not seamless if one of rotations is upside-down
- final TransitionInfo seamlessUpsideDown = new TransitionInfoBuilder(TRANSIT_CHANGE)
- .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY)
- .setRotate(upsideDown, ROTATION_ANIMATION_UNSPECIFIED).build())
- .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
- .setRotate(upsideDown, ROTATION_ANIMATION_SEAMLESS).build())
- .build();
- assertFalse(DefaultTransitionHandler.isRotationSeamless(seamlessUpsideDown, displays));
-
- // Not seamless if system alert windows
- final TransitionInfo seamlessButAlert = new TransitionInfoBuilder(TRANSIT_CHANGE)
- .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(
- FLAG_IS_DISPLAY | FLAG_DISPLAY_HAS_ALERT_WINDOWS).setRotate().build())
- .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
- .setRotate(ROTATION_ANIMATION_SEAMLESS).build())
- .build();
- assertFalse(DefaultTransitionHandler.isRotationSeamless(seamlessButAlert, displays));
+ assertEquals(ROTATION_ANIMATION_ROTATE, DefaultTransitionHandler.getRotationAnimationHint(
+ displayChange, pipDispRotate, displays));
// Not seamless if there is no changed task.
final TransitionInfo noTask = new TransitionInfoBuilder(TRANSIT_CHANGE)
- .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY)
- .setRotate().build())
+ .addChange(displayChange)
.build();
- assertFalse(DefaultTransitionHandler.isRotationSeamless(noTask, displays));
+ assertEquals(ROTATION_ANIMATION_ROTATE, DefaultTransitionHandler.getRotationAnimationHint(
+ displayChange, noTask, displays));
- // Seamless if display is explicitly seamless.
- final TransitionInfo seamlessDisplay = new TransitionInfoBuilder(TRANSIT_CHANGE)
- .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY)
+ // Not seamless if one of rotations is upside-down
+ displayChange = new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY)
+ .setRotate(upsideDown, ROTATION_ANIMATION_UNSPECIFIED).build();
+ final TransitionInfo seamlessUpsideDown = new TransitionInfoBuilder(TRANSIT_CHANGE)
+ .addChange(displayChange)
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
+ .setRotate(upsideDown, ROTATION_ANIMATION_SEAMLESS).build())
+ .build();
+ assertEquals(ROTATION_ANIMATION_ROTATE, DefaultTransitionHandler.getRotationAnimationHint(
+ displayChange, seamlessUpsideDown, displays));
+
+ // Not seamless if system alert windows
+ displayChange = new ChangeBuilder(TRANSIT_CHANGE)
+ .setFlags(FLAG_IS_DISPLAY | FLAG_DISPLAY_HAS_ALERT_WINDOWS).setRotate().build();
+ final TransitionInfo seamlessButAlert = new TransitionInfoBuilder(TRANSIT_CHANGE)
+ .addChange(displayChange)
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
.setRotate(ROTATION_ANIMATION_SEAMLESS).build())
.build();
- assertTrue(DefaultTransitionHandler.isRotationSeamless(seamlessDisplay, displays));
+ assertEquals(ROTATION_ANIMATION_ROTATE, DefaultTransitionHandler.getRotationAnimationHint(
+ displayChange, seamlessButAlert, displays));
+
+ // Seamless if display is explicitly seamless.
+ displayChange = new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY)
+ .setRotate(ROTATION_ANIMATION_SEAMLESS).build();
+ final TransitionInfo seamlessDisplay = new TransitionInfoBuilder(TRANSIT_CHANGE)
+ .addChange(displayChange)
+ // The animation hint of task will be ignored.
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
+ .setRotate(ROTATION_ANIMATION_ROTATE).build())
+ .build();
+ assertEquals(ROTATION_ANIMATION_SEAMLESS, DefaultTransitionHandler.getRotationAnimationHint(
+ displayChange, seamlessDisplay, displays));
}
@Test
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index 2beb33a..9aa3787 100755
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -141,6 +141,9 @@
return {};
}
loaded_arsc = LoadedArsc::Load(data, length, loaded_idmap.get(), property_flags);
+ } else if (loaded_idmap != nullptr &&
+ IsFabricatedOverlay(std::string(loaded_idmap->OverlayApkPath()))) {
+ loaded_arsc = LoadedArsc::Load(loaded_idmap.get());
} else {
loaded_arsc = LoadedArsc::CreateEmpty();
}
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index 35b6170..5b69cca 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -820,6 +820,13 @@
return true;
}
+bool LoadedArsc::LoadStringPool(const LoadedIdmap* loaded_idmap) {
+ if (loaded_idmap != nullptr) {
+ global_string_pool_ = util::make_unique<OverlayStringPool>(loaded_idmap);
+ }
+ return true;
+}
+
std::unique_ptr<LoadedArsc> LoadedArsc::Load(incfs::map_ptr<void> data,
const size_t length,
const LoadedIdmap* loaded_idmap,
@@ -855,6 +862,16 @@
return loaded_arsc;
}
+std::unique_ptr<LoadedArsc> LoadedArsc::Load(const LoadedIdmap* loaded_idmap) {
+ ATRACE_NAME("LoadedArsc::Load");
+
+ // Not using make_unique because the constructor is private.
+ std::unique_ptr<LoadedArsc> loaded_arsc(new LoadedArsc());
+ loaded_arsc->LoadStringPool(loaded_idmap);
+ return loaded_arsc;
+}
+
+
std::unique_ptr<LoadedArsc> LoadedArsc::CreateEmpty() {
return std::unique_ptr<LoadedArsc>(new LoadedArsc());
}
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index b3d6a4d..e459639 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -314,6 +314,8 @@
const LoadedIdmap* loaded_idmap = nullptr,
package_property_t property_flags = 0U);
+ static std::unique_ptr<LoadedArsc> Load(const LoadedIdmap* loaded_idmap = nullptr);
+
// Create an empty LoadedArsc. This is used when an APK has no resources.arsc.
static std::unique_ptr<LoadedArsc> CreateEmpty();
@@ -338,6 +340,7 @@
LoadedArsc() = default;
bool LoadTable(
const Chunk& chunk, const LoadedIdmap* loaded_idmap, package_property_t property_flags);
+ bool LoadStringPool(const LoadedIdmap* loaded_idmap);
std::unique_ptr<ResStringPool> global_string_pool_ = util::make_unique<ResStringPool>();
std::vector<std::unique_ptr<const LoadedPackage>> packages_;
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 3d66244..8c614bc 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -53,7 +53,7 @@
// The version should only be changed when a backwards-incompatible change must be made to the
// fabricated overlay file format. Old fabricated overlays must be migrated to the new file format
// to prevent losing fabricated overlay data.
-constexpr const uint32_t kFabricatedOverlayCurrentVersion = 1;
+constexpr const uint32_t kFabricatedOverlayCurrentVersion = 2;
// Returns whether or not the path represents a fabricated overlay.
bool IsFabricatedOverlay(const std::string& path);
diff --git a/lowpan/java/Android.bp b/lowpan/java/Android.bp
deleted file mode 100644
index 58513d7..0000000
--- a/lowpan/java/Android.bp
+++ /dev/null
@@ -1,17 +0,0 @@
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-filegroup {
- name: "framework-lowpan-sources",
- srcs: [
- "**/*.java",
- "**/*.aidl",
- ],
- visibility: ["//frameworks/base"],
-}
diff --git a/lowpan/java/android/net/lowpan/ILowpanEnergyScanCallback.aidl b/lowpan/java/android/net/lowpan/ILowpanEnergyScanCallback.aidl
deleted file mode 100644
index f09dbe3..0000000
--- a/lowpan/java/android/net/lowpan/ILowpanEnergyScanCallback.aidl
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/** {@hide} */
-interface ILowpanEnergyScanCallback {
- oneway void onEnergyScanResult(int channel, int rssi);
- oneway void onEnergyScanFinished();
-}
diff --git a/lowpan/java/android/net/lowpan/ILowpanInterface.aidl b/lowpan/java/android/net/lowpan/ILowpanInterface.aidl
deleted file mode 100644
index 603dc3c..0000000
--- a/lowpan/java/android/net/lowpan/ILowpanInterface.aidl
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.net.IpPrefix;
-import android.net.lowpan.ILowpanEnergyScanCallback;
-import android.net.lowpan.ILowpanInterfaceListener;
-import android.net.lowpan.ILowpanNetScanCallback;
-import android.net.lowpan.LowpanBeaconInfo;
-import android.net.lowpan.LowpanChannelInfo;
-import android.net.lowpan.LowpanCredential;
-import android.net.lowpan.LowpanIdentity;
-import android.net.lowpan.LowpanProvision;
-
-/** {@hide} */
-interface ILowpanInterface {
-
- // These are here for the sake of C++ interface implementations.
-
- const String PERM_ACCESS_LOWPAN_STATE = "android.permission.ACCESS_LOWPAN_STATE";
- const String PERM_CHANGE_LOWPAN_STATE = "android.permission.CHANGE_LOWPAN_STATE";
- const String PERM_READ_LOWPAN_CREDENTIAL = "android.permission.READ_LOWPAN_CREDENTIAL";
-
- /**
- * Channel mask key.
- * Used for setting a channel mask when starting a scan.
- * Type: int[]
- * */
- const String KEY_CHANNEL_MASK = "android.net.lowpan.property.CHANNEL_MASK";
-
- /**
- * Max Transmit Power Key.
- * Used for setting the maximum transmit power when starting a network scan.
- * Type: Integer
- * */
- const String KEY_MAX_TX_POWER = "android.net.lowpan.property.MAX_TX_POWER";
-
- // Interface States
-
- const String STATE_OFFLINE = "offline";
- const String STATE_COMMISSIONING = "commissioning";
- const String STATE_ATTACHING = "attaching";
- const String STATE_ATTACHED = "attached";
- const String STATE_FAULT = "fault";
-
- // Device Roles
-
- const String ROLE_END_DEVICE = "end-device";
- const String ROLE_ROUTER = "router";
- const String ROLE_SLEEPY_END_DEVICE = "sleepy-end-device";
- const String ROLE_SLEEPY_ROUTER = "sleepy-router";
- const String ROLE_LEADER = "leader";
- const String ROLE_COORDINATOR = "coordinator";
- const String ROLE_DETACHED = "detached";
-
- const String NETWORK_TYPE_UNKNOWN = "unknown";
-
- /**
- * Network type for Thread 1.x networks.
- *
- * @see android.net.lowpan.LowpanIdentity#getType
- * @see #getLowpanIdentity
- */
- const String NETWORK_TYPE_THREAD_V1 = "org.threadgroup.thread.v1";
-
- // Service-Specific Error Code Constants
-
- const int ERROR_UNSPECIFIED = 1;
- const int ERROR_INVALID_ARGUMENT = 2;
- const int ERROR_DISABLED = 3;
- const int ERROR_WRONG_STATE = 4;
- const int ERROR_TIMEOUT = 5;
- const int ERROR_IO_FAILURE = 6;
- const int ERROR_NCP_PROBLEM = 7;
- const int ERROR_BUSY = 8;
- const int ERROR_ALREADY = 9;
- const int ERROR_CANCELED = 10;
- const int ERROR_FEATURE_NOT_SUPPORTED = 11;
- const int ERROR_JOIN_FAILED_UNKNOWN = 12;
- const int ERROR_JOIN_FAILED_AT_SCAN = 13;
- const int ERROR_JOIN_FAILED_AT_AUTH = 14;
- const int ERROR_FORM_FAILED_AT_SCAN = 15;
-
- // Methods
-
- @utf8InCpp String getName();
-
- @utf8InCpp String getNcpVersion();
- @utf8InCpp String getDriverVersion();
- LowpanChannelInfo[] getSupportedChannels();
- @utf8InCpp String[] getSupportedNetworkTypes();
- byte[] getMacAddress();
-
- boolean isEnabled();
- void setEnabled(boolean enabled);
-
- boolean isUp();
- boolean isCommissioned();
- boolean isConnected();
- @utf8InCpp String getState();
-
- @utf8InCpp String getRole();
- @utf8InCpp String getPartitionId();
- byte[] getExtendedAddress();
-
- LowpanIdentity getLowpanIdentity();
- LowpanCredential getLowpanCredential();
-
- @utf8InCpp String[] getLinkAddresses();
- IpPrefix[] getLinkNetworks();
-
- void join(in LowpanProvision provision);
- void form(in LowpanProvision provision);
- void attach(in LowpanProvision provision);
- void leave();
- void reset();
-
- void startCommissioningSession(in LowpanBeaconInfo beaconInfo);
- void closeCommissioningSession();
- oneway void sendToCommissioner(in byte[] packet);
-
- void beginLowPower();
- oneway void pollForData();
-
- oneway void onHostWake();
-
- void addListener(ILowpanInterfaceListener listener);
- oneway void removeListener(ILowpanInterfaceListener listener);
-
- void startNetScan(in Map properties, ILowpanNetScanCallback listener);
- oneway void stopNetScan();
-
- void startEnergyScan(in Map properties, ILowpanEnergyScanCallback listener);
- oneway void stopEnergyScan();
-
- void addOnMeshPrefix(in IpPrefix prefix, int flags);
- oneway void removeOnMeshPrefix(in IpPrefix prefix);
-
- void addExternalRoute(in IpPrefix prefix, int flags);
- oneway void removeExternalRoute(in IpPrefix prefix);
-}
diff --git a/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl b/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl
deleted file mode 100644
index 5e4049a..0000000
--- a/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.net.IpPrefix;
-import android.net.lowpan.LowpanIdentity;
-
-/** {@hide} */
-interface ILowpanInterfaceListener {
- oneway void onEnabledChanged(boolean value);
-
- oneway void onConnectedChanged(boolean value);
-
- oneway void onUpChanged(boolean value);
-
- oneway void onRoleChanged(@utf8InCpp String value);
-
- oneway void onStateChanged(@utf8InCpp String value);
-
- oneway void onLowpanIdentityChanged(in LowpanIdentity value);
-
- oneway void onLinkNetworkAdded(in IpPrefix value);
-
- oneway void onLinkNetworkRemoved(in IpPrefix value);
-
- oneway void onLinkAddressAdded(@utf8InCpp String value);
-
- oneway void onLinkAddressRemoved(@utf8InCpp String value);
-
- oneway void onReceiveFromCommissioner(in byte[] packet);
-}
diff --git a/lowpan/java/android/net/lowpan/ILowpanManager.aidl b/lowpan/java/android/net/lowpan/ILowpanManager.aidl
deleted file mode 100644
index 326aa65..0000000
--- a/lowpan/java/android/net/lowpan/ILowpanManager.aidl
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2016 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.net.lowpan;
-import android.net.lowpan.ILowpanInterface;
-import android.net.lowpan.ILowpanManagerListener;
-
-/** {@hide} */
-interface ILowpanManager {
-
- /* Keep this in sync with Context.LOWPAN_SERVICE */
- const String LOWPAN_SERVICE_NAME = "lowpan";
-
- ILowpanInterface getInterface(@utf8InCpp String name);
-
- @utf8InCpp String[] getInterfaceList();
-
- void addListener(ILowpanManagerListener listener);
- void removeListener(ILowpanManagerListener listener);
-
- void addInterface(ILowpanInterface lowpan_interface);
- void removeInterface(ILowpanInterface lowpan_interface);
-}
diff --git a/lowpan/java/android/net/lowpan/ILowpanManagerListener.aidl b/lowpan/java/android/net/lowpan/ILowpanManagerListener.aidl
deleted file mode 100644
index d4846f6..0000000
--- a/lowpan/java/android/net/lowpan/ILowpanManagerListener.aidl
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2016 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.net.lowpan;
-
-import android.net.lowpan.ILowpanInterface;
-
-/** {@hide} */
-interface ILowpanManagerListener {
- oneway void onInterfaceAdded(ILowpanInterface lowpanInterface);
- oneway void onInterfaceRemoved(ILowpanInterface lowpanInterface);
-}
diff --git a/lowpan/java/android/net/lowpan/ILowpanNetScanCallback.aidl b/lowpan/java/android/net/lowpan/ILowpanNetScanCallback.aidl
deleted file mode 100644
index 9743fce..0000000
--- a/lowpan/java/android/net/lowpan/ILowpanNetScanCallback.aidl
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.net.lowpan.LowpanBeaconInfo;
-
-/** {@hide} */
-interface ILowpanNetScanCallback {
- oneway void onNetScanBeacon(in LowpanBeaconInfo beacon);
- oneway void onNetScanFinished();
-}
diff --git a/lowpan/java/android/net/lowpan/InterfaceDisabledException.java b/lowpan/java/android/net/lowpan/InterfaceDisabledException.java
deleted file mode 100644
index e917d45..0000000
--- a/lowpan/java/android/net/lowpan/InterfaceDisabledException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating this operation requires the interface to be enabled.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class InterfaceDisabledException extends LowpanException {
-
- public InterfaceDisabledException() {}
-
- public InterfaceDisabledException(String message) {
- super(message);
- }
-
- public InterfaceDisabledException(String message, Throwable cause) {
- super(message, cause);
- }
-
- protected InterfaceDisabledException(Exception cause) {
- super(cause);
- }
-}
diff --git a/lowpan/java/android/net/lowpan/JoinFailedAtAuthException.java b/lowpan/java/android/net/lowpan/JoinFailedAtAuthException.java
deleted file mode 100644
index 7aceb71..0000000
--- a/lowpan/java/android/net/lowpan/JoinFailedAtAuthException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating the join operation was unable to find the given network.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class JoinFailedAtAuthException extends JoinFailedException {
-
- public JoinFailedAtAuthException() {}
-
- public JoinFailedAtAuthException(String message) {
- super(message);
- }
-
- public JoinFailedAtAuthException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public JoinFailedAtAuthException(Exception cause) {
- super(cause);
- }
-}
diff --git a/lowpan/java/android/net/lowpan/JoinFailedAtScanException.java b/lowpan/java/android/net/lowpan/JoinFailedAtScanException.java
deleted file mode 100644
index a4346f98..0000000
--- a/lowpan/java/android/net/lowpan/JoinFailedAtScanException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating the join operation was unable to find the given network.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class JoinFailedAtScanException extends JoinFailedException {
-
- public JoinFailedAtScanException() {}
-
- public JoinFailedAtScanException(String message) {
- super(message);
- }
-
- public JoinFailedAtScanException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public JoinFailedAtScanException(Exception cause) {
- super(cause);
- }
-}
diff --git a/lowpan/java/android/net/lowpan/JoinFailedException.java b/lowpan/java/android/net/lowpan/JoinFailedException.java
deleted file mode 100644
index e51d382..0000000
--- a/lowpan/java/android/net/lowpan/JoinFailedException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating the join operation has failed.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class JoinFailedException extends LowpanException {
-
- public JoinFailedException() {}
-
- public JoinFailedException(String message) {
- super(message);
- }
-
- public JoinFailedException(String message, Throwable cause) {
- super(message, cause);
- }
-
- protected JoinFailedException(Exception cause) {
- super(cause);
- }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanBeaconInfo.aidl b/lowpan/java/android/net/lowpan/LowpanBeaconInfo.aidl
deleted file mode 100644
index 9464fea..0000000
--- a/lowpan/java/android/net/lowpan/LowpanBeaconInfo.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-parcelable LowpanBeaconInfo cpp_header "android/net/lowpan/LowpanBeaconInfo.h";
diff --git a/lowpan/java/android/net/lowpan/LowpanBeaconInfo.java b/lowpan/java/android/net/lowpan/LowpanBeaconInfo.java
deleted file mode 100644
index 5d4a3a0..0000000
--- a/lowpan/java/android/net/lowpan/LowpanBeaconInfo.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import com.android.internal.util.HexDump;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Objects;
-import java.util.TreeSet;
-
-/**
- * Describes a LoWPAN Beacon
- *
- * @hide
- */
-// @SystemApi
-public class LowpanBeaconInfo implements Parcelable {
- public static final int UNKNOWN_RSSI = Integer.MAX_VALUE;
- public static final int UNKNOWN_LQI = 0;
-
- private LowpanIdentity mIdentity;
- private int mRssi = UNKNOWN_RSSI;
- private int mLqi = UNKNOWN_LQI;
- private byte[] mBeaconAddress = null;
- private final TreeSet<Integer> mFlags = new TreeSet<>();
-
- public static final int FLAG_CAN_ASSIST = 1;
-
- /** @hide */
- public static class Builder {
- final LowpanIdentity.Builder mIdentityBuilder = new LowpanIdentity.Builder();
- final LowpanBeaconInfo mBeaconInfo = new LowpanBeaconInfo();
-
- public Builder setLowpanIdentity(LowpanIdentity x) {
- mIdentityBuilder.setLowpanIdentity(x);
- return this;
- }
-
- public Builder setName(String x) {
- mIdentityBuilder.setName(x);
- return this;
- }
-
- public Builder setXpanid(byte x[]) {
- mIdentityBuilder.setXpanid(x);
- return this;
- }
-
- public Builder setPanid(int x) {
- mIdentityBuilder.setPanid(x);
- return this;
- }
-
- public Builder setChannel(int x) {
- mIdentityBuilder.setChannel(x);
- return this;
- }
-
- public Builder setType(String x) {
- mIdentityBuilder.setType(x);
- return this;
- }
-
- public Builder setRssi(int x) {
- mBeaconInfo.mRssi = x;
- return this;
- }
-
- public Builder setLqi(int x) {
- mBeaconInfo.mLqi = x;
- return this;
- }
-
- public Builder setBeaconAddress(byte x[]) {
- mBeaconInfo.mBeaconAddress = (x != null ? x.clone() : null);
- return this;
- }
-
- public Builder setFlag(int x) {
- mBeaconInfo.mFlags.add(x);
- return this;
- }
-
- public Builder setFlags(Collection<Integer> x) {
- mBeaconInfo.mFlags.addAll(x);
- return this;
- }
-
- public LowpanBeaconInfo build() {
- mBeaconInfo.mIdentity = mIdentityBuilder.build();
- if (mBeaconInfo.mBeaconAddress == null) {
- mBeaconInfo.mBeaconAddress = new byte[0];
- }
- return mBeaconInfo;
- }
- }
-
- private LowpanBeaconInfo() {}
-
- public LowpanIdentity getLowpanIdentity() {
- return mIdentity;
- }
-
- public int getRssi() {
- return mRssi;
- }
-
- public int getLqi() {
- return mLqi;
- }
-
- public byte[] getBeaconAddress() {
- return mBeaconAddress.clone();
- }
-
- public Collection<Integer> getFlags() {
- return (Collection<Integer>) mFlags.clone();
- }
-
- public boolean isFlagSet(int flag) {
- return mFlags.contains(flag);
- }
-
- @Override
- public String toString() {
- StringBuffer sb = new StringBuffer();
-
- sb.append(mIdentity.toString());
-
- if (mRssi != UNKNOWN_RSSI) {
- sb.append(", RSSI:").append(mRssi).append("dBm");
- }
-
- if (mLqi != UNKNOWN_LQI) {
- sb.append(", LQI:").append(mLqi);
- }
-
- if (mBeaconAddress.length > 0) {
- sb.append(", BeaconAddress:").append(HexDump.toHexString(mBeaconAddress));
- }
-
- for (Integer flag : mFlags) {
- switch (flag.intValue()) {
- case FLAG_CAN_ASSIST:
- sb.append(", CAN_ASSIST");
- break;
- default:
- sb.append(", FLAG_").append(Integer.toHexString(flag));
- break;
- }
- }
-
- return sb.toString();
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mIdentity, mRssi, mLqi, Arrays.hashCode(mBeaconAddress), mFlags);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof LowpanBeaconInfo)) {
- return false;
- }
- LowpanBeaconInfo rhs = (LowpanBeaconInfo) obj;
- return mIdentity.equals(rhs.mIdentity)
- && Arrays.equals(mBeaconAddress, rhs.mBeaconAddress)
- && mRssi == rhs.mRssi
- && mLqi == rhs.mLqi
- && mFlags.equals(rhs.mFlags);
- }
-
- /** Implement the Parcelable interface. */
- @Override
- public int describeContents() {
- return 0;
- }
-
- /** Implement the Parcelable interface. */
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- mIdentity.writeToParcel(dest, flags);
- dest.writeInt(mRssi);
- dest.writeInt(mLqi);
- dest.writeByteArray(mBeaconAddress);
-
- dest.writeInt(mFlags.size());
- for (Integer val : mFlags) {
- dest.writeInt(val);
- }
- }
-
- /** Implement the Parcelable interface. */
- public static final @android.annotation.NonNull Creator<LowpanBeaconInfo> CREATOR =
- new Creator<LowpanBeaconInfo>() {
- public LowpanBeaconInfo createFromParcel(Parcel in) {
- Builder builder = new Builder();
-
- builder.setLowpanIdentity(LowpanIdentity.CREATOR.createFromParcel(in));
-
- builder.setRssi(in.readInt());
- builder.setLqi(in.readInt());
-
- builder.setBeaconAddress(in.createByteArray());
-
- for (int i = in.readInt(); i > 0; i--) {
- builder.setFlag(in.readInt());
- }
-
- return builder.build();
- }
-
- public LowpanBeaconInfo[] newArray(int size) {
- return new LowpanBeaconInfo[size];
- }
- };
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanChannelInfo.aidl b/lowpan/java/android/net/lowpan/LowpanChannelInfo.aidl
deleted file mode 100644
index 0676deb..0000000
--- a/lowpan/java/android/net/lowpan/LowpanChannelInfo.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-parcelable LowpanChannelInfo cpp_header "android/net/lowpan/LowpanChannelInfo.h";
diff --git a/lowpan/java/android/net/lowpan/LowpanChannelInfo.java b/lowpan/java/android/net/lowpan/LowpanChannelInfo.java
deleted file mode 100644
index 12c98b6..0000000
--- a/lowpan/java/android/net/lowpan/LowpanChannelInfo.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import java.util.Objects;
-
-/**
- * Provides detailed information about a given channel.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanChannelInfo implements Parcelable {
-
- public static final int UNKNOWN_POWER = Integer.MAX_VALUE;
- public static final float UNKNOWN_FREQUENCY = 0.0f;
- public static final float UNKNOWN_BANDWIDTH = 0.0f;
-
- private int mIndex = 0;
- private String mName = null;
- private float mSpectrumCenterFrequency = UNKNOWN_FREQUENCY;
- private float mSpectrumBandwidth = UNKNOWN_BANDWIDTH;
- private int mMaxTransmitPower = UNKNOWN_POWER;
- private boolean mIsMaskedByRegulatoryDomain = false;
-
- /** @hide */
- public static LowpanChannelInfo getChannelInfoForIeee802154Page0(int index) {
- LowpanChannelInfo info = new LowpanChannelInfo();
-
- if (index < 0) {
- info = null;
-
- } else if (index == 0) {
- info.mSpectrumCenterFrequency = 868300000.0f;
- info.mSpectrumBandwidth = 600000.0f;
-
- } else if (index < 11) {
- info.mSpectrumCenterFrequency = 906000000.0f - (2000000.0f * 1) + 2000000.0f * (index);
- info.mSpectrumBandwidth = 0; // Unknown
-
- } else if (index < 26) {
- info.mSpectrumCenterFrequency =
- 2405000000.0f - (5000000.0f * 11) + 5000000.0f * (index);
- info.mSpectrumBandwidth = 2000000.0f;
-
- } else {
- info = null;
- }
-
- info.mName = Integer.toString(index);
-
- return info;
- }
-
- private LowpanChannelInfo() {}
-
- private LowpanChannelInfo(int index, String name, float cf, float bw) {
- mIndex = index;
- mName = name;
- mSpectrumCenterFrequency = cf;
- mSpectrumBandwidth = bw;
- }
-
- public String getName() {
- return mName;
- }
-
- public int getIndex() {
- return mIndex;
- }
-
- public int getMaxTransmitPower() {
- return mMaxTransmitPower;
- }
-
- public boolean isMaskedByRegulatoryDomain() {
- return mIsMaskedByRegulatoryDomain;
- }
-
- public float getSpectrumCenterFrequency() {
- return mSpectrumCenterFrequency;
- }
-
- public float getSpectrumBandwidth() {
- return mSpectrumBandwidth;
- }
-
- @Override
- public String toString() {
- StringBuffer sb = new StringBuffer();
-
- sb.append("Channel ").append(mIndex);
-
- if (mName != null && !mName.equals(Integer.toString(mIndex))) {
- sb.append(" (").append(mName).append(")");
- }
-
- if (mSpectrumCenterFrequency > 0.0f) {
- if (mSpectrumCenterFrequency > 1000000000.0f) {
- sb.append(", SpectrumCenterFrequency: ")
- .append(mSpectrumCenterFrequency / 1000000000.0f)
- .append("GHz");
- } else if (mSpectrumCenterFrequency > 1000000.0f) {
- sb.append(", SpectrumCenterFrequency: ")
- .append(mSpectrumCenterFrequency / 1000000.0f)
- .append("MHz");
- } else {
- sb.append(", SpectrumCenterFrequency: ")
- .append(mSpectrumCenterFrequency / 1000.0f)
- .append("kHz");
- }
- }
-
- if (mSpectrumBandwidth > 0.0f) {
- if (mSpectrumBandwidth > 1000000000.0f) {
- sb.append(", SpectrumBandwidth: ")
- .append(mSpectrumBandwidth / 1000000000.0f)
- .append("GHz");
- } else if (mSpectrumBandwidth > 1000000.0f) {
- sb.append(", SpectrumBandwidth: ")
- .append(mSpectrumBandwidth / 1000000.0f)
- .append("MHz");
- } else {
- sb.append(", SpectrumBandwidth: ")
- .append(mSpectrumBandwidth / 1000.0f)
- .append("kHz");
- }
- }
-
- if (mMaxTransmitPower != UNKNOWN_POWER) {
- sb.append(", MaxTransmitPower: ").append(mMaxTransmitPower).append("dBm");
- }
-
- return sb.toString();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof LowpanChannelInfo)) {
- return false;
- }
- LowpanChannelInfo rhs = (LowpanChannelInfo) obj;
- return Objects.equals(mName, rhs.mName)
- && mIndex == rhs.mIndex
- && mIsMaskedByRegulatoryDomain == rhs.mIsMaskedByRegulatoryDomain
- && mSpectrumCenterFrequency == rhs.mSpectrumCenterFrequency
- && mSpectrumBandwidth == rhs.mSpectrumBandwidth
- && mMaxTransmitPower == rhs.mMaxTransmitPower;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(
- mName,
- mIndex,
- mIsMaskedByRegulatoryDomain,
- mSpectrumCenterFrequency,
- mSpectrumBandwidth,
- mMaxTransmitPower);
- }
-
- /** Implement the Parcelable interface. */
- @Override
- public int describeContents() {
- return 0;
- }
-
- /** Implement the Parcelable interface. */
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mIndex);
- dest.writeString(mName);
- dest.writeFloat(mSpectrumCenterFrequency);
- dest.writeFloat(mSpectrumBandwidth);
- dest.writeInt(mMaxTransmitPower);
- dest.writeBoolean(mIsMaskedByRegulatoryDomain);
- }
-
- /** Implement the Parcelable interface. */
- public static final @android.annotation.NonNull Creator<LowpanChannelInfo> CREATOR =
- new Creator<LowpanChannelInfo>() {
-
- public LowpanChannelInfo createFromParcel(Parcel in) {
- LowpanChannelInfo info = new LowpanChannelInfo();
-
- info.mIndex = in.readInt();
- info.mName = in.readString();
- info.mSpectrumCenterFrequency = in.readFloat();
- info.mSpectrumBandwidth = in.readFloat();
- info.mMaxTransmitPower = in.readInt();
- info.mIsMaskedByRegulatoryDomain = in.readBoolean();
-
- return info;
- }
-
- public LowpanChannelInfo[] newArray(int size) {
- return new LowpanChannelInfo[size];
- }
- };
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanCommissioningSession.java b/lowpan/java/android/net/lowpan/LowpanCommissioningSession.java
deleted file mode 100644
index 8f75e8d..0000000
--- a/lowpan/java/android/net/lowpan/LowpanCommissioningSession.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.IpPrefix;
-import android.os.DeadObjectException;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.RemoteException;
-
-/**
- * Commissioning Session.
- *
- * <p>This class enables a device to learn the credential needed to join a network using a technique
- * called "in-band commissioning".
- *
- * @hide
- */
-// @SystemApi
-public class LowpanCommissioningSession {
-
- private final ILowpanInterface mBinder;
- private final LowpanBeaconInfo mBeaconInfo;
- private final ILowpanInterfaceListener mInternalCallback = new InternalCallback();
- private final Looper mLooper;
- private Handler mHandler;
- private Callback mCallback = null;
- private volatile boolean mIsClosed = false;
-
- /**
- * Callback base class for {@link LowpanCommissioningSession}
- *
- * @hide
- */
- // @SystemApi
- public abstract static class Callback {
- public void onReceiveFromCommissioner(@NonNull byte[] packet) {};
-
- public void onClosed() {};
- }
-
- private class InternalCallback extends ILowpanInterfaceListener.Stub {
- @Override
- public void onStateChanged(String value) {
- if (!mIsClosed) {
- switch (value) {
- case ILowpanInterface.STATE_OFFLINE:
- case ILowpanInterface.STATE_FAULT:
- synchronized (LowpanCommissioningSession.this) {
- lockedCleanup();
- }
- }
- }
- }
-
- @Override
- public void onReceiveFromCommissioner(byte[] packet) {
- mHandler.post(
- () -> {
- synchronized (LowpanCommissioningSession.this) {
- if (!mIsClosed && (mCallback != null)) {
- mCallback.onReceiveFromCommissioner(packet);
- }
- }
- });
- }
-
- // We ignore all other callbacks.
- @Override
- public void onEnabledChanged(boolean value) {}
-
- @Override
- public void onConnectedChanged(boolean value) {}
-
- @Override
- public void onUpChanged(boolean value) {}
-
- @Override
- public void onRoleChanged(String value) {}
-
- @Override
- public void onLowpanIdentityChanged(LowpanIdentity value) {}
-
- @Override
- public void onLinkNetworkAdded(IpPrefix value) {}
-
- @Override
- public void onLinkNetworkRemoved(IpPrefix value) {}
-
- @Override
- public void onLinkAddressAdded(String value) {}
-
- @Override
- public void onLinkAddressRemoved(String value) {}
- }
-
- LowpanCommissioningSession(
- ILowpanInterface binder, LowpanBeaconInfo beaconInfo, Looper looper) {
- mBinder = binder;
- mBeaconInfo = beaconInfo;
- mLooper = looper;
-
- if (mLooper != null) {
- mHandler = new Handler(mLooper);
- } else {
- mHandler = new Handler();
- }
-
- try {
- mBinder.addListener(mInternalCallback);
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
-
- private void lockedCleanup() {
- // Note: this method is only called from synchronized contexts.
-
- if (!mIsClosed) {
- try {
- mBinder.removeListener(mInternalCallback);
-
- } catch (DeadObjectException x) {
- /* We don't care if we receive a DOE at this point.
- * DOE is as good as success as far as we are concerned.
- */
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
-
- if (mCallback != null) {
- mHandler.post(() -> mCallback.onClosed());
- }
- }
-
- mCallback = null;
- mIsClosed = true;
- }
-
- /** TODO: doc */
- @NonNull
- public LowpanBeaconInfo getBeaconInfo() {
- return mBeaconInfo;
- }
-
- /** TODO: doc */
- public void sendToCommissioner(@NonNull byte[] packet) {
- if (!mIsClosed) {
- try {
- mBinder.sendToCommissioner(packet);
-
- } catch (DeadObjectException x) {
- /* This method is a best-effort delivery.
- * We don't care if we receive a DOE at this point.
- */
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
- }
-
- /** TODO: doc */
- public synchronized void setCallback(@Nullable Callback cb, @Nullable Handler handler) {
- if (!mIsClosed) {
- /* This class can be created with or without a default looper.
- * Also, this method can be called with or without a specific
- * handler. If a handler is specified, it is to always be used.
- * Otherwise, if there was a Looper specified when this object
- * was created, we create a new handle based on that looper.
- * Otherwise we just create a default handler object. Since we
- * don't really know how the previous handler was created, we
- * end up always replacing it here. This isn't a huge problem
- * because this method should be called infrequently.
- */
- if (handler != null) {
- mHandler = handler;
- } else if (mLooper != null) {
- mHandler = new Handler(mLooper);
- } else {
- mHandler = new Handler();
- }
- mCallback = cb;
- }
- }
-
- /** TODO: doc */
- public synchronized void close() {
- if (!mIsClosed) {
- try {
- mBinder.closeCommissioningSession();
-
- lockedCleanup();
-
- } catch (DeadObjectException x) {
- /* We don't care if we receive a DOE at this point.
- * DOE is as good as success as far as we are concerned.
- */
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
- }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanCredential.aidl b/lowpan/java/android/net/lowpan/LowpanCredential.aidl
deleted file mode 100644
index af0c2d6..0000000
--- a/lowpan/java/android/net/lowpan/LowpanCredential.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-parcelable LowpanCredential cpp_header "android/net/lowpan/LowpanCredential.h";
diff --git a/lowpan/java/android/net/lowpan/LowpanCredential.java b/lowpan/java/android/net/lowpan/LowpanCredential.java
deleted file mode 100644
index dcbb831..0000000
--- a/lowpan/java/android/net/lowpan/LowpanCredential.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import com.android.internal.util.HexDump;
-import java.util.Arrays;
-import java.util.Objects;
-
-/**
- * Describes a credential for a LoWPAN network.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanCredential implements Parcelable {
-
- public static final int UNSPECIFIED_KEY_INDEX = 0;
-
- private byte[] mMasterKey = null;
- private int mMasterKeyIndex = UNSPECIFIED_KEY_INDEX;
-
- LowpanCredential() {}
-
- private LowpanCredential(byte[] masterKey, int keyIndex) {
- setMasterKey(masterKey, keyIndex);
- }
-
- private LowpanCredential(byte[] masterKey) {
- setMasterKey(masterKey);
- }
-
- public static LowpanCredential createMasterKey(byte[] masterKey) {
- return new LowpanCredential(masterKey);
- }
-
- public static LowpanCredential createMasterKey(byte[] masterKey, int keyIndex) {
- return new LowpanCredential(masterKey, keyIndex);
- }
-
- void setMasterKey(byte[] masterKey) {
- if (masterKey != null) {
- masterKey = masterKey.clone();
- }
- mMasterKey = masterKey;
- }
-
- void setMasterKeyIndex(int keyIndex) {
- mMasterKeyIndex = keyIndex;
- }
-
- void setMasterKey(byte[] masterKey, int keyIndex) {
- setMasterKey(masterKey);
- setMasterKeyIndex(keyIndex);
- }
-
- public byte[] getMasterKey() {
- if (mMasterKey != null) {
- return mMasterKey.clone();
- }
- return null;
- }
-
- public int getMasterKeyIndex() {
- return mMasterKeyIndex;
- }
-
- public boolean isMasterKey() {
- return mMasterKey != null;
- }
-
- public String toSensitiveString() {
- StringBuffer sb = new StringBuffer();
-
- sb.append("<LowpanCredential");
-
- if (isMasterKey()) {
- sb.append(" MasterKey:").append(HexDump.toHexString(mMasterKey));
- if (mMasterKeyIndex != UNSPECIFIED_KEY_INDEX) {
- sb.append(", Index:").append(mMasterKeyIndex);
- }
- } else {
- sb.append(" empty");
- }
-
- sb.append(">");
-
- return sb.toString();
- }
-
- @Override
- public String toString() {
- StringBuffer sb = new StringBuffer();
-
- sb.append("<LowpanCredential");
-
- if (isMasterKey()) {
- // We don't print out the contents of the key here,
- // we only do that in toSensitiveString.
- sb.append(" MasterKey");
- if (mMasterKeyIndex != UNSPECIFIED_KEY_INDEX) {
- sb.append(", Index:").append(mMasterKeyIndex);
- }
- } else {
- sb.append(" empty");
- }
-
- sb.append(">");
-
- return sb.toString();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof LowpanCredential)) {
- return false;
- }
- LowpanCredential rhs = (LowpanCredential) obj;
- return Arrays.equals(mMasterKey, rhs.mMasterKey) && mMasterKeyIndex == rhs.mMasterKeyIndex;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(Arrays.hashCode(mMasterKey), mMasterKeyIndex);
- }
-
- /** Implement the Parcelable interface. */
- @Override
- public int describeContents() {
- return 0;
- }
-
- /** Implement the Parcelable interface. */
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeByteArray(mMasterKey);
- dest.writeInt(mMasterKeyIndex);
- }
-
- /** Implement the Parcelable interface. */
- public static final @android.annotation.NonNull Creator<LowpanCredential> CREATOR =
- new Creator<LowpanCredential>() {
-
- public LowpanCredential createFromParcel(Parcel in) {
- LowpanCredential credential = new LowpanCredential();
-
- credential.mMasterKey = in.createByteArray();
- credential.mMasterKeyIndex = in.readInt();
-
- return credential;
- }
-
- public LowpanCredential[] newArray(int size) {
- return new LowpanCredential[size];
- }
- };
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanEnergyScanResult.java b/lowpan/java/android/net/lowpan/LowpanEnergyScanResult.java
deleted file mode 100644
index da87752..0000000
--- a/lowpan/java/android/net/lowpan/LowpanEnergyScanResult.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Describes the result from one channel of an energy scan.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanEnergyScanResult {
- public static final int UNKNOWN = Integer.MAX_VALUE;
-
- private int mChannel = UNKNOWN;
- private int mMaxRssi = UNKNOWN;
-
- LowpanEnergyScanResult() {}
-
- public int getChannel() {
- return mChannel;
- }
-
- public int getMaxRssi() {
- return mMaxRssi;
- }
-
- void setChannel(int x) {
- mChannel = x;
- }
-
- void setMaxRssi(int x) {
- mMaxRssi = x;
- }
-
- @Override
- public String toString() {
- return "LowpanEnergyScanResult(channel: " + mChannel + ", maxRssi:" + mMaxRssi + ")";
- }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanException.java b/lowpan/java/android/net/lowpan/LowpanException.java
deleted file mode 100644
index 5dfce48..0000000
--- a/lowpan/java/android/net/lowpan/LowpanException.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.os.ServiceSpecificException;
-import android.util.AndroidException;
-
-/**
- * <code>LowpanException</code> is thrown if an action to a LoWPAN interface could not be performed
- * or a LoWPAN interface property could not be fetched or changed.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class LowpanException extends AndroidException {
- public LowpanException() {}
-
- public LowpanException(String message) {
- super(message);
- }
-
- public LowpanException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public LowpanException(Exception cause) {
- super(cause);
- }
-
- /* This method returns LowpanException so that the caller
- * can add "throw" before the invocation of this method.
- * This might seem superfluous, but it is actually to
- * help provide a hint to the java compiler that this
- * function will not return.
- */
- static LowpanException rethrowFromServiceSpecificException(ServiceSpecificException e)
- throws LowpanException {
- switch (e.errorCode) {
- case ILowpanInterface.ERROR_DISABLED:
- throw new InterfaceDisabledException(e);
-
- case ILowpanInterface.ERROR_WRONG_STATE:
- throw new WrongStateException(e);
-
- case ILowpanInterface.ERROR_CANCELED:
- throw new OperationCanceledException(e);
-
- case ILowpanInterface.ERROR_JOIN_FAILED_UNKNOWN:
- throw new JoinFailedException(e);
-
- case ILowpanInterface.ERROR_JOIN_FAILED_AT_SCAN:
- throw new JoinFailedAtScanException(e);
-
- case ILowpanInterface.ERROR_JOIN_FAILED_AT_AUTH:
- throw new JoinFailedAtAuthException(e);
-
- case ILowpanInterface.ERROR_FORM_FAILED_AT_SCAN:
- throw new NetworkAlreadyExistsException(e);
-
- case ILowpanInterface.ERROR_FEATURE_NOT_SUPPORTED:
- throw new LowpanException(
- e.getMessage() != null ? e.getMessage() : "Feature not supported", e);
-
- case ILowpanInterface.ERROR_NCP_PROBLEM:
- throw new LowpanRuntimeException(
- e.getMessage() != null ? e.getMessage() : "NCP problem", e);
-
- case ILowpanInterface.ERROR_INVALID_ARGUMENT:
- throw new LowpanRuntimeException(
- e.getMessage() != null ? e.getMessage() : "Invalid argument", e);
-
- case ILowpanInterface.ERROR_UNSPECIFIED:
- default:
- throw new LowpanRuntimeException(e);
- }
- }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanIdentity.aidl b/lowpan/java/android/net/lowpan/LowpanIdentity.aidl
deleted file mode 100644
index fcef98f..0000000
--- a/lowpan/java/android/net/lowpan/LowpanIdentity.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-parcelable LowpanIdentity cpp_header "android/net/lowpan/LowpanIdentity.h";
diff --git a/lowpan/java/android/net/lowpan/LowpanIdentity.java b/lowpan/java/android/net/lowpan/LowpanIdentity.java
deleted file mode 100644
index 1997bc4..0000000
--- a/lowpan/java/android/net/lowpan/LowpanIdentity.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.annotation.NonNull;
-import android.icu.text.StringPrep;
-import android.icu.text.StringPrepParseException;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.Log;
-import com.android.internal.util.HexDump;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.Objects;
-
-/**
- * Describes an instance of a LoWPAN network.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanIdentity implements Parcelable {
- private static final String TAG = LowpanIdentity.class.getSimpleName();
-
- // Constants
- public static final int UNSPECIFIED_CHANNEL = -1;
- public static final int UNSPECIFIED_PANID = 0xFFFFFFFF;
- // Builder
-
- /** @hide */
- // @SystemApi
- public static class Builder {
- private static final StringPrep stringPrep =
- StringPrep.getInstance(StringPrep.RFC3920_RESOURCEPREP);
-
- final LowpanIdentity mIdentity = new LowpanIdentity();
-
- private static String escape(@NonNull byte[] bytes) {
- StringBuffer sb = new StringBuffer();
- for (byte b : bytes) {
- if (b >= 32 && b <= 126) {
- sb.append((char) b);
- } else {
- sb.append(String.format("\\0x%02x", b & 0xFF));
- }
- }
- return sb.toString();
- }
-
- public Builder setLowpanIdentity(@NonNull LowpanIdentity x) {
- Objects.requireNonNull(x);
- setRawName(x.getRawName());
- setXpanid(x.getXpanid());
- setPanid(x.getPanid());
- setChannel(x.getChannel());
- setType(x.getType());
- return this;
- }
-
- public Builder setName(@NonNull String name) {
- Objects.requireNonNull(name);
- try {
- mIdentity.mName = stringPrep.prepare(name, StringPrep.DEFAULT);
- mIdentity.mRawName = mIdentity.mName.getBytes(StandardCharsets.UTF_8);
- mIdentity.mIsNameValid = true;
- } catch (StringPrepParseException x) {
- Log.w(TAG, x.toString());
- setRawName(name.getBytes(StandardCharsets.UTF_8));
- }
- return this;
- }
-
- public Builder setRawName(@NonNull byte[] name) {
- Objects.requireNonNull(name);
- mIdentity.mRawName = name.clone();
- mIdentity.mName = new String(name, StandardCharsets.UTF_8);
- try {
- String nameCheck = stringPrep.prepare(mIdentity.mName, StringPrep.DEFAULT);
- mIdentity.mIsNameValid =
- Arrays.equals(nameCheck.getBytes(StandardCharsets.UTF_8), name);
- } catch (StringPrepParseException x) {
- Log.w(TAG, x.toString());
- mIdentity.mIsNameValid = false;
- }
-
- // Non-normal names must be rendered differently to avoid confusion.
- if (!mIdentity.mIsNameValid) {
- mIdentity.mName = "«" + escape(name) + "»";
- }
-
- return this;
- }
-
- public Builder setXpanid(byte x[]) {
- mIdentity.mXpanid = (x != null ? x.clone() : null);
- return this;
- }
-
- public Builder setPanid(int x) {
- mIdentity.mPanid = x;
- return this;
- }
-
- public Builder setType(@NonNull String x) {
- mIdentity.mType = x;
- return this;
- }
-
- public Builder setChannel(int x) {
- mIdentity.mChannel = x;
- return this;
- }
-
- public LowpanIdentity build() {
- return mIdentity;
- }
- }
-
- LowpanIdentity() {}
-
- // Instance Variables
-
- private String mName = "";
- private boolean mIsNameValid = true;
- private byte[] mRawName = new byte[0];
- private String mType = "";
- private byte[] mXpanid = new byte[0];
- private int mPanid = UNSPECIFIED_PANID;
- private int mChannel = UNSPECIFIED_CHANNEL;
-
- // Public Getters
-
- public String getName() {
- return mName;
- }
-
- public boolean isNameValid() {
- return mIsNameValid;
- }
-
- public byte[] getRawName() {
- return mRawName.clone();
- }
-
- public byte[] getXpanid() {
- return mXpanid.clone();
- }
-
- public int getPanid() {
- return mPanid;
- }
-
- public String getType() {
- return mType;
- }
-
- public int getChannel() {
- return mChannel;
- }
-
- @Override
- public String toString() {
- StringBuffer sb = new StringBuffer();
-
- sb.append("Name:").append(getName());
-
- if (mType.length() > 0) {
- sb.append(", Type:").append(mType);
- }
-
- if (mXpanid.length > 0) {
- sb.append(", XPANID:").append(HexDump.toHexString(mXpanid));
- }
-
- if (mPanid != UNSPECIFIED_PANID) {
- sb.append(", PANID:").append(String.format("0x%04X", mPanid));
- }
-
- if (mChannel != UNSPECIFIED_CHANNEL) {
- sb.append(", Channel:").append(mChannel);
- }
-
- return sb.toString();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof LowpanIdentity)) {
- return false;
- }
- LowpanIdentity rhs = (LowpanIdentity) obj;
- return Arrays.equals(mRawName, rhs.mRawName)
- && Arrays.equals(mXpanid, rhs.mXpanid)
- && mType.equals(rhs.mType)
- && mPanid == rhs.mPanid
- && mChannel == rhs.mChannel;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(
- Arrays.hashCode(mRawName), mType, Arrays.hashCode(mXpanid), mPanid, mChannel);
- }
-
- /** Implement the Parcelable interface. */
- @Override
- public int describeContents() {
- return 0;
- }
-
- /** Implement the Parcelable interface. */
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeByteArray(mRawName);
- dest.writeString(mType);
- dest.writeByteArray(mXpanid);
- dest.writeInt(mPanid);
- dest.writeInt(mChannel);
- }
-
- /** Implement the Parcelable interface. */
- public static final @android.annotation.NonNull Creator<LowpanIdentity> CREATOR =
- new Creator<LowpanIdentity>() {
-
- public LowpanIdentity createFromParcel(Parcel in) {
- Builder builder = new Builder();
-
- builder.setRawName(in.createByteArray());
- builder.setType(in.readString());
- builder.setXpanid(in.createByteArray());
- builder.setPanid(in.readInt());
- builder.setChannel(in.readInt());
-
- return builder.build();
- }
-
- public LowpanIdentity[] newArray(int size) {
- return new LowpanIdentity[size];
- }
- };
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanInterface.java b/lowpan/java/android/net/lowpan/LowpanInterface.java
deleted file mode 100644
index 57e9135..0000000
--- a/lowpan/java/android/net/lowpan/LowpanInterface.java
+++ /dev/null
@@ -1,824 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.net.IpPrefix;
-import android.net.LinkAddress;
-import android.os.DeadObjectException;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceSpecificException;
-import android.util.Log;
-import java.util.HashMap;
-
-/**
- * Class for managing a specific Low-power Wireless Personal Area Network (LoWPAN) interface.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanInterface {
- private static final String TAG = LowpanInterface.class.getSimpleName();
-
- /** Detached role. The interface is not currently attached to a network. */
- public static final String ROLE_DETACHED = ILowpanInterface.ROLE_DETACHED;
-
- /** End-device role. End devices do not route traffic for other nodes. */
- public static final String ROLE_END_DEVICE = ILowpanInterface.ROLE_END_DEVICE;
-
- /** Router role. Routers help route traffic around the mesh network. */
- public static final String ROLE_ROUTER = ILowpanInterface.ROLE_ROUTER;
-
- /**
- * Sleepy End-Device role.
- *
- * <p>End devices with this role are nominally asleep, waking up periodically to check in with
- * their parent to see if there are packets destined for them. Such devices are capable of
- * extraordinarilly low power consumption, but packet latency can be on the order of dozens of
- * seconds(depending on how the node is configured).
- */
- public static final String ROLE_SLEEPY_END_DEVICE = ILowpanInterface.ROLE_SLEEPY_END_DEVICE;
-
- /**
- * Sleepy-router role.
- *
- * <p>Routers with this role are nominally asleep, waking up periodically to check in with other
- * routers and their children.
- */
- public static final String ROLE_SLEEPY_ROUTER = ILowpanInterface.ROLE_SLEEPY_ROUTER;
-
- /** TODO: doc */
- public static final String ROLE_LEADER = ILowpanInterface.ROLE_LEADER;
-
- /** TODO: doc */
- public static final String ROLE_COORDINATOR = ILowpanInterface.ROLE_COORDINATOR;
-
- /**
- * Offline state.
- *
- * <p>This is the initial state of the LoWPAN interface when the underlying driver starts. In
- * this state the NCP is idle and not connected to any network.
- *
- * <p>This state can be explicitly entered by calling {@link #reset()}, {@link #leave()}, or
- * <code>setUp(false)</code>, with the later two only working if we were not previously in the
- * {@link #STATE_FAULT} state.
- *
- * @see #getState()
- * @see #STATE_FAULT
- */
- public static final String STATE_OFFLINE = ILowpanInterface.STATE_OFFLINE;
-
- /**
- * Commissioning state.
- *
- * <p>The interface enters this state after a call to {@link #startCommissioningSession()}. This
- * state may only be entered directly from the {@link #STATE_OFFLINE} state.
- *
- * @see #startCommissioningSession()
- * @see #getState()
- * @hide
- */
- public static final String STATE_COMMISSIONING = ILowpanInterface.STATE_COMMISSIONING;
-
- /**
- * Attaching state.
- *
- * <p>The interface enters this state when it starts the process of trying to find other nodes
- * so that it can attach to any pre-existing network fragment, or when it is in the process of
- * calculating the optimal values for unspecified parameters when forming a new network.
- *
- * <p>The interface may stay in this state for a prolonged period of time (or may spontaneously
- * enter this state from {@link #STATE_ATTACHED}) if the underlying network technology is
- * heirarchical (like ZigBeeIP) or if the device role is that of an "end-device" ({@link
- * #ROLE_END_DEVICE} or {@link #ROLE_SLEEPY_END_DEVICE}). This is because such roles cannot
- * create their own network fragments.
- *
- * @see #STATE_ATTACHED
- * @see #getState()
- */
- public static final String STATE_ATTACHING = ILowpanInterface.STATE_ATTACHING;
-
- /**
- * Attached state.
- *
- * <p>The interface enters this state from {@link #STATE_ATTACHING} once it is actively
- * participating on a network fragment.
- *
- * @see #STATE_ATTACHING
- * @see #getState()
- */
- public static final String STATE_ATTACHED = ILowpanInterface.STATE_ATTACHED;
-
- /**
- * Fault state.
- *
- * <p>The interface will enter this state when the driver has detected some sort of problem from
- * which it was not immediately able to recover.
- *
- * <p>This state can be entered spontaneously from any other state. Calling {@link #reset} will
- * cause the device to return to the {@link #STATE_OFFLINE} state.
- *
- * @see #getState
- * @see #STATE_OFFLINE
- */
- public static final String STATE_FAULT = ILowpanInterface.STATE_FAULT;
-
- /**
- * Network type for Thread 1.x networks.
- *
- * @see android.net.lowpan.LowpanIdentity#getType
- * @see #getLowpanIdentity
- * @hide
- */
- public static final String NETWORK_TYPE_THREAD_V1 = ILowpanInterface.NETWORK_TYPE_THREAD_V1;
-
- public static final String EMPTY_PARTITION_ID = "";
-
- /**
- * Callback base class for LowpanInterface
- *
- * @hide
- */
- // @SystemApi
- public abstract static class Callback {
- public void onConnectedChanged(boolean value) {}
-
- public void onEnabledChanged(boolean value) {}
-
- public void onUpChanged(boolean value) {}
-
- public void onRoleChanged(@NonNull String value) {}
-
- public void onStateChanged(@NonNull String state) {}
-
- public void onLowpanIdentityChanged(@NonNull LowpanIdentity value) {}
-
- public void onLinkNetworkAdded(IpPrefix prefix) {}
-
- public void onLinkNetworkRemoved(IpPrefix prefix) {}
-
- public void onLinkAddressAdded(LinkAddress address) {}
-
- public void onLinkAddressRemoved(LinkAddress address) {}
- }
-
- private final ILowpanInterface mBinder;
- private final Looper mLooper;
- private final HashMap<Integer, ILowpanInterfaceListener> mListenerMap = new HashMap<>();
-
- /**
- * Create a new LowpanInterface instance. Applications will almost always want to use {@link
- * LowpanManager#getInterface LowpanManager.getInterface()} instead of this.
- *
- * @param context the application context
- * @param service the Binder interface
- * @param looper the Binder interface
- * @hide
- */
- public LowpanInterface(Context context, ILowpanInterface service, Looper looper) {
- /* We aren't currently using the context, but if we need
- * it later on we can easily add it to the class.
- */
-
- mBinder = service;
- mLooper = looper;
- }
-
- /**
- * Returns the ILowpanInterface object associated with this interface.
- *
- * @hide
- */
- public ILowpanInterface getService() {
- return mBinder;
- }
-
- // Public Actions
-
- /**
- * Form a new network with the given network information optional credential. Unspecified fields
- * in the network information will be filled in with reasonable values. If the network
- * credential is unspecified, one will be generated automatically.
- *
- * <p>This method will block until either the network was successfully formed or an error
- * prevents the network form being formed.
- *
- * <p>Upon success, the interface will be up and attached to the newly formed network.
- *
- * @see #join(LowpanProvision)
- */
- public void form(@NonNull LowpanProvision provision) throws LowpanException {
- try {
- mBinder.form(provision);
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- /**
- * Attempts to join a new network with the given network information. This method will block
- * until either the network was successfully joined or an error prevented the network from being
- * formed. Upon success, the interface will be up and attached to the newly joined network.
- *
- * <p>Note that “joining” is distinct from “attaching”: Joining requires at least one other peer
- * device to be present in order for the operation to complete successfully.
- */
- public void join(@NonNull LowpanProvision provision) throws LowpanException {
- try {
- mBinder.join(provision);
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- /**
- * Attaches to the network described by identity and credential. This is similar to {@link
- * #join}, except that (assuming the identity and credential are valid) it will always succeed
- * and provision the interface, even if there are no peers nearby.
- *
- * <p>This method will block execution until the operation has completed.
- */
- public void attach(@NonNull LowpanProvision provision) throws LowpanException {
- try {
- mBinder.attach(provision);
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- /**
- * Bring down the network interface and forget all non-volatile details about the current
- * network.
- *
- * <p>This method will block execution until the operation has completed.
- */
- public void leave() throws LowpanException {
- try {
- mBinder.leave();
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- /**
- * Start a new commissioning session. Will fail if the interface is attached to a network or if
- * the interface is disabled.
- */
- public @NonNull LowpanCommissioningSession startCommissioningSession(
- @NonNull LowpanBeaconInfo beaconInfo) throws LowpanException {
- try {
- mBinder.startCommissioningSession(beaconInfo);
-
- return new LowpanCommissioningSession(mBinder, beaconInfo, mLooper);
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- /**
- * Reset this network interface as if it has been power cycled. Will bring the network interface
- * down if it was previously up. Will not erase any non-volatile settings.
- *
- * <p>This method will block execution until the operation has completed.
- *
- * @hide
- */
- public void reset() throws LowpanException {
- try {
- mBinder.reset();
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- // Public Getters and Setters
-
- /** Returns the name of this network interface. */
- @NonNull
- public String getName() {
- try {
- return mBinder.getName();
-
- } catch (DeadObjectException x) {
- return "";
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
-
- /**
- * Indicates if the interface is enabled or disabled.
- *
- * @see #setEnabled
- * @see android.net.lowpan.LowpanException#LOWPAN_DISABLED
- */
- public boolean isEnabled() {
- try {
- return mBinder.isEnabled();
-
- } catch (DeadObjectException x) {
- return false;
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
-
- /**
- * Enables or disables the LoWPAN interface. When disabled, the interface is put into a
- * low-power state and all commands that require the NCP to be queried will fail with {@link
- * android.net.lowpan.LowpanException#LOWPAN_DISABLED}.
- *
- * @see #isEnabled
- * @see android.net.lowpan.LowpanException#LOWPAN_DISABLED
- * @hide
- */
- public void setEnabled(boolean enabled) throws LowpanException {
- try {
- mBinder.setEnabled(enabled);
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- /**
- * Indicates if the network interface is up or down.
- *
- * @hide
- */
- public boolean isUp() {
- try {
- return mBinder.isUp();
-
- } catch (DeadObjectException x) {
- return false;
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
-
- /**
- * Indicates if there is at least one peer in range.
- *
- * @return <code>true</code> if we have at least one other peer in range, <code>false</code>
- * otherwise.
- */
- public boolean isConnected() {
- try {
- return mBinder.isConnected();
-
- } catch (DeadObjectException x) {
- return false;
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
-
- /**
- * Indicates if this interface is currently commissioned onto an existing network. If the
- * interface is commissioned, the interface may be brought up using setUp().
- */
- public boolean isCommissioned() {
- try {
- return mBinder.isCommissioned();
-
- } catch (DeadObjectException x) {
- return false;
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
-
- /**
- * Get interface state
- *
- * <h3>State Diagram</h3>
- *
- * <img src="LowpanInterface-1.png" />
- *
- * @return The current state of the interface.
- * @see #STATE_OFFLINE
- * @see #STATE_COMMISSIONING
- * @see #STATE_ATTACHING
- * @see #STATE_ATTACHED
- * @see #STATE_FAULT
- */
- public String getState() {
- try {
- return mBinder.getState();
-
- } catch (DeadObjectException x) {
- return STATE_FAULT;
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
-
- /** Get network partition/fragment identifier. */
- public String getPartitionId() {
- try {
- return mBinder.getPartitionId();
-
- } catch (DeadObjectException x) {
- return EMPTY_PARTITION_ID;
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
-
- /** TODO: doc */
- public LowpanIdentity getLowpanIdentity() {
- try {
- return mBinder.getLowpanIdentity();
-
- } catch (DeadObjectException x) {
- return new LowpanIdentity();
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
-
- /** TODO: doc */
- @NonNull
- public String getRole() {
- try {
- return mBinder.getRole();
-
- } catch (DeadObjectException x) {
- return ROLE_DETACHED;
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
-
- /** TODO: doc */
- @Nullable
- public LowpanCredential getLowpanCredential() {
- try {
- return mBinder.getLowpanCredential();
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
-
- public @NonNull String[] getSupportedNetworkTypes() throws LowpanException {
- try {
- return mBinder.getSupportedNetworkTypes();
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- public @NonNull LowpanChannelInfo[] getSupportedChannels() throws LowpanException {
- try {
- return mBinder.getSupportedChannels();
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- // Listener Support
-
- /**
- * Registers a subclass of {@link LowpanInterface.Callback} to receive events.
- *
- * @param cb Subclass of {@link LowpanInterface.Callback} which will receive events.
- * @param handler If not <code>null</code>, events will be dispatched via the given handler
- * object. If <code>null</code>, the thread upon which events will be dispatched is
- * unspecified.
- * @see #registerCallback(Callback)
- * @see #unregisterCallback(Callback)
- */
- public void registerCallback(@NonNull Callback cb, @Nullable Handler handler) {
- ILowpanInterfaceListener.Stub listenerBinder =
- new ILowpanInterfaceListener.Stub() {
- private Handler mHandler;
-
- {
- if (handler != null) {
- mHandler = handler;
- } else if (mLooper != null) {
- mHandler = new Handler(mLooper);
- } else {
- mHandler = new Handler();
- }
- }
-
- @Override
- public void onEnabledChanged(boolean value) {
- mHandler.post(() -> cb.onEnabledChanged(value));
- }
-
- @Override
- public void onConnectedChanged(boolean value) {
- mHandler.post(() -> cb.onConnectedChanged(value));
- }
-
- @Override
- public void onUpChanged(boolean value) {
- mHandler.post(() -> cb.onUpChanged(value));
- }
-
- @Override
- public void onRoleChanged(String value) {
- mHandler.post(() -> cb.onRoleChanged(value));
- }
-
- @Override
- public void onStateChanged(String value) {
- mHandler.post(() -> cb.onStateChanged(value));
- }
-
- @Override
- public void onLowpanIdentityChanged(LowpanIdentity value) {
- mHandler.post(() -> cb.onLowpanIdentityChanged(value));
- }
-
- @Override
- public void onLinkNetworkAdded(IpPrefix value) {
- mHandler.post(() -> cb.onLinkNetworkAdded(value));
- }
-
- @Override
- public void onLinkNetworkRemoved(IpPrefix value) {
- mHandler.post(() -> cb.onLinkNetworkRemoved(value));
- }
-
- @Override
- public void onLinkAddressAdded(String value) {
- LinkAddress la;
- try {
- la = new LinkAddress(value);
- } catch (IllegalArgumentException x) {
- Log.e(
- TAG,
- "onLinkAddressAdded: Bad LinkAddress \"" + value + "\", " + x);
- return;
- }
- mHandler.post(() -> cb.onLinkAddressAdded(la));
- }
-
- @Override
- public void onLinkAddressRemoved(String value) {
- LinkAddress la;
- try {
- la = new LinkAddress(value);
- } catch (IllegalArgumentException x) {
- Log.e(
- TAG,
- "onLinkAddressRemoved: Bad LinkAddress \""
- + value
- + "\", "
- + x);
- return;
- }
- mHandler.post(() -> cb.onLinkAddressRemoved(la));
- }
-
- @Override
- public void onReceiveFromCommissioner(byte[] packet) {
- // This is only used by the LowpanCommissioningSession.
- }
- };
- try {
- mBinder.addListener(listenerBinder);
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
-
- synchronized (mListenerMap) {
- mListenerMap.put(System.identityHashCode(cb), listenerBinder);
- }
- }
-
- /**
- * Registers a subclass of {@link LowpanInterface.Callback} to receive events.
- *
- * <p>The thread upon which events will be dispatched is unspecified.
- *
- * @param cb Subclass of {@link LowpanInterface.Callback} which will receive events.
- * @see #registerCallback(Callback, Handler)
- * @see #unregisterCallback(Callback)
- */
- public void registerCallback(Callback cb) {
- registerCallback(cb, null);
- }
-
- /**
- * Unregisters a previously registered callback class.
- *
- * @param cb Subclass of {@link LowpanInterface.Callback} which was previously registered to
- * receive events.
- * @see #registerCallback(Callback, Handler)
- * @see #registerCallback(Callback)
- */
- public void unregisterCallback(Callback cb) {
- int hashCode = System.identityHashCode(cb);
- synchronized (mListenerMap) {
- ILowpanInterfaceListener listenerBinder = mListenerMap.get(hashCode);
-
- if (listenerBinder != null) {
- mListenerMap.remove(hashCode);
-
- try {
- mBinder.removeListener(listenerBinder);
- } catch (DeadObjectException x) {
- // We ignore a dead object exception because that
- // pretty clearly means our callback isn't registered.
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
- }
- }
-
- // Active and Passive Scanning
-
- /**
- * Creates a new {@link android.net.lowpan.LowpanScanner} object for this interface.
- *
- * <p>This method allocates a new unique object for each call.
- *
- * @see android.net.lowpan.LowpanScanner
- */
- public @NonNull LowpanScanner createScanner() {
- return new LowpanScanner(mBinder);
- }
-
- // Route Management
-
- /**
- * Makes a copy of the internal list of LinkAddresses.
- *
- * @hide
- */
- public LinkAddress[] getLinkAddresses() throws LowpanException {
- try {
- String[] linkAddressStrings = mBinder.getLinkAddresses();
- LinkAddress[] ret = new LinkAddress[linkAddressStrings.length];
- int i = 0;
- for (String str : linkAddressStrings) {
- ret[i++] = new LinkAddress(str);
- }
- return ret;
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- /**
- * Makes a copy of the internal list of networks reachable on via this link.
- *
- * @hide
- */
- public IpPrefix[] getLinkNetworks() throws LowpanException {
- try {
- return mBinder.getLinkNetworks();
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- /**
- * Advertise the given IP prefix as an on-mesh prefix.
- *
- * @hide
- */
- public void addOnMeshPrefix(IpPrefix prefix, int flags) throws LowpanException {
- try {
- mBinder.addOnMeshPrefix(prefix, flags);
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- /**
- * Remove an IP prefix previously advertised by this device from the list of advertised on-mesh
- * prefixes.
- *
- * @hide
- */
- public void removeOnMeshPrefix(IpPrefix prefix) {
- try {
- mBinder.removeOnMeshPrefix(prefix);
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- // Catch and ignore all service exceptions
- Log.e(TAG, x.toString());
- }
- }
-
- /**
- * Advertise this device to other devices on the mesh network as having a specific route to the
- * given network. This device will then receive forwarded traffic for that network.
- *
- * @hide
- */
- public void addExternalRoute(IpPrefix prefix, int flags) throws LowpanException {
- try {
- mBinder.addExternalRoute(prefix, flags);
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- /**
- * Revoke a previously advertised specific route to the given network.
- *
- * @hide
- */
- public void removeExternalRoute(IpPrefix prefix) {
- try {
- mBinder.removeExternalRoute(prefix);
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- // Catch and ignore all service exceptions
- Log.e(TAG, x.toString());
- }
- }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanManager.java b/lowpan/java/android/net/lowpan/LowpanManager.java
deleted file mode 100644
index 33b35e6..0000000
--- a/lowpan/java/android/net/lowpan/LowpanManager.java
+++ /dev/null
@@ -1,335 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.BackgroundThread;
-
-import java.lang.ref.WeakReference;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.WeakHashMap;
-
-/**
- * Manager object for looking up LoWPAN interfaces.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanManager {
- private static final String TAG = LowpanManager.class.getSimpleName();
-
- /** @hide */
- // @SystemApi
- public abstract static class Callback {
- public void onInterfaceAdded(LowpanInterface lowpanInterface) {}
-
- public void onInterfaceRemoved(LowpanInterface lowpanInterface) {}
- }
-
- private final Map<Integer, ILowpanManagerListener> mListenerMap = new HashMap<>();
- private final Map<String, LowpanInterface> mInterfaceCache = new HashMap<>();
-
- /* This is a WeakHashMap because we don't want to hold onto
- * a strong reference to ILowpanInterface, so that it can be
- * garbage collected if it isn't being used anymore. Since
- * the value class holds onto this specific ILowpanInterface,
- * we also need to have a weak reference to the value.
- * This design pattern allows us to skip removal of items
- * from this Map without leaking memory.
- */
- private final Map<IBinder, WeakReference<LowpanInterface>> mBinderCache =
- new WeakHashMap<>();
-
- private final ILowpanManager mService;
- private final Context mContext;
- private final Looper mLooper;
-
- // Static Methods
-
- public static LowpanManager from(Context context) {
- return (LowpanManager) context.getSystemService(Context.LOWPAN_SERVICE);
- }
-
- /** @hide */
- public static LowpanManager getManager() {
- IBinder binder = ServiceManager.getService(Context.LOWPAN_SERVICE);
-
- if (binder != null) {
- ILowpanManager service = ILowpanManager.Stub.asInterface(binder);
- return new LowpanManager(service);
- }
-
- return null;
- }
-
- // Constructors
-
- LowpanManager(ILowpanManager service) {
- mService = service;
- mContext = null;
- mLooper = null;
- }
-
- /**
- * Create a new LowpanManager instance. Applications will almost always want to use {@link
- * android.content.Context#getSystemService Context.getSystemService()} to retrieve the standard
- * {@link android.content.Context#LOWPAN_SERVICE Context.LOWPAN_SERVICE}.
- *
- * @param context the application context
- * @param service the Binder interface
- * @hide - hide this because it takes in a parameter of type ILowpanManager, which is a system
- * private class.
- */
- public LowpanManager(Context context, ILowpanManager service) {
- this(context, service, BackgroundThread.get().getLooper());
- }
-
- @VisibleForTesting
- public LowpanManager(Context context, ILowpanManager service, Looper looper) {
- mContext = context;
- mService = service;
- mLooper = looper;
- }
-
- /** @hide */
- @Nullable
- public LowpanInterface getInterfaceNoCreate(@NonNull ILowpanInterface ifaceService) {
- LowpanInterface iface = null;
-
- synchronized (mBinderCache) {
- if (mBinderCache.containsKey(ifaceService.asBinder())) {
- iface = mBinderCache.get(ifaceService.asBinder()).get();
- }
- }
-
- return iface;
- }
-
- /** @hide */
- @Nullable
- public LowpanInterface getInterface(@NonNull ILowpanInterface ifaceService) {
- LowpanInterface iface = null;
-
- try {
- synchronized (mBinderCache) {
- if (mBinderCache.containsKey(ifaceService.asBinder())) {
- iface = mBinderCache.get(ifaceService.asBinder()).get();
- }
-
- if (iface == null) {
- String ifaceName = ifaceService.getName();
-
- iface = new LowpanInterface(mContext, ifaceService, mLooper);
-
- synchronized (mInterfaceCache) {
- mInterfaceCache.put(iface.getName(), iface);
- }
-
- mBinderCache.put(ifaceService.asBinder(), new WeakReference(iface));
-
- /* Make sure we remove the object from the
- * interface cache if the associated service
- * dies.
- */
- ifaceService
- .asBinder()
- .linkToDeath(
- new IBinder.DeathRecipient() {
- @Override
- public void binderDied() {
- synchronized (mInterfaceCache) {
- LowpanInterface iface =
- mInterfaceCache.get(ifaceName);
-
- if ((iface != null)
- && (iface.getService() == ifaceService)) {
- mInterfaceCache.remove(ifaceName);
- }
- }
- }
- },
- 0);
- }
- }
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
-
- return iface;
- }
-
- /**
- * Returns a reference to the requested LowpanInterface object. If the given interface doesn't
- * exist, or it is not a LoWPAN interface, returns null.
- */
- @Nullable
- public LowpanInterface getInterface(@NonNull String name) {
- LowpanInterface iface = null;
-
- try {
- /* This synchronized block covers both branches of the enclosed
- * if() statement in order to avoid a race condition. Two threads
- * calling getInterface() with the same name would race to create
- * the associated LowpanInterface object, creating two of them.
- * Having the whole block be synchronized avoids that race.
- */
- synchronized (mInterfaceCache) {
- if (mInterfaceCache.containsKey(name)) {
- iface = mInterfaceCache.get(name);
-
- } else {
- ILowpanInterface ifaceService = mService.getInterface(name);
-
- if (ifaceService != null) {
- iface = getInterface(ifaceService);
- }
- }
- }
- } catch (RemoteException x) {
- throw x.rethrowFromSystemServer();
- }
-
- return iface;
- }
-
- /**
- * Returns a reference to the first registered LowpanInterface object. If there are no LoWPAN
- * interfaces registered, returns null.
- */
- @Nullable
- public LowpanInterface getInterface() {
- String[] ifaceList = getInterfaceList();
- if (ifaceList.length > 0) {
- return getInterface(ifaceList[0]);
- }
- return null;
- }
-
- /**
- * Returns a string array containing the names of LoWPAN interfaces. This list may contain fewer
- * interfaces if the calling process does not have permissions to see individual interfaces.
- */
- @NonNull
- public String[] getInterfaceList() {
- try {
- return mService.getInterfaceList();
- } catch (RemoteException x) {
- throw x.rethrowFromSystemServer();
- }
- }
-
- /**
- * Registers a callback object to receive notifications when LoWPAN interfaces are added or
- * removed.
- *
- * @hide
- */
- public void registerCallback(@NonNull Callback cb, @Nullable Handler handler)
- throws LowpanException {
- ILowpanManagerListener.Stub listenerBinder =
- new ILowpanManagerListener.Stub() {
- private Handler mHandler;
-
- {
- if (handler != null) {
- mHandler = handler;
- } else if (mLooper != null) {
- mHandler = new Handler(mLooper);
- } else {
- mHandler = new Handler();
- }
- }
-
- @Override
- public void onInterfaceAdded(ILowpanInterface ifaceService) {
- Runnable runnable =
- () -> {
- LowpanInterface iface = getInterface(ifaceService);
-
- if (iface != null) {
- cb.onInterfaceAdded(iface);
- }
- };
-
- mHandler.post(runnable);
- }
-
- @Override
- public void onInterfaceRemoved(ILowpanInterface ifaceService) {
- Runnable runnable =
- () -> {
- LowpanInterface iface = getInterfaceNoCreate(ifaceService);
-
- if (iface != null) {
- cb.onInterfaceRemoved(iface);
- }
- };
-
- mHandler.post(runnable);
- }
- };
- try {
- mService.addListener(listenerBinder);
- } catch (RemoteException x) {
- throw x.rethrowFromSystemServer();
- }
-
- synchronized (mListenerMap) {
- mListenerMap.put(Integer.valueOf(System.identityHashCode(cb)), listenerBinder);
- }
- }
-
- /** @hide */
- public void registerCallback(@NonNull Callback cb) throws LowpanException {
- registerCallback(cb, null);
- }
-
- /**
- * Unregisters a previously registered {@link LowpanManager.Callback} object.
- *
- * @hide
- */
- public void unregisterCallback(@NonNull Callback cb) {
- Integer hashCode = Integer.valueOf(System.identityHashCode(cb));
- ILowpanManagerListener listenerBinder = null;
-
- synchronized (mListenerMap) {
- listenerBinder = mListenerMap.get(hashCode);
- mListenerMap.remove(hashCode);
- }
-
- if (listenerBinder != null) {
- try {
- mService.removeListener(listenerBinder);
- } catch (RemoteException x) {
- throw x.rethrowFromSystemServer();
- }
- } else {
- throw new RuntimeException("Attempt to unregister an unknown callback");
- }
- }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanProperties.java b/lowpan/java/android/net/lowpan/LowpanProperties.java
deleted file mode 100644
index cc45ff85..0000000
--- a/lowpan/java/android/net/lowpan/LowpanProperties.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/** {@hide} */
-public final class LowpanProperties {
-
- public static final LowpanProperty<int[]> KEY_CHANNEL_MASK =
- new LowpanStandardProperty("android.net.lowpan.property.CHANNEL_MASK", int[].class);
-
- public static final LowpanProperty<Integer> KEY_MAX_TX_POWER =
- new LowpanStandardProperty("android.net.lowpan.property.MAX_TX_POWER", Integer.class);
-
- /** @hide */
- private LowpanProperties() {}
-
- /** @hide */
- static final class LowpanStandardProperty<T> extends LowpanProperty<T> {
- private final String mName;
- private final Class<T> mType;
-
- LowpanStandardProperty(String name, Class<T> type) {
- mName = name;
- mType = type;
- }
-
- @Override
- public String getName() {
- return mName;
- }
-
- @Override
- public Class<T> getType() {
- return mType;
- }
-
- @Override
- public String toString() {
- return getName();
- }
- }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanProperty.java b/lowpan/java/android/net/lowpan/LowpanProperty.java
deleted file mode 100644
index 7f26986..0000000
--- a/lowpan/java/android/net/lowpan/LowpanProperty.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import java.util.Map;
-
-/** {@hide} */
-public abstract class LowpanProperty<T> {
- public abstract String getName();
-
- public abstract Class<T> getType();
-
- public void putInMap(Map map, T value) {
- map.put(getName(), value);
- }
-
- public T getFromMap(Map map) {
- return (T) map.get(getName());
- }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanProvision.aidl b/lowpan/java/android/net/lowpan/LowpanProvision.aidl
deleted file mode 100644
index 100e9dc..0000000
--- a/lowpan/java/android/net/lowpan/LowpanProvision.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-parcelable LowpanProvision cpp_header "android/net/lowpan/LowpanProvision.h";
diff --git a/lowpan/java/android/net/lowpan/LowpanProvision.java b/lowpan/java/android/net/lowpan/LowpanProvision.java
deleted file mode 100644
index 68c8709..0000000
--- a/lowpan/java/android/net/lowpan/LowpanProvision.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
-import java.util.Objects;
-
-/**
- * Describes the information needed to describe a network
- *
- * @hide
- */
-// @SystemApi
-public class LowpanProvision implements Parcelable {
-
- // Builder
-
- /** @hide */
- // @SystemApi
- public static class Builder {
- private final LowpanProvision provision = new LowpanProvision();
-
- public Builder setLowpanIdentity(@NonNull LowpanIdentity identity) {
- provision.mIdentity = identity;
- return this;
- }
-
- public Builder setLowpanCredential(@NonNull LowpanCredential credential) {
- provision.mCredential = credential;
- return this;
- }
-
- public LowpanProvision build() {
- return provision;
- }
- }
-
- private LowpanProvision() {}
-
- // Instance Variables
-
- private LowpanIdentity mIdentity = new LowpanIdentity();
- private LowpanCredential mCredential = null;
-
- // Public Getters and Setters
-
- @NonNull
- public LowpanIdentity getLowpanIdentity() {
- return mIdentity;
- }
-
- @Nullable
- public LowpanCredential getLowpanCredential() {
- return mCredential;
- }
-
- @Override
- public String toString() {
- StringBuffer sb = new StringBuffer();
-
- sb.append("LowpanProvision { identity => ").append(mIdentity.toString());
-
- if (mCredential != null) {
- sb.append(", credential => ").append(mCredential.toString());
- }
-
- sb.append("}");
-
- return sb.toString();
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mIdentity, mCredential);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof LowpanProvision)) {
- return false;
- }
- LowpanProvision rhs = (LowpanProvision) obj;
-
- if (!mIdentity.equals(rhs.mIdentity)) {
- return false;
- }
-
- if (!Objects.equals(mCredential, rhs.mCredential)) {
- return false;
- }
-
- return true;
- }
-
- /** Implement the Parcelable interface. */
- @Override
- public int describeContents() {
- return 0;
- }
-
- /** Implement the Parcelable interface. */
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- mIdentity.writeToParcel(dest, flags);
- if (mCredential == null) {
- dest.writeBoolean(false);
- } else {
- dest.writeBoolean(true);
- mCredential.writeToParcel(dest, flags);
- }
- }
-
- /** Implement the Parcelable interface. */
- public static final @android.annotation.NonNull Creator<LowpanProvision> CREATOR =
- new Creator<LowpanProvision>() {
- public LowpanProvision createFromParcel(Parcel in) {
- Builder builder = new Builder();
-
- builder.setLowpanIdentity(LowpanIdentity.CREATOR.createFromParcel(in));
-
- if (in.readBoolean()) {
- builder.setLowpanCredential(LowpanCredential.CREATOR.createFromParcel(in));
- }
-
- return builder.build();
- }
-
- public LowpanProvision[] newArray(int size) {
- return new LowpanProvision[size];
- }
- };
-};
diff --git a/lowpan/java/android/net/lowpan/LowpanRuntimeException.java b/lowpan/java/android/net/lowpan/LowpanRuntimeException.java
deleted file mode 100644
index 71a5a13..0000000
--- a/lowpan/java/android/net/lowpan/LowpanRuntimeException.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.util.AndroidRuntimeException;
-
-/**
- * Generic runtime exception for LoWPAN operations.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanRuntimeException extends AndroidRuntimeException {
-
- public LowpanRuntimeException() {}
-
- public LowpanRuntimeException(String message) {
- super(message);
- }
-
- public LowpanRuntimeException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public LowpanRuntimeException(Exception cause) {
- super(cause);
- }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanScanner.java b/lowpan/java/android/net/lowpan/LowpanScanner.java
deleted file mode 100644
index 59156c4..0000000
--- a/lowpan/java/android/net/lowpan/LowpanScanner.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.ServiceSpecificException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * LoWPAN Scanner
- *
- * <p>This class allows performing network (active) scans and energy (passive) scans.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class LowpanScanner {
- private static final String TAG = LowpanScanner.class.getSimpleName();
-
- // Public Classes
-
- /**
- * Callback base class for LowpanScanner
- *
- * @hide
- */
- // @SystemApi
- public abstract static class Callback {
- public void onNetScanBeacon(LowpanBeaconInfo beacon) {}
-
- public void onEnergyScanResult(LowpanEnergyScanResult result) {}
-
- public void onScanFinished() {}
- }
-
- // Instance Variables
-
- private ILowpanInterface mBinder;
- private Callback mCallback = null;
- private Handler mHandler = null;
- private ArrayList<Integer> mChannelMask = null;
- private int mTxPower = Integer.MAX_VALUE;
-
- // Constructors/Accessors and Exception Glue
-
- LowpanScanner(@NonNull ILowpanInterface binder) {
- mBinder = binder;
- }
-
- /** Sets an instance of {@link LowpanScanner.Callback} to receive events. */
- public synchronized void setCallback(@Nullable Callback cb, @Nullable Handler handler) {
- mCallback = cb;
- mHandler = handler;
- }
-
- /** Sets an instance of {@link LowpanScanner.Callback} to receive events. */
- public void setCallback(@Nullable Callback cb) {
- setCallback(cb, null);
- }
-
- /**
- * Sets the channel mask to use when scanning.
- *
- * @param mask The channel mask to use when scanning. If <code>null</code>, any previously set
- * channel mask will be cleared and all channels not masked by the current regulatory zone
- * will be scanned.
- */
- public void setChannelMask(@Nullable Collection<Integer> mask) {
- if (mask == null) {
- mChannelMask = null;
- } else {
- if (mChannelMask == null) {
- mChannelMask = new ArrayList<>();
- } else {
- mChannelMask.clear();
- }
- mChannelMask.addAll(mask);
- }
- }
-
- /**
- * Gets the current channel mask.
- *
- * @return the current channel mask, or <code>null</code> if no channel mask is currently set.
- */
- public @Nullable Collection<Integer> getChannelMask() {
- return (Collection<Integer>) mChannelMask.clone();
- }
-
- /**
- * Adds a channel to the channel mask used for scanning.
- *
- * <p>If a channel mask was previously <code>null</code>, a new one is created containing only
- * this channel. May be called multiple times to add additional channels ot the channel mask.
- *
- * @see #setChannelMask
- * @see #getChannelMask
- * @see #getTxPower
- */
- public void addChannel(int channel) {
- if (mChannelMask == null) {
- mChannelMask = new ArrayList<>();
- }
- mChannelMask.add(Integer.valueOf(channel));
- }
-
- /**
- * Sets the maximum transmit power to be used for active scanning.
- *
- * <p>The actual transmit power used is the lesser of this value and the currently configured
- * maximum transmit power for the interface.
- *
- * @see #getTxPower
- */
- public void setTxPower(int txPower) {
- mTxPower = txPower;
- }
-
- /**
- * Gets the maximum transmit power used for active scanning.
- *
- * @see #setTxPower
- */
- public int getTxPower() {
- return mTxPower;
- }
-
- private Map<String, Object> createScanOptionMap() {
- Map<String, Object> map = new HashMap();
-
- if (mChannelMask != null) {
- LowpanProperties.KEY_CHANNEL_MASK.putInMap(
- map, mChannelMask.stream().mapToInt(i -> i).toArray());
- }
-
- if (mTxPower != Integer.MAX_VALUE) {
- LowpanProperties.KEY_MAX_TX_POWER.putInMap(map, Integer.valueOf(mTxPower));
- }
-
- return map;
- }
-
- /**
- * Start a network scan.
- *
- * <p>This method will return once the scan has started.
- *
- * @see #stopNetScan
- */
- public void startNetScan() throws LowpanException {
- Map<String, Object> map = createScanOptionMap();
-
- ILowpanNetScanCallback binderListener =
- new ILowpanNetScanCallback.Stub() {
- public void onNetScanBeacon(LowpanBeaconInfo beaconInfo) {
- Callback callback;
- Handler handler;
-
- synchronized (LowpanScanner.this) {
- callback = mCallback;
- handler = mHandler;
- }
-
- if (callback == null) {
- return;
- }
-
- Runnable runnable = () -> callback.onNetScanBeacon(beaconInfo);
-
- if (handler != null) {
- handler.post(runnable);
- } else {
- runnable.run();
- }
- }
-
- public void onNetScanFinished() {
- Callback callback;
- Handler handler;
-
- synchronized (LowpanScanner.this) {
- callback = mCallback;
- handler = mHandler;
- }
-
- if (callback == null) {
- return;
- }
-
- Runnable runnable = () -> callback.onScanFinished();
-
- if (handler != null) {
- handler.post(runnable);
- } else {
- runnable.run();
- }
- }
- };
-
- try {
- mBinder.startNetScan(map, binderListener);
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- /**
- * Stop a network scan currently in progress.
- *
- * @see #startNetScan
- */
- public void stopNetScan() {
- try {
- mBinder.stopNetScan();
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
-
- /**
- * Start an energy scan.
- *
- * <p>This method will return once the scan has started.
- *
- * @see #stopEnergyScan
- */
- public void startEnergyScan() throws LowpanException {
- Map<String, Object> map = createScanOptionMap();
-
- ILowpanEnergyScanCallback binderListener =
- new ILowpanEnergyScanCallback.Stub() {
- public void onEnergyScanResult(int channel, int rssi) {
- Callback callback = mCallback;
- Handler handler = mHandler;
-
- if (callback == null) {
- return;
- }
-
- Runnable runnable =
- () -> {
- if (callback != null) {
- LowpanEnergyScanResult result =
- new LowpanEnergyScanResult();
- result.setChannel(channel);
- result.setMaxRssi(rssi);
- callback.onEnergyScanResult(result);
- }
- };
-
- if (handler != null) {
- handler.post(runnable);
- } else {
- runnable.run();
- }
- }
-
- public void onEnergyScanFinished() {
- Callback callback = mCallback;
- Handler handler = mHandler;
-
- if (callback == null) {
- return;
- }
-
- Runnable runnable = () -> callback.onScanFinished();
-
- if (handler != null) {
- handler.post(runnable);
- } else {
- runnable.run();
- }
- }
- };
-
- try {
- mBinder.startEnergyScan(map, binderListener);
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- /**
- * Stop an energy scan currently in progress.
- *
- * @see #startEnergyScan
- */
- public void stopEnergyScan() {
- try {
- mBinder.stopEnergyScan();
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
-}
diff --git a/lowpan/java/android/net/lowpan/NetworkAlreadyExistsException.java b/lowpan/java/android/net/lowpan/NetworkAlreadyExistsException.java
deleted file mode 100644
index 90ef498..0000000
--- a/lowpan/java/android/net/lowpan/NetworkAlreadyExistsException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating the form operation found a network nearby with the same identity.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class NetworkAlreadyExistsException extends LowpanException {
-
- public NetworkAlreadyExistsException() {}
-
- public NetworkAlreadyExistsException(String message) {
- super(message, null);
- }
-
- public NetworkAlreadyExistsException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public NetworkAlreadyExistsException(Exception cause) {
- super(cause);
- }
-}
diff --git a/lowpan/java/android/net/lowpan/OperationCanceledException.java b/lowpan/java/android/net/lowpan/OperationCanceledException.java
deleted file mode 100644
index fcafe3a..0000000
--- a/lowpan/java/android/net/lowpan/OperationCanceledException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating this operation was canceled by the driver before it could finish.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class OperationCanceledException extends LowpanException {
-
- public OperationCanceledException() {}
-
- public OperationCanceledException(String message) {
- super(message);
- }
-
- public OperationCanceledException(String message, Throwable cause) {
- super(message, cause);
- }
-
- protected OperationCanceledException(Exception cause) {
- super(cause);
- }
-}
diff --git a/lowpan/java/android/net/lowpan/WrongStateException.java b/lowpan/java/android/net/lowpan/WrongStateException.java
deleted file mode 100644
index 3565419..0000000
--- a/lowpan/java/android/net/lowpan/WrongStateException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating the interface is the wrong state for an operation.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class WrongStateException extends LowpanException {
-
- public WrongStateException() {}
-
- public WrongStateException(String message) {
- super(message);
- }
-
- public WrongStateException(String message, Throwable cause) {
- super(message, cause);
- }
-
- protected WrongStateException(Exception cause) {
- super(cause);
- }
-}
diff --git a/lowpan/java/android/net/lowpan/package.html b/lowpan/java/android/net/lowpan/package.html
deleted file mode 100644
index 342e32e..0000000
--- a/lowpan/java/android/net/lowpan/package.html
+++ /dev/null
@@ -1,29 +0,0 @@
-<HTML>
-<BODY>
-<p>@SystemApi</p>
-<!-- @hide -->
-<p>Provides classes to manage Low-power Wireless Personal Area Network (LoWPAN) functionality on the device.
-Examples of such network technologies include <a href="http://threadgroup.org/">Thread</a> and
-<a href="http://www.zigbee.org/zigbee-for-developers/network-specifications/zigbeeip/">ZigBee IP</a>.</p>
-<p>The LoWPAN APIs provide a means by which applications can communicate
-with the lower-level wireless stack that provides LoWPAN network access.</p>
-
-<p>Some APIs may require the following user permissions:</p>
-<ul>
- <li>{@link android.Manifest.permission#ACCESS_LOWPAN_STATE}</li>
- <li>{@link android.Manifest.permission#CHANGE_LOWPAN_STATE}</li>
- <li>TBD</li>
-</ul>
-
-<p class="note"><strong>Note:</strong> Not all Android-powered devices provide LoWPAN functionality.
-If your application uses these APIs, declare so with a <a
-href="{@docRoot}guide/topics/manifest/uses-feature-element.html">{@code <uses-feature>}</a>
-element in the manifest file:</p>
-<pre>
-<manifest ...>
- <uses-feature android:name="android.hardware.lowpan" />
- ...
-</manifest>
-</pre>
-</BODY>
-</HTML>
diff --git a/lowpan/tests/Android.bp b/lowpan/tests/Android.bp
deleted file mode 100644
index 5908929..0000000
--- a/lowpan/tests/Android.bp
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Make test APK
-// ============================================================
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_test {
- name: "FrameworksLowpanApiTests",
- srcs: ["**/*.java"],
- // Filter all src files to just java files
- jacoco: {
- include_filter: ["android.net.lowpan.*"],
- exclude_filter: [
- "android.net.lowpan.LowpanInterfaceTest*",
- "android.net.lowpan.LowpanManagerTest*",
- ],
- },
- static_libs: [
- "androidx.test.rules",
- "guava",
- "mockito-target-minus-junit4",
- "frameworks-base-testutils",
- ],
- libs: [
- "android.test.runner",
- "android.test.base",
- ],
- platform_apis: true,
- test_suites: ["device-tests"],
- certificate: "platform",
-}
diff --git a/lowpan/tests/AndroidManifest.xml b/lowpan/tests/AndroidManifest.xml
deleted file mode 100644
index 8e68fc7..0000000
--- a/lowpan/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
- ~ Copyright (C) 2017 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.net.lowpan.test">
-
- <application>
- <uses-library android:name="android.test.runner" />
- <activity android:label="LowpanTestDummyLabel"
- android:name="LowpanTestDummyName"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
- </application>
-
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="android.net.lowpan.test"
- android:label="Frameworks LoWPAN API Tests">
- </instrumentation>
-
-</manifest>
diff --git a/lowpan/tests/AndroidTest.xml b/lowpan/tests/AndroidTest.xml
deleted file mode 100644
index 978cc02..0000000
--- a/lowpan/tests/AndroidTest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<configuration description="Runs Frameworks LoWPAN API Tests.">
- <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
- <option name="test-file-name" value="FrameworksLowpanApiTests.apk" />
- </target_preparer>
-
- <option name="test-suite-tag" value="apct" />
- <option name="test-tag" value="FrameworksLowpanApiTests" />
- <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
- <option name="package" value="android.net.lowpan.test" />
- <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
- </test>
-</configuration>
diff --git a/lowpan/tests/README.md b/lowpan/tests/README.md
deleted file mode 100644
index cb5772e..0000000
--- a/lowpan/tests/README.md
+++ /dev/null
@@ -1,50 +0,0 @@
-# LoWPAN Unit Tests
-This package contains unit tests for the android LoWPAN framework System APIs based on the
-[Android Testing Support Library](http://developer.android.com/tools/testing-support-library/index.html).
-The test cases are built using the [JUnit](http://junit.org/) and [Mockito](http://mockito.org/)
-libraries.
-
-## Running Tests
-The easiest way to run tests is simply run
-
-```
-frameworks/base/lowpan/tests/runtests.sh
-```
-
-`runtests.sh` will build the test project and all of its dependencies and push the APK to the
-connected device. It will then run the tests on the device.
-
-To pick up changes in framework/base, you will need to:
-1. rebuild the framework library 'make -j32'
-2. sync over the updated library to the device 'adb sync'
-3. restart framework on the device 'adb shell stop' then 'adb shell start'
-
-To enable syncing data to the device for first time after clean reflash:
-1. adb disable-verity
-2. adb reboot
-3. adb remount
-
-See below for a few example of options to limit which tests are run.
-See the
-[AndroidJUnitRunner Documentation](https://developer.android.com/reference/android/support/test/runner/AndroidJUnitRunner.html)
-for more details on the supported options.
-
-```
-runtests.sh -e package android.net.lowpan
-runtests.sh -e class android.net.lowpan.LowpanManagerTest
-```
-
-If you manually build and push the test APK to the device you can run tests using
-
-```
-adb shell am instrument -w 'android.net.wifi.test/androidx.test.runner.AndroidJUnitRunner'
-```
-
-## Adding Tests
-Tests can be added by adding classes to the src directory. JUnit4 style test cases can
-be written by simply annotating test methods with `org.junit.Test`.
-
-## Debugging Tests
-If you are trying to debug why tests are not doing what you expected, you can add android log
-statements and use logcat to view them. The beginning and end of every tests is automatically logged
-with the tag `TestRunner`.
diff --git a/lowpan/tests/runtests.sh b/lowpan/tests/runtests.sh
deleted file mode 100755
index 8267a79..0000000
--- a/lowpan/tests/runtests.sh
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/usr/bin/env bash
-
-if [ -z $ANDROID_BUILD_TOP ]; then
- echo "You need to source and lunch before you can use this script"
- exit 1
-fi
-
-echo "Running tests"
-
-set -e # fail early
-
-echo "+ mmma -j32 $ANDROID_BUILD_TOP/frameworks/base/lowpan/tests"
-# NOTE Don't actually run the command above since this shell doesn't inherit functions from the
-# caller.
-make -j32 -C $ANDROID_BUILD_TOP -f build/core/main.mk MODULES-IN-frameworks-base-lowpan-tests
-
-set -x # print commands
-
-adb root
-adb wait-for-device
-
-adb install -r -g "$OUT/data/app/FrameworksLowpanApiTests/FrameworksLowpanApiTests.apk"
-
-adb shell am instrument -w "$@" 'android.net.lowpan.test/androidx.test.runner.AndroidJUnitRunner'
diff --git a/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java b/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java
deleted file mode 100644
index 86f9d0e..0000000
--- a/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import static org.mockito.Mockito.*;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.test.TestLooper;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Map;
-
-/** Unit tests for android.net.lowpan.LowpanInterface. */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class LowpanInterfaceTest {
- private static final String TEST_PACKAGE_NAME = "TestPackage";
-
- @Mock Context mContext;
- @Mock ILowpanInterface mLowpanInterfaceService;
- @Mock IBinder mLowpanInterfaceBinder;
- @Mock ApplicationInfo mApplicationInfo;
- @Mock IBinder mAppBinder;
- @Mock LowpanInterface.Callback mLowpanInterfaceCallback;
-
- private Handler mHandler;
- private final TestLooper mTestLooper = new TestLooper();
- private ILowpanInterfaceListener mInterfaceListener;
- private LowpanInterface mLowpanInterface;
- private Map<String, Object> mPropertyMap;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
- when(mContext.getOpPackageName()).thenReturn(TEST_PACKAGE_NAME);
- when(mLowpanInterfaceService.getName()).thenReturn("wpan0");
- when(mLowpanInterfaceService.asBinder()).thenReturn(mLowpanInterfaceBinder);
-
- mLowpanInterface =
- new LowpanInterface(mContext, mLowpanInterfaceService, mTestLooper.getLooper());
- }
-
- @Test
- public void testStateChangedCallback() throws Exception {
- // Register our callback
- mLowpanInterface.registerCallback(mLowpanInterfaceCallback);
-
- // Verify a listener was added
- verify(mLowpanInterfaceService)
- .addListener(
- argThat(
- listener -> {
- mInterfaceListener = listener;
- return listener instanceof ILowpanInterfaceListener;
- }));
-
- // Change some properties
- mInterfaceListener.onStateChanged(LowpanInterface.STATE_OFFLINE);
- mTestLooper.dispatchAll();
-
- // Verify that the property was changed
- verify(mLowpanInterfaceCallback)
- .onStateChanged(
- argThat(stateString -> stateString.equals(LowpanInterface.STATE_OFFLINE)));
- }
-}
diff --git a/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java b/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java
deleted file mode 100644
index 998e8a5..0000000
--- a/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.*;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.test.TestLooper;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/** Unit tests for android.net.lowpan.LowpanManager. */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class LowpanManagerTest {
- private static final String TEST_PACKAGE_NAME = "TestPackage";
-
- @Mock Context mContext;
- @Mock ILowpanManager mLowpanService;
- @Mock ILowpanInterface mLowpanInterfaceService;
- @Mock IBinder mLowpanInterfaceBinder;
- @Mock ApplicationInfo mApplicationInfo;
- @Mock IBinder mAppBinder;
- @Mock LowpanManager.Callback mLowpanManagerCallback;
-
- private Handler mHandler;
- private final TestLooper mTestLooper = new TestLooper();
- private LowpanManager mLowpanManager;
-
- private ILowpanManagerListener mManagerListener;
- private LowpanInterface mLowpanInterface;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
- when(mContext.getOpPackageName()).thenReturn(TEST_PACKAGE_NAME);
-
- mLowpanManager = new LowpanManager(mContext, mLowpanService, mTestLooper.getLooper());
- }
-
- @Test
- public void testGetEmptyInterfaceList() throws Exception {
- when(mLowpanService.getInterfaceList()).thenReturn(new String[0]);
- assertTrue(mLowpanManager.getInterfaceList().length == 0);
- assertTrue(mLowpanManager.getInterface() == null);
- }
-
- @Test
- public void testGetInterfaceList() throws Exception {
- when(mLowpanService.getInterfaceList()).thenReturn(new String[] {"wpan0"});
- when(mLowpanService.getInterface("wpan0")).thenReturn(mLowpanInterfaceService);
- when(mLowpanInterfaceService.getName()).thenReturn("wpan0");
- when(mLowpanInterfaceService.asBinder()).thenReturn(mLowpanInterfaceBinder);
- assertEquals(mLowpanManager.getInterfaceList().length, 1);
-
- LowpanInterface iface = mLowpanManager.getInterface();
- assertNotNull(iface);
- assertEquals(iface.getName(), "wpan0");
- }
-
- @Test
- public void testRegisterCallback() throws Exception {
- when(mLowpanInterfaceService.getName()).thenReturn("wpan0");
- when(mLowpanInterfaceService.asBinder()).thenReturn(mLowpanInterfaceBinder);
-
- // Register our callback
- mLowpanManager.registerCallback(mLowpanManagerCallback);
-
- // Verify a listener was added
- verify(mLowpanService)
- .addListener(
- argThat(
- listener -> {
- mManagerListener = listener;
- return listener instanceof ILowpanManagerListener;
- }));
-
- // Add an interface
- mManagerListener.onInterfaceAdded(mLowpanInterfaceService);
- mTestLooper.dispatchAll();
-
- // Verify that the interface was added
- verify(mLowpanManagerCallback)
- .onInterfaceAdded(
- argThat(
- iface -> {
- mLowpanInterface = iface;
- return iface instanceof LowpanInterface;
- }));
- verifyNoMoreInteractions(mLowpanManagerCallback);
-
- // This check causes the test to fail with a weird error, but I'm not sure why.
- assertEquals(mLowpanInterface.getService(), mLowpanInterfaceService);
-
- // Verify that calling getInterface on the LowpanManager object will yield the same
- // LowpanInterface object.
- when(mLowpanService.getInterfaceList()).thenReturn(new String[] {"wpan0"});
- when(mLowpanService.getInterface("wpan0")).thenReturn(mLowpanInterfaceService);
- assertEquals(mLowpanManager.getInterface(), mLowpanInterface);
-
- // Remove the service
- mManagerListener.onInterfaceRemoved(mLowpanInterfaceService);
- mTestLooper.dispatchAll();
-
- // Verify that the interface was removed
- verify(mLowpanManagerCallback).onInterfaceRemoved(mLowpanInterface);
- }
-
- @Test
- public void testUnregisterCallback() throws Exception {
- when(mLowpanInterfaceService.getName()).thenReturn("wpan0");
- when(mLowpanInterfaceService.asBinder()).thenReturn(mLowpanInterfaceBinder);
-
- // Register our callback
- mLowpanManager.registerCallback(mLowpanManagerCallback);
-
- // Verify a listener was added
- verify(mLowpanService)
- .addListener(
- argThat(
- listener -> {
- mManagerListener = listener;
- return listener instanceof ILowpanManagerListener;
- }));
-
- // Add an interface
- mManagerListener.onInterfaceAdded(mLowpanInterfaceService);
- mTestLooper.dispatchAll();
-
- // Verify that the interface was added
- verify(mLowpanManagerCallback)
- .onInterfaceAdded(
- argThat(
- iface -> {
- mLowpanInterface = iface;
- return iface instanceof LowpanInterface;
- }));
- verifyNoMoreInteractions(mLowpanManagerCallback);
-
- // Unregister our callback
- mLowpanManager.unregisterCallback(mLowpanManagerCallback);
-
- // Verify the listener was removed
- verify(mLowpanService).removeListener(mManagerListener);
-
- // Verify that the callback wasn't invoked.
- verifyNoMoreInteractions(mLowpanManagerCallback);
- }
-}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 444366a..c666140 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -2294,10 +2294,10 @@
public static int[] DEFAULT_STREAM_VOLUME = new int[] {
4, // STREAM_VOICE_CALL
7, // STREAM_SYSTEM
- 5, // STREAM_RING
+ 5, // STREAM_RING // configured in AudioService by config_audio_notif_vol_default
5, // STREAM_MUSIC
6, // STREAM_ALARM
- 5, // STREAM_NOTIFICATION
+ 5, // STREAM_NOTIFICATION // configured in AudioService by config_audio_ring_vol_default
7, // STREAM_BLUETOOTH_SCO
7, // STREAM_SYSTEM_ENFORCED
5, // STREAM_DTMF
diff --git a/media/java/android/media/BluetoothProfileConnectionInfo.java b/media/java/android/media/BluetoothProfileConnectionInfo.java
index c148846..f3a65a1 100644
--- a/media/java/android/media/BluetoothProfileConnectionInfo.java
+++ b/media/java/android/media/BluetoothProfileConnectionInfo.java
@@ -126,6 +126,21 @@
}
/**
+ * @hide
+ * Factory method for <code>BluetoothProfileConnectionInfo</code> for an LE output device
+ * @param suppressNoisyIntent if true the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY}
+ * intent will not be sent.
+ * @param volume the volume index of the device, -1 if unknown or to be ignored
+ * @return an instance of BluetoothProfileConnectionInfo for the BLE output device that reflects
+ * the given parameters
+ */
+ public static @NonNull BluetoothProfileConnectionInfo createLeAudioOutputInfo(
+ boolean suppressNoisyIntent, int volume) {
+ return new BluetoothProfileConnectionInfo(BluetoothProfile.LE_AUDIO, suppressNoisyIntent,
+ volume, /*isLeOutput*/ true);
+ }
+
+ /**
* @return The profile connection
*/
public int getProfile() {
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java
new file mode 100644
index 0000000..ae162b5
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 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.mediaframeworktest.unit;
+
+import static org.junit.Assert.assertEquals;
+
+import android.bluetooth.BluetoothProfile;
+import android.media.BluetoothProfileConnectionInfo;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class BluetoothProfileConnectionInfoTest {
+
+ @Test
+ public void testCoverageLeAudioOutputVolume() {
+ final boolean supprNoisy = false;
+ final int volume = 1;
+ final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo
+ .createLeAudioOutputInfo(supprNoisy, volume);
+ assertEquals(info.getProfile(), BluetoothProfile.LE_AUDIO);
+ assertEquals(info.isSuppressNoisyIntent(), supprNoisy);
+ assertEquals(info.isLeOutput(), true);
+ assertEquals(info.getVolume(), volume);
+ }
+
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
index 98438cd..b6b8837 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
@@ -189,7 +189,8 @@
out.write(buffer, 0, bytesRead);
}
}
- } catch (IOException | SecurityException | IllegalStateException e) {
+ } catch (IOException | SecurityException | IllegalStateException
+ | IllegalArgumentException e) {
Log.w(LOG_TAG, "Error staging apk from content URI", e);
return false;
}
diff --git a/packages/SettingsLib/Spa/.idea/codeStyles/Project.xml b/packages/SettingsLib/Spa/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..318eeef
--- /dev/null
+++ b/packages/SettingsLib/Spa/.idea/codeStyles/Project.xml
@@ -0,0 +1,126 @@
+<component name="ProjectCodeStyleConfiguration">
+ <code_scheme name="Project" version="173">
+ <JetCodeStyleSettings>
+ <option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
+ <option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
+ </JetCodeStyleSettings>
+ <codeStyleSettings language="XML">
+ <option name="FORCE_REARRANGE_MODE" value="1" />
+ <indentOptions>
+ <option name="CONTINUATION_INDENT_SIZE" value="4" />
+ </indentOptions>
+ <arrangement>
+ <rules>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>xmlns:android</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>^$</XML_NAMESPACE>
+ </AND>
+ </match>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>xmlns:.*</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>^$</XML_NAMESPACE>
+ </AND>
+ </match>
+ <order>BY_NAME</order>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>.*:id</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+ </AND>
+ </match>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>.*:name</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+ </AND>
+ </match>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>name</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>^$</XML_NAMESPACE>
+ </AND>
+ </match>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>style</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>^$</XML_NAMESPACE>
+ </AND>
+ </match>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>.*</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>^$</XML_NAMESPACE>
+ </AND>
+ </match>
+ <order>BY_NAME</order>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>.*</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+ </AND>
+ </match>
+ <order>ANDROID_ATTRIBUTE_ORDER</order>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>.*</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>.*</XML_NAMESPACE>
+ </AND>
+ </match>
+ <order>BY_NAME</order>
+ </rule>
+ </section>
+ </rules>
+ </arrangement>
+ </codeStyleSettings>
+ <codeStyleSettings language="kotlin">
+ <indentOptions>
+ <option name="CONTINUATION_INDENT_SIZE" value="4" />
+ </indentOptions>
+ </codeStyleSettings>
+ </code_scheme>
+</component>
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/.idea/codeStyles/codeStyleConfig.xml b/packages/SettingsLib/Spa/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..0f7bc51
--- /dev/null
+++ b/packages/SettingsLib/Spa/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+<component name="ProjectCodeStyleConfiguration">
+ <state>
+ <option name="USE_PER_PROJECT_SETTINGS" value="true" />
+ </state>
+</component>
diff --git a/packages/SettingsLib/Spa/.idea/copyright/Apache_2.xml b/packages/SettingsLib/Spa/.idea/copyright/Apache_2.xml
new file mode 100644
index 0000000..d1866d0
--- /dev/null
+++ b/packages/SettingsLib/Spa/.idea/copyright/Apache_2.xml
@@ -0,0 +1,10 @@
+<component name="CopyrightManager">
+ <copyright>
+ <option name="notice"
+ value="Copyright (C) &#36;today.year 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."/>
+ <option name="keyword" value="Copyright"/>
+ <option name="allowReplaceKeyword" value=""/>
+ <option name="myName" value="Apache 2"/>
+ <option name="myLocal" value="true"/>
+ </copyright>
+</component>
diff --git a/packages/SettingsLib/Spa/.idea/copyright/profiles_settings.xml b/packages/SettingsLib/Spa/.idea/copyright/profiles_settings.xml
new file mode 100644
index 0000000..b011125
--- /dev/null
+++ b/packages/SettingsLib/Spa/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,8 @@
+<component name="CopyrightManager">
+ <settings default="Apache 2">
+ <LanguageOptions name="XML">
+ <option name="fileTypeOverride" value="3" />
+ <option name="prefixLines" value="false" />
+ </LanguageOptions>
+ </settings>
+</component>
diff --git a/packages/SettingsLib/Spa/.idea/vcs.xml b/packages/SettingsLib/Spa/.idea/vcs.xml
new file mode 100644
index 0000000..f3aa348
--- /dev/null
+++ b/packages/SettingsLib/Spa/.idea/vcs.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="CommitMessageInspectionProfile">
+ <profile version="1.0">
+ <inspection_tool class="BodyLimit" enabled="true" level="WARNING" enabled_by_default="true" />
+ <inspection_tool class="SubjectBodySeparation" enabled="true" level="WARNING" enabled_by_default="true" />
+ <inspection_tool class="SubjectLimit" enabled="true" level="WARNING" enabled_by_default="true">
+ <option name="RIGHT_MARGIN" value="50" />
+ </inspection_tool>
+ </profile>
+ </component>
+ <component name="VcsDirectoryMappings">
+ <mapping directory="$PROJECT_DIR$/../../.." vcs="Git" />
+ </component>
+</project>
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/codelab/Android.bp b/packages/SettingsLib/Spa/gallery/Android.bp
similarity index 96%
rename from packages/SettingsLib/Spa/codelab/Android.bp
rename to packages/SettingsLib/Spa/gallery/Android.bp
index 8fbbf9a..bc083c9 100644
--- a/packages/SettingsLib/Spa/codelab/Android.bp
+++ b/packages/SettingsLib/Spa/gallery/Android.bp
@@ -19,7 +19,7 @@
}
android_app {
- name: "SpaLibCodelab",
+ name: "SpaLibGallery",
srcs: ["src/**/*.kt"],
diff --git a/packages/SettingsLib/Spa/codelab/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
similarity index 84%
rename from packages/SettingsLib/Spa/codelab/AndroidManifest.xml
rename to packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index 36b9313..914a45b 100644
--- a/packages/SettingsLib/Spa/codelab/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -15,14 +15,13 @@
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.settingslib.spa.codelab">
+ package="com.android.settingslib.spa.gallery">
<application
android:label="@string/app_name"
- android:supportsRtl="true"
- android:theme="@style/Theme.SpaLib.DayNight">
+ android:supportsRtl="true">
<activity
- android:name="com.android.settingslib.spa.codelab.MainActivity"
+ android:name="com.android.settingslib.spa.gallery.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/packages/SettingsLib/Spa/codelab/build.gradle b/packages/SettingsLib/Spa/gallery/build.gradle
similarity index 93%
rename from packages/SettingsLib/Spa/codelab/build.gradle
rename to packages/SettingsLib/Spa/gallery/build.gradle
index 169ecf0..98e6745 100644
--- a/packages/SettingsLib/Spa/codelab/build.gradle
+++ b/packages/SettingsLib/Spa/gallery/build.gradle
@@ -20,11 +20,11 @@
}
android {
- namespace 'com.android.settingslib.spa.codelab'
+ namespace 'com.android.settingslib.spa.gallery'
compileSdk 33
defaultConfig {
- applicationId "com.android.settingslib.spa.codelab"
+ applicationId "com.android.settingslib.spa.gallery"
minSdk spa_min_sdk
targetSdk 33
versionCode 1
diff --git a/packages/SettingsLib/Spa/codelab/res/values/strings.xml b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
similarity index 84%
rename from packages/SettingsLib/Spa/codelab/res/values/strings.xml
rename to packages/SettingsLib/Spa/gallery/res/values/strings.xml
index 6fdbba0..510e6c2 100644
--- a/packages/SettingsLib/Spa/codelab/res/values/strings.xml
+++ b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
@@ -14,6 +14,6 @@
limitations under the License.
-->
<resources>
- <!-- Codelab App name. [DO NOT TRANSLATE] -->
- <string name="app_name" translatable="false">SpaLib Codelab</string>
+ <!-- Gallery App name. [DO NOT TRANSLATE] -->
+ <string name="app_name" translatable="false">SpaLib Gallery</string>
</resources>
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/MainActivity.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/MainActivity.kt
similarity index 80%
rename from packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/MainActivity.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/MainActivity.kt
index eef81e0..7db53b4 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/MainActivity.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/MainActivity.kt
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.codelab
+package com.android.settingslib.spa.gallery
-import com.android.settingslib.spa.codelab.page.codelabPageRepository
import com.android.settingslib.spa.framework.SpaActivity
+import com.android.settingslib.spa.gallery.page.galleryPageRepository
-class MainActivity : SpaActivity(codelabPageRepository)
+class MainActivity : SpaActivity(galleryPageRepository)
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
similarity index 98%
rename from packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/ArgumentPage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
index 5c6b609..937e594 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/ArgumentPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.codelab.page
+package com.android.settingslib.spa.gallery.page
import android.os.Bundle
import androidx.compose.foundation.layout.Column
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/FooterPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
similarity index 97%
rename from packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/FooterPage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
index 9fcbff8..143c365 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/FooterPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.codelab.page
+package com.android.settingslib.spa.gallery.page
import android.os.Bundle
import androidx.compose.foundation.layout.Column
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/HomePage.kt
similarity index 95%
rename from packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/HomePage.kt
index 57a69c4..0d17cd2 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/HomePage.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.codelab.page
+package com.android.settingslib.spa.gallery.page
import android.os.Bundle
import androidx.compose.foundation.layout.Column
@@ -25,10 +25,10 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.codelab.R
import com.android.settingslib.spa.framework.api.SettingsPageProvider
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.gallery.R
object HomePageProvider : SettingsPageProvider {
override val name = Destinations.Home
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PageRepository.kt
similarity index 91%
rename from packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PageRepository.kt
index 54c588a..8f38d82 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PageRepository.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.codelab.page
+package com.android.settingslib.spa.gallery.page
import com.android.settingslib.spa.framework.api.SettingsPageRepository
@@ -26,7 +26,7 @@
const val Slider = "Slider"
}
-val codelabPageRepository = SettingsPageRepository(
+val galleryPageRepository = SettingsPageRepository(
allPages = listOf(
HomePageProvider,
PreferencePageProvider,
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt
similarity index 82%
rename from packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PreferencePage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt
index d53562d..8a29d35 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt
@@ -14,19 +14,22 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.codelab.page
+package com.android.settingslib.spa.gallery.page
import android.os.Bundle
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.DisabledByDefault
+import androidx.compose.material.icons.outlined.TouchApp
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
-import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
@@ -36,6 +39,7 @@
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.ui.SettingsIcon
import kotlinx.coroutines.delay
object PreferencePageProvider : SettingsPageProvider {
@@ -75,14 +79,17 @@
}
})
- var count by remember { mutableStateOf(0) }
+ var count by rememberSaveable { mutableStateOf(0) }
Preference(object : PreferenceModel {
override val title = "Click me"
override val summary = derivedStateOf { count.toString() }
override val onClick: (() -> Unit) = { count++ }
+ override val icon = @Composable {
+ SettingsIcon(imageVector = Icons.Outlined.TouchApp)
+ }
})
- var ticks by remember { mutableStateOf(0) }
+ var ticks by rememberSaveable { mutableStateOf(0) }
LaunchedEffect(ticks) {
delay(1000L)
ticks++
@@ -96,6 +103,9 @@
override val title = "Disabled"
override val summary = "Disabled".toState()
override val enabled = false.toState()
+ override val icon = @Composable {
+ SettingsIcon(imageVector = Icons.Outlined.DisabledByDefault)
+ }
})
}
}
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SliderPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
similarity index 98%
rename from packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SliderPage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
index 6e96581..9bcac1b 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SliderPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.codelab.page
+package com.android.settingslib.spa.gallery.page
import android.os.Bundle
import androidx.compose.foundation.layout.Column
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SwitchPreferencePage.kt
similarity index 98%
rename from packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SwitchPreferencePage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SwitchPreferencePage.kt
index b566afa..b6f7258 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SwitchPreferencePage.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.codelab.page
+package com.android.settingslib.spa.gallery.page
import android.os.Bundle
import androidx.compose.foundation.layout.Column
diff --git a/packages/SettingsLib/Spa/settings.gradle b/packages/SettingsLib/Spa/settings.gradle
index 1b69c1e..897fa67 100644
--- a/packages/SettingsLib/Spa/settings.gradle
+++ b/packages/SettingsLib/Spa/settings.gradle
@@ -30,5 +30,5 @@
}
rootProject.name = "SpaLib"
include ':spa'
-include ':codelab'
+include ':gallery'
include ':tests'
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaActivity.kt
index 5b39b6e..51d3714 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaActivity.kt
@@ -24,6 +24,7 @@
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
+import com.android.settingslib.spa.R
import com.android.settingslib.spa.framework.api.SettingsPageProvider
import com.android.settingslib.spa.framework.api.SettingsPageRepository
import com.android.settingslib.spa.framework.compose.localNavController
@@ -33,6 +34,7 @@
private val settingsPageRepository: SettingsPageRepository,
) : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
+ setTheme(R.style.Theme_SpaLib_DayNight)
super.onCreate(savedInstanceState)
setContent {
MainContent()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
index 1a80ed2..0e6f53a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
@@ -19,6 +19,7 @@
import androidx.compose.foundation.clickable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import com.android.settingslib.spa.framework.compose.stateOf
@@ -38,6 +39,14 @@
get() = stateOf("")
/**
+ * The icon of this [Preference].
+ *
+ * Default is `null` which means no icon.
+ */
+ val icon: (@Composable () -> Unit)?
+ get() = null
+
+ /**
* Indicates whether this [Preference] is enabled.
*
* Disabled [Preference] will be displayed in disabled style.
@@ -61,13 +70,16 @@
*/
@Composable
fun Preference(model: PreferenceModel) {
- val modifier = model.onClick?.let { onClick ->
- Modifier.clickable(enabled = model.enabled.value) { onClick() }
- } ?: Modifier
+ val modifier = remember(model.enabled.value, model.onClick) {
+ model.onClick?.let { onClick ->
+ Modifier.clickable(enabled = model.enabled.value, onClick = onClick)
+ } ?: Modifier
+ }
BasePreference(
title = model.title,
summary = model.summary,
modifier = modifier,
+ icon = model.icon,
enabled = model.enabled,
)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
index 0dab0df..b6d6936 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
@@ -100,7 +100,7 @@
) {
val checkedValue = checked.value
val indication = LocalIndication.current
- val modifier = remember(checkedValue) {
+ val modifier = remember(checkedValue, changeable.value) {
if (checkedValue != null && onCheckedChange != null) {
Modifier.toggleable(
value = checkedValue,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt
index 41fd03b..296cf3b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt
@@ -20,8 +20,11 @@
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Info
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
@@ -32,7 +35,12 @@
fun Footer(footerText: String) {
if (footerText.isEmpty()) return
Column(Modifier.padding(SettingsDimension.itemPadding)) {
- SettingsIcon(imageVector = Icons.Outlined.Info, contentDescription = null)
+ Icon(
+ imageVector = Icons.Outlined.Info,
+ contentDescription = null,
+ modifier = Modifier.size(SettingsDimension.itemIconSize),
+ tint = MaterialTheme.colorScheme.onSurfaceVariant,
+ )
Spacer(modifier = Modifier.height(SettingsDimension.itemPaddingVertical))
SettingsBody(footerText)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Icon.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Icon.kt
index cb08cdb..4f28e37 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Icon.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Icon.kt
@@ -25,13 +25,10 @@
import com.android.settingslib.spa.framework.theme.SettingsDimension
@Composable
-fun SettingsIcon(
- imageVector: ImageVector,
- contentDescription: String?,
-) {
+fun SettingsIcon(imageVector: ImageVector) {
Icon(
imageVector = imageVector,
- contentDescription = contentDescription,
+ contentDescription = null,
modifier = Modifier.size(SettingsDimension.itemIconSize),
tint = MaterialTheme.colorScheme.onSurface,
)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppOpsController.kt
new file mode 100644
index 0000000..3808f64
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppOpsController.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spaprivileged.framework.app
+
+import android.app.AppOpsManager
+import android.app.AppOpsManager.MODE_ALLOWED
+import android.app.AppOpsManager.MODE_ERRORED
+import android.app.AppOpsManager.Mode
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+
+class AppOpsController(
+ context: Context,
+ private val app: ApplicationInfo,
+ private val op: Int,
+) {
+ private val appOpsManager = checkNotNull(context.getSystemService(AppOpsManager::class.java))
+
+ val isAllowed: LiveData<Boolean>
+ get() = _isAllowed
+
+ fun setAllowed(allowed: Boolean) {
+ val mode = if (allowed) MODE_ALLOWED else MODE_ERRORED
+ appOpsManager.setMode(op, app.uid, app.packageName, mode)
+ _isAllowed.postValue(allowed)
+ }
+
+ @Mode
+ private fun getMode(): Int = appOpsManager.checkOpNoThrow(op, app.uid, app.packageName)
+
+ private val _isAllowed = object : MutableLiveData<Boolean>() {
+ override fun onActive() {
+ postValue(getMode() == MODE_ALLOWED)
+ }
+
+ override fun onInactive() {
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
similarity index 79%
copy from packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
copy to packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
index 7c9df10..8dde897 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.common.data.model
+package com.android.settingslib.spaprivileged.framework.app
-/** Models a two-dimensional position */
-data class Position(
- val x: Int,
- val y: Int,
-)
+import android.content.pm.ApplicationInfo
+
+interface AppRecord {
+ val app: ApplicationInfo
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/Apps.kt
similarity index 70%
copy from packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
copy to packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/Apps.kt
index 7c9df10..f675545 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/Apps.kt
@@ -14,10 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.common.data.model
+package com.android.settingslib.spaprivileged.framework.app
-/** Models a two-dimensional position */
-data class Position(
- val x: Int,
- val y: Int,
-)
+import android.content.pm.ApplicationInfo
+import android.os.UserHandle
+
+val ApplicationInfo.userId: Int
+ get() = UserHandle.getUserId(uid)
+
+fun ApplicationInfo.toRoute() = "$packageName/$userId"
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/PackageManagers.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/PackageManagers.kt
index 5a3e666..66b05da 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/PackageManagers.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/PackageManagers.kt
@@ -16,10 +16,14 @@
package com.android.settingslib.spaprivileged.framework.app
+import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
object PackageManagers {
fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo =
PackageManager.getPackageInfoAsUserCached(packageName, 0, userId)
+
+ fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo =
+ PackageManager.getApplicationInfoAsUserCached(packageName, 0, userId)
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
new file mode 100644
index 0000000..06d7547
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spaprivileged.template.app
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.widget.ui.Footer
+
+@Composable
+fun AppInfoPage(
+ title: String,
+ packageName: String,
+ userId: Int,
+ footerText: String,
+ content: @Composable () -> Unit,
+) {
+ // TODO: Replace with SettingsScaffold
+ Column(Modifier.verticalScroll(rememberScrollState())) {
+ Text(
+ text = title,
+ modifier = Modifier.padding(SettingsDimension.itemPadding),
+ color = MaterialTheme.colorScheme.onSurface,
+ style = MaterialTheme.typography.headlineMedium,
+ )
+
+ AppInfo(packageName, userId)
+
+ content()
+
+ Footer(footerText)
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
new file mode 100644
index 0000000..57e9e9a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spaprivileged.template.app
+
+import android.content.Context
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.rememberContext
+import com.android.settingslib.spa.widget.preference.SwitchPreference
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spaprivileged.framework.app.AppRecord
+import com.android.settingslib.spaprivileged.framework.app.PackageManagers
+import kotlinx.coroutines.Dispatchers
+
+private const val PERMISSION = "permission"
+private const val PACKAGE_NAME = "packageName"
+private const val USER_ID = "userId"
+
+open class TogglePermissionAppInfoPageProvider(
+ private val factory: TogglePermissionAppListModelFactory,
+) : SettingsPageProvider {
+ override val name = "TogglePermissionAppInfoPage"
+
+ override val arguments = listOf(
+ navArgument(PERMISSION) { type = NavType.StringType },
+ navArgument(PACKAGE_NAME) { type = NavType.StringType },
+ navArgument(USER_ID) { type = NavType.IntType },
+ )
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ checkNotNull(arguments)
+ val permission = checkNotNull(arguments.getString(PERMISSION))
+ val packageName = checkNotNull(arguments.getString(PACKAGE_NAME))
+ val userId = arguments.getInt(USER_ID)
+ val listModel = rememberContext { context -> factory.createModel(permission, context) }
+ TogglePermissionAppInfoPage(listModel, packageName, userId)
+ }
+
+ fun getRoute(permissionType: String, packageName: String, userId: Int): String =
+ "$name/$permissionType/$packageName/$userId"
+}
+
+@Composable
+private fun TogglePermissionAppInfoPage(
+ listModel: TogglePermissionAppListModel<out AppRecord>,
+ packageName: String,
+ userId: Int,
+) {
+ AppInfoPage(
+ title = stringResource(listModel.pageTitleResId),
+ packageName = packageName,
+ userId = userId,
+ footerText = stringResource(listModel.footerResId),
+ ) {
+ val model = createSwitchModel(listModel, packageName, userId)
+ LaunchedEffect(model, Dispatchers.Default) {
+ model.initState()
+ }
+ SwitchPreference(model)
+ }
+}
+
+@Composable
+private fun <T : AppRecord> createSwitchModel(
+ listModel: TogglePermissionAppListModel<T>,
+ packageName: String,
+ userId: Int,
+): TogglePermissionSwitchModel<T> {
+ val record = remember {
+ val app = PackageManagers.getApplicationInfoAsUser(packageName, userId)
+ listModel.transformItem(app)
+ }
+ val context = LocalContext.current
+ val isAllowed = listModel.isAllowed(record)
+ return remember {
+ TogglePermissionSwitchModel(context, listModel, record, isAllowed)
+ }
+}
+
+private class TogglePermissionSwitchModel<T : AppRecord>(
+ context: Context,
+ private val listModel: TogglePermissionAppListModel<T>,
+ private val record: T,
+ isAllowed: State<Boolean?>,
+) : SwitchPreferenceModel {
+ override val title: String = context.getString(listModel.switchTitleResId)
+ override val checked = isAllowed
+ override val changeable = mutableStateOf(true)
+
+ fun initState() {
+ changeable.value = listModel.isChangeable(record)
+ }
+
+ override val onCheckedChange: (Boolean) -> Unit = { newChecked ->
+ listModel.setAllowed(record, newChecked)
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListModel.kt
new file mode 100644
index 0000000..88ad9da
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListModel.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spaprivileged.template.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import com.android.settingslib.spaprivileged.framework.app.AppRecord
+
+interface TogglePermissionAppListModel<T : AppRecord> {
+ val pageTitleResId: Int
+ val switchTitleResId: Int
+ val footerResId: Int
+
+ fun transformItem(app: ApplicationInfo): T
+
+ @Composable
+ fun isAllowed(record: T): State<Boolean?>
+
+ fun isChangeable(record: T): Boolean
+ fun setAllowed(record: T, newAllowed: Boolean)
+}
+
+interface TogglePermissionAppListModelFactory {
+ fun createModel(
+ permission: String,
+ context: Context,
+ ): TogglePermissionAppListModel<out AppRecord>
+}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 11cb9c1..65696253 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -488,15 +488,15 @@
<!-- Default speech rate choices -->
<string-array name="tts_rate_entries">
- <item>Very slow</item>
- <item>Slow</item>
- <item>Normal</item>
- <item>Fast</item>
- <item>Faster</item>
- <item>Very fast</item>
- <item>Rapid</item>
- <item>Very rapid</item>
- <item>Fastest</item>
+ <item>60%</item>
+ <item>80%</item>
+ <item>100%</item>
+ <item>150%</item>
+ <item>200%</item>
+ <item>250%</item>
+ <item>300%</item>
+ <item>350%</item>
+ <item>400%</item>
</string-array>
<!-- Do not translate. -->
<string-array name="tts_rate_values">
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 7fbd100..cd3242a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -297,6 +297,9 @@
mCachedDevices.remove(i);
}
}
+
+ // To clear the SetMemberPair flag when the Bluetooth is turning off.
+ mOngoingSetMemberPair = null;
}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index cc7d23e..dc2c6356 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -165,6 +165,8 @@
* @param includeFadeIn true if the animator should also fade in the view and child views.
* @param fadeInInterpolator the interpolator to use when fading in the view. Unused if
* [includeFadeIn] is false.
+ * @param onAnimationEnd an optional runnable that will be run once the animation
+ * finishes successfully. Will not be run if the animation is cancelled.
*/
@JvmOverloads
fun animateAddition(
@@ -174,7 +176,8 @@
duration: Long = DEFAULT_DURATION,
includeMargins: Boolean = false,
includeFadeIn: Boolean = false,
- fadeInInterpolator: Interpolator = DEFAULT_FADE_IN_INTERPOLATOR
+ fadeInInterpolator: Interpolator = DEFAULT_FADE_IN_INTERPOLATOR,
+ onAnimationEnd: Runnable? = null,
): Boolean {
if (
occupiesSpace(
@@ -193,7 +196,8 @@
origin,
interpolator,
duration,
- ignorePreviousValues = !includeMargins
+ ignorePreviousValues = !includeMargins,
+ onAnimationEnd,
)
addListener(rootView, listener, recursive = true)
@@ -246,14 +250,16 @@
origin: Hotspot,
interpolator: Interpolator,
duration: Long,
- ignorePreviousValues: Boolean
+ ignorePreviousValues: Boolean,
+ onAnimationEnd: Runnable? = null,
): View.OnLayoutChangeListener {
return createListener(
interpolator,
duration,
ephemeral = true,
origin = origin,
- ignorePreviousValues = ignorePreviousValues
+ ignorePreviousValues = ignorePreviousValues,
+ onAnimationEnd,
)
}
@@ -272,7 +278,8 @@
duration: Long,
ephemeral: Boolean,
origin: Hotspot? = null,
- ignorePreviousValues: Boolean = false
+ ignorePreviousValues: Boolean = false,
+ onAnimationEnd: Runnable? = null,
): View.OnLayoutChangeListener {
return object : View.OnLayoutChangeListener {
override fun onLayoutChange(
@@ -340,7 +347,8 @@
endValues,
interpolator,
duration,
- ephemeral
+ ephemeral,
+ onAnimationEnd,
)
}
}
@@ -903,7 +911,8 @@
endValues: Map<Bound, Int>,
interpolator: Interpolator,
duration: Long,
- ephemeral: Boolean
+ ephemeral: Boolean,
+ onAnimationEnd: Runnable? = null,
) {
val propertyValuesHolders =
buildList {
@@ -941,6 +950,9 @@
// listener.
recursivelyRemoveListener(view)
}
+ if (!cancelled) {
+ onAnimationEnd?.run()
+ }
}
override fun onAnimationCancel(animation: Animator?) {
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/GetMainLooperViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/GetMainLooperViaContextDetector.kt
new file mode 100644
index 0000000..a629eee
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/GetMainLooperViaContextDetector.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 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.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+
+@Suppress("UnstableApiUsage")
+class GetMainLooperViaContextDetector : Detector(), SourceCodeScanner {
+
+ override fun getApplicableMethodNames(): List<String> {
+ return listOf("getMainThreadHandler", "getMainLooper", "getMainExecutor")
+ }
+
+ override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+ if (context.evaluator.isMemberInSubClassOf(method, "android.content.Context")) {
+ context.report(
+ ISSUE,
+ method,
+ context.getNameLocation(node),
+ "Please inject a @Main Executor instead."
+ )
+ }
+ }
+
+ companion object {
+ @JvmField
+ val ISSUE: Issue =
+ Issue.create(
+ id = "GetMainLooperViaContextDetector",
+ briefDescription = "Please use idiomatic SystemUI executors, injecting " +
+ "them via Dagger.",
+ explanation = "Injecting the @Main Executor is preferred in order to make" +
+ "dependencies explicit and increase testability. It's much " +
+ "easier to pass a FakeExecutor on your test ctor than to " +
+ "deal with loopers in unit tests.",
+ category = Category.LINT,
+ priority = 8,
+ severity = Severity.WARNING,
+ implementation = Implementation(GetMainLooperViaContextDetector::class.java,
+ Scope.JAVA_FILE_SCOPE)
+ )
+ }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
new file mode 100644
index 0000000..b72d03d
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 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.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+
+class RegisterReceiverViaContextDetector : Detector(), SourceCodeScanner {
+
+ override fun getApplicableMethodNames(): List<String> {
+ return listOf("registerReceiver", "registerReceiverAsUser", "registerReceiverForAllUsers")
+ }
+
+ override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+ if (context.evaluator.isMemberInSubClassOf(method, "android.content.Context")) {
+ context.report(
+ ISSUE,
+ method,
+ context.getNameLocation(node),
+ "BroadcastReceivers should be registered via BroadcastDispatcher."
+ )
+ }
+ }
+
+ companion object {
+ @JvmField
+ val ISSUE: Issue =
+ Issue.create(
+ id = "RegisterReceiverViaContextDetector",
+ briefDescription = "Broadcast registrations via Context are blocking " +
+ "calls. Please use BroadcastDispatcher.",
+ explanation =
+ "Context#registerReceiver is a blocking call to the system server, " +
+ "making it very likely that you'll drop a frame. Please use " +
+ "BroadcastDispatcher instead (or move this call to a " +
+ "@Background Executor.)",
+ category = Category.PERFORMANCE,
+ priority = 8,
+ severity = Severity.WARNING,
+ implementation = Implementation(RegisterReceiverViaContextDetector::class.java,
+ Scope.JAVA_FILE_SCOPE)
+ )
+ }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
new file mode 100644
index 0000000..a584894
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 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.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiField
+import org.jetbrains.uast.UReferenceExpression
+
+@Suppress("UnstableApiUsage")
+class SoftwareBitmapDetector : Detector(), SourceCodeScanner {
+
+ override fun getApplicableReferenceNames(): List<String> {
+ return mutableListOf("ALPHA_8", "RGB_565", "ARGB_8888", "RGBA_F16", "RGBA_1010102")
+ }
+
+ override fun visitReference(
+ context: JavaContext,
+ reference: UReferenceExpression,
+ referenced: PsiElement
+ ) {
+
+ val evaluator = context.evaluator
+ if (evaluator.isMemberInClass(referenced as? PsiField, "android.graphics.Bitmap.Config")) {
+ context.report(
+ ISSUE,
+ referenced,
+ context.getNameLocation(referenced),
+ "Usage of Config.HARDWARE is highly encouraged."
+ )
+ }
+ }
+
+ companion object {
+ @JvmField
+ val ISSUE: Issue =
+ Issue.create(
+ id = "SoftwareBitmapDetector",
+ briefDescription = "Software bitmap detected. Please use Config.HARDWARE instead.",
+ explanation =
+ "Software bitmaps occupy twice as much memory, when compared to Config.HARDWARE. " +
+ "In case you need to manipulate the pixels, please consider to either use" +
+ "a shader (encouraged), or a short lived software bitmap.",
+ category = Category.PERFORMANCE,
+ priority = 8,
+ severity = Severity.WARNING,
+ implementation = Implementation(SoftwareBitmapDetector::class.java,
+ Scope.JAVA_FILE_SCOPE)
+ )
+ }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index 226aebbd..c7c73d3 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -27,8 +27,13 @@
class SystemUIIssueRegistry : IssueRegistry() {
override val issues: List<Issue>
- get() = listOf(BindServiceViaContextDetector.ISSUE,
- BroadcastSentViaContextDetector.ISSUE)
+ get() = listOf(
+ BindServiceViaContextDetector.ISSUE,
+ BroadcastSentViaContextDetector.ISSUE,
+ GetMainLooperViaContextDetector.ISSUE,
+ RegisterReceiverViaContextDetector.ISSUE,
+ SoftwareBitmapDetector.ISSUE,
+ )
override val api: Int
get() = CURRENT_API
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt
new file mode 100644
index 0000000..ec761cd
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2022 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.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class GetMainLooperViaContextDetectorTest : LintDetectorTest() {
+
+ override fun getDetector(): Detector = GetMainLooperViaContextDetector()
+ override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+ override fun getIssues(): List<Issue> = listOf(GetMainLooperViaContextDetector.ISSUE)
+
+ private val explanation = "Please inject a @Main Executor instead."
+
+ @Test
+ fun testGetMainThreadHandler() {
+ lint().files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import android.os.Handler;
+
+ public class TestClass1 {
+ public void test(Context context) {
+ Handler mainThreadHandler = context.getMainThreadHandler();
+ }
+ }
+ """
+ ).indented(),
+ *stubs)
+ .issues(GetMainLooperViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
+ }
+
+ @Test
+ fun testGetMainLooper() {
+ lint().files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import android.os.Looper;
+
+ public class TestClass1 {
+ public void test(Context context) {
+ Looper mainLooper = context.getMainLooper();
+ }
+ }
+ """
+ ).indented(),
+ *stubs)
+ .issues(GetMainLooperViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
+ }
+
+ @Test
+ fun testGetMainExecutor() {
+ lint().files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import java.util.concurrent.Executor;
+
+ public class TestClass1 {
+ public void test(Context context) {
+ Executor mainExecutor = context.getMainExecutor();
+ }
+ }
+ """
+ ).indented(),
+ *stubs)
+ .issues(GetMainLooperViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
+ }
+
+ private val contextStub: TestFile = java(
+ """
+ package android.content;
+ import android.os.Handler;import android.os.Looper;import java.util.concurrent.Executor;
+
+ public class Context {
+ public Looper getMainLooper() { return null; };
+ public Executor getMainExecutor() { return null; };
+ public Handler getMainThreadHandler() { return null; };
+ }
+ """
+ )
+
+ private val looperStub: TestFile = java(
+ """
+ package android.os;
+
+ public class Looper {}
+ """
+ )
+
+ private val handlerStub: TestFile = java(
+ """
+ package android.os;
+
+ public class Handler {}
+ """
+ )
+
+ private val stubs = arrayOf(contextStub, looperStub, handlerStub)
+}
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
new file mode 100644
index 0000000..76c0519
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2022 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.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class RegisterReceiverViaContextDetectorTest : LintDetectorTest() {
+
+ override fun getDetector(): Detector = RegisterReceiverViaContextDetector()
+ override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+ override fun getIssues(): List<Issue> = listOf(
+ RegisterReceiverViaContextDetector.ISSUE)
+
+ private val explanation = "BroadcastReceivers should be registered via BroadcastDispatcher."
+
+ @Test
+ fun testRegisterReceiver() {
+ lint().files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.BroadcastReceiver;
+ import android.content.Context;
+ import android.content.IntentFilter;
+
+ public class TestClass1 {
+ public void bind(Context context, BroadcastReceiver receiver,
+ IntentFilter filter) {
+ context.registerReceiver(receiver, filter, 0);
+ }
+ }
+ """
+ ).indented(),
+ *stubs)
+ .issues(RegisterReceiverViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
+ }
+
+ @Test
+ fun testRegisterReceiverAsUser() {
+ lint().files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.BroadcastReceiver;
+ import android.content.Context;
+ import android.content.IntentFilter;
+ import android.os.Handler;
+ import android.os.UserHandle;
+
+ public class TestClass1 {
+ public void bind(Context context, BroadcastReceiver receiver,
+ IntentFilter filter, Handler handler) {
+ context.registerReceiverAsUser(receiver, UserHandle.ALL, filter,
+ "permission", handler);
+ }
+ }
+ """
+ ).indented(),
+ *stubs)
+ .issues(RegisterReceiverViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
+ }
+
+ @Test
+ fun testRegisterReceiverForAllUsers() {
+ lint().files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.BroadcastReceiver;
+ import android.content.Context;
+ import android.content.IntentFilter;
+ import android.os.Handler;
+ import android.os.UserHandle;
+
+ public class TestClass1 {
+ public void bind(Context context, BroadcastReceiver receiver,
+ IntentFilter filter, Handler handler) {
+ context.registerReceiverForAllUsers(receiver, filter, "permission",
+ handler);
+ }
+ }
+ """
+ ).indented(),
+ *stubs)
+ .issues(RegisterReceiverViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
+ }
+
+ private val contextStub: TestFile = java(
+ """
+ package android.content;
+ import android.os.Handler;
+ import android.os.UserHandle;
+
+ public class Context {
+ public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+ int flags) {};
+ public void registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
+ IntentFilter filter, String broadcastPermission, Handler scheduler) {};
+ public void registerReceiverForAllUsers(BroadcastReceiver receiver, IntentFilter filter,
+ String broadcastPermission, Handler scheduler) {};
+ }
+ """
+ )
+
+ private val broadcastReceiverStub: TestFile = java(
+ """
+ package android.content;
+
+ public class BroadcastReceiver {}
+ """
+ )
+
+ private val intentFilterStub: TestFile = java(
+ """
+ package android.content;
+
+ public class IntentFilter {}
+ """
+ )
+
+ private val handlerStub: TestFile = java(
+ """
+ package android.os;
+
+ public class Handler {}
+ """
+ )
+
+ private val userHandleStub: TestFile = java(
+ """
+ package android.os;
+
+ public enum UserHandle {
+ ALL
+ }
+ """
+ )
+
+ private val stubs = arrayOf(contextStub, broadcastReceiverStub, intentFilterStub, handlerStub,
+ userHandleStub)
+}
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt
new file mode 100644
index 0000000..890f2b8
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 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.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+@Suppress("UnstableApiUsage")
+class SoftwareBitmapDetectorTest : LintDetectorTest() {
+
+ override fun getDetector(): Detector = SoftwareBitmapDetector()
+ override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+ override fun getIssues(): List<Issue> = listOf(SoftwareBitmapDetector.ISSUE)
+
+ private val explanation = "Usage of Config.HARDWARE is highly encouraged."
+
+ @Test
+ fun testSoftwareBitmap() {
+ lint().files(
+ TestFiles.java(
+ """
+ import android.graphics.Bitmap;
+
+ public class TestClass1 {
+ public void test() {
+ Bitmap.createBitmap(300, 300, Bitmap.Config.RGB_565);
+ Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
+ }
+ }
+ """
+ ).indented(),
+ *stubs)
+ .issues(SoftwareBitmapDetector.ISSUE)
+ .run()
+ .expectWarningCount(2)
+ .expectContains(explanation)
+ }
+
+ @Test
+ fun testHardwareBitmap() {
+ lint().files(
+ TestFiles.java(
+ """
+ import android.graphics.Bitmap;
+
+ public class TestClass1 {
+ public void test() {
+ Bitmap.createBitmap(300, 300, Bitmap.Config.HARDWARE);
+ }
+ }
+ """
+ ).indented(),
+ *stubs)
+ .issues(SoftwareBitmapDetector.ISSUE)
+ .run()
+ .expectWarningCount(0)
+ }
+
+ private val bitmapStub: TestFile = java(
+ """
+ package android.graphics;
+
+ public class Bitmap {
+ public enum Config {
+ ARGB_8888,
+ RGB_565,
+ HARDWARE
+ }
+ public static Bitmap createBitmap(int width, int height, Config config) {
+ return null;
+ }
+ }
+ """
+ )
+
+ private val stubs = arrayOf(bitmapStub)
+}
diff --git a/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_bg.xml b/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_bg.xml
new file mode 100644
index 0000000..4da47af
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_bg.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2022 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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape android:shape="rectangle">
+ <solid android:color="@color/accessibility_magnifier_bg" />
+ <corners android:radius="24dp" />
+ <stroke
+ android:color="@color/accessibility_magnifier_bg_stroke"
+ android:width="1dp" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_btn_bg.xml b/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_btn_bg.xml
new file mode 100644
index 0000000..5c9dd56
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_btn_bg.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+android:color="?android:attr/colorControlHighlight">
+<item android:id="@android:id/mask">
+ <shape android:shape="oval">
+ <solid android:color="@color/accessibility_magnifier_bg" />
+ <size
+ android:width="56dp"
+ android:height="56dp"/>
+ <corners android:radius="2dp"/>
+ </shape>
+</item>
+</ripple>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/accessibility_magnifier_btn_bg.xml b/packages/SystemUI/res/drawable/accessibility_magnifier_btn_bg.xml
new file mode 100644
index 0000000..f633b3e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_magnifier_btn_bg.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+ <solid android:color="@color/accessibility_magnifier_bg" />
+ <size
+ android:width="56dp"
+ android:height="56dp"/>
+ <corners android:radius="2dp"/>
+ <stroke
+ android:color="@color/accessibility_magnifier_bg_stroke"
+ android:width="1dp" />
+ </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
deleted file mode 100644
index 5084ca4..0000000
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
+++ /dev/null
@@ -1 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_3_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_3_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25" android:pivotX="40.25" android:pivotY="40.25" android:scaleX="0.975" android:scaleY="0.975"><path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#f2b8b5" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="9.950000000000003" android:translateY="10" android:pivotX="30" android:pivotY="30" android:scaleX="1.2" android:scaleY="1.2"><group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30" android:translateY="38.75" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/></group><group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30" android:translateY="25" android:pivotX="0.002" android:pivotY="7.488" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_1_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/></group></group><group android:name="_R_G_L_0_G" android:translateX="20.659" android:translateY="15.75"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/><path android:name="_R_G_L_0_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/><path android:name="_R_G_L_0_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_3_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="167" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="167" android:startOffset="0" android:valueFrom="2.5" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="83" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="0.975" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="167" android:valueFrom="0.975" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.659,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.096 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_2_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="417" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml
deleted file mode 100644
index c4f8181..0000000
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml
+++ /dev/null
@@ -1 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_3_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_3_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25" android:pivotX="40.25" android:pivotY="40.25" android:scaleX="0.975" android:scaleY="0.975"><path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#f2b8b5" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="9.950000000000003" android:translateY="10" android:pivotX="30" android:pivotY="30" android:scaleX="1.2" android:scaleY="1.2"><group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30" android:translateY="38.75" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/></group><group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30" android:translateY="25" android:pivotX="0.002" android:pivotY="7.488" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_1_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/></group></group><group android:name="_R_G_L_0_G" android:translateX="20.75" android:translateY="15.75" android:pivotX="19.341" android:pivotY="24.25" android:scaleX="0.5" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 "/><path android:name="_R_G_L_0_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 "/><path android:name="_R_G_L_0_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_3_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="167" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="167" android:startOffset="0" android:valueFrom="2.5" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="83" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="0.975" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="167" android:valueFrom="0.975" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.659,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.096 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="233" android:startOffset="0" android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="267" android:startOffset="233" android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueTo="M37.91 20.05 C37.91,20.05 37.89,14.16 37.89,14.16 C37.89,10.79 35.15,8.05 31.86,8.03 C28.46,8.01 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="167" android:startOffset="0" android:valueFrom="0.5" android:valueTo="0.5" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="167" android:startOffset="0" android:valueFrom="0.5" android:valueTo="0.5" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="333" android:startOffset="167" android:valueFrom="0.5" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="333" android:startOffset="167" android:valueFrom="0.5" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="167" android:valueFrom="0" android:valueTo="0.5" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="683" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
deleted file mode 100644
index c05a8d5..0000000
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
+++ /dev/null
@@ -1 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_3_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_3_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25" android:pivotX="40.25" android:pivotY="40.25" android:scaleX="0.975" android:scaleY="0"><path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#f2b8b5" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="0" android:strokeAlpha="0" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="9.950000000000003" android:translateY="10" android:pivotX="30" android:pivotY="30" android:scaleX="1.2" android:scaleY="1.2"><group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30" android:translateY="38.75" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/></group><group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30" android:translateY="25" android:pivotX="0.002" android:pivotY="7.488" android:scaleX="1" android:scaleY="0"><path android:name="_R_G_L_1_G_D_1_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/></group></group><group android:name="_R_G_L_0_G" android:translateX="20.659" android:translateY="15.75"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/><path android:name="_R_G_L_0_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/><path android:name="_R_G_L_0_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_3_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="83" android:startOffset="0" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeWidth" android:duration="233" android:startOffset="67" android:valueFrom="0" android:valueTo="2.5" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="0.975" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="167" android:valueFrom="0" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="167" android:valueFrom="0" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="267" android:valueFrom="1.1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="267" android:valueFrom="1.1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="167" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="167" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="167" android:valueFrom="0" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="267" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.341,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="267" android:valueFrom="1.1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_2_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="350" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml
deleted file mode 100644
index 1694429..0000000
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml
+++ /dev/null
@@ -1 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_2_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="20.75" android:translateY="15.75"><path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/><path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/><path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/><path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/></group><group android:name="_R_G_L_0_G" android:translateX="37.357" android:translateY="43.25" android:pivotX="2.75" android:pivotY="2.75" android:scaleX="1.41866" android:scaleY="1.41866"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#d3e3fd" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " android:valueTo="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="143" android:startOffset="107" android:valueFrom="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueTo="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.331,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="140" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="50" android:startOffset="140" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " android:valueTo="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="107" android:valueFrom="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueTo="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_2_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="250" android:startOffset="0" android:valueFrom="M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " android:valueTo="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.189,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="95" android:startOffset="0" android:valueFrom="M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " android:valueTo="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="24" android:startOffset="95" android:valueFrom="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueTo="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="81" android:startOffset="119" android:valueFrom="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.261,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="233" android:startOffset="200" android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueTo="M37.91 20.05 C37.91,20.05 37.89,14.16 37.89,14.16 C37.89,10.79 35.15,8.05 31.86,8.03 C28.46,8.01 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.123,0 0.23,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="120" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="20" android:startOffset="120" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="517" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_magnification_menu_close.xml b/packages/SystemUI/res/drawable/ic_magnification_menu_close.xml
new file mode 100644
index 0000000..a44a484
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_menu_close.xml
@@ -0,0 +1,29 @@
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32dp"
+ android:height="32dp"
+ android:viewportWidth="32"
+ android:viewportHeight="32">
+ <path
+ android:pathData="M7.3334,24.6667L24.6674,7.3334M7.3334,7.3334L24.6674,24.6667"
+ android:strokeLineJoin="round"
+ android:strokeWidth="1.5"
+ android:fillColor="#00000000"
+ android:strokeColor="#000000"
+ android:fillType="evenOdd"
+ android:strokeLineCap="round"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_magnification_menu_large.xml b/packages/SystemUI/res/drawable/ic_magnification_menu_large.xml
new file mode 100644
index 0000000..1ab0d4d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_menu_large.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:width="24dp"
+ android:height="24dp">
+ <path
+ android:pathData="M3 21L21 21C22.1 21 23 20.1 23 19L23 5C23 3.9 22.1 3 21 3L3 3C1.9 3 1 3.9 1 5L1 19C1 20.1 1.9 21 3 21ZM3 5L21 5L21 19L3 19L3 5ZM4 15L16.75 15L16.75 6L4 6L4 15Z"
+ android:fillType="evenOdd"
+ android:fillColor="#000000" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_magnification_menu_medium.xml b/packages/SystemUI/res/drawable/ic_magnification_menu_medium.xml
new file mode 100644
index 0000000..6fc89a6
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_menu_medium.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:width="24dp"
+ android:height="24dp">
+ <path
+ android:pathData="M3 21L21 21C22.1 21 23 20.1 23 19L23 5C23 3.9 22.1 3 21 3L3 3C1.9 3 1 3.9 1 5L1 19C1 20.1 1.9 21 3 21ZM3 5L21 5L21 19L3 19L3 5ZM4 12.75L13.75 12.75L13.75 6L4 6L4 12.75Z"
+ android:fillType="evenOdd"
+ android:fillColor="#000000" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_magnification_menu_small.xml b/packages/SystemUI/res/drawable/ic_magnification_menu_small.xml
new file mode 100644
index 0000000..fd73574
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_menu_small.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:width="24dp"
+ android:height="24dp">
+ <path
+ android:pathData="M3 21L21 21C22.1 21 23 20.1 23 19L23 5C23 3.9 22.1 3 21 3L3 3C1.9 3 1 3.9 1 5L1 19C1 20.1 1.9 21 3 21ZM3 5L21 5L21 19L3 19L3 5ZM4 10.5L8.5 10.5L8.5 6L4 6L4 10.5Z"
+ android:fillType="evenOdd"
+ android:fillColor="#000000" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_move_magnification.xml b/packages/SystemUI/res/drawable/ic_move_magnification.xml
index 96db365..1bff559 100644
--- a/packages/SystemUI/res/drawable/ic_move_magnification.xml
+++ b/packages/SystemUI/res/drawable/ic_move_magnification.xml
@@ -23,18 +23,21 @@
<size
android:height="@dimen/magnification_drag_view_size"
android:width="@dimen/magnification_drag_view_size"/>
+ <corners android:topLeftRadius="12dp"/>
+
</shape>
</item>
<item
android:gravity="center">
+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="30dp"
- android:height="30dp"
android:viewportWidth="24"
- android:viewportHeight="24">
+ android:viewportHeight="24"
+ android:width="24dp"
+ android:height="24dp">
<path
- android:pathData="M18.19,12.44l-3.24,-1.62c1.29,-1 2.12,-2.56 2.12,-4.32c0,-3.03 -2.47,-5.5 -5.5,-5.5s-5.5,2.47 -5.5,5.5c0,2.13 1.22,3.98 3,4.89v3.26c-2.11,-0.45 -2.01,-0.44 -2.26,-0.44c-0.53,0 -1.03,0.21 -1.41,0.59L4,16.22l5.09,5.09C9.52,21.75 10.12,22 10.74,22h6.3c0.98,0 1.81,-0.7 1.97,-1.67l0.8,-4.71C20.03,14.32 19.38,13.04 18.19,12.44zM17.84,15.29L17.04,20h-6.3c-0.09,0 -0.17,-0.04 -0.24,-0.1l-3.68,-3.68l4.25,0.89V6.5c0,-0.28 0.22,-0.5 0.5,-0.5c0.28,0 0.5,0.22 0.5,0.5v6h1.76l3.46,1.73C17.69,14.43 17.91,14.86 17.84,15.29zM8.07,6.5c0,-1.93 1.57,-3.5 3.5,-3.5s3.5,1.57 3.5,3.5c0,0.95 -0.38,1.81 -1,2.44V6.5c0,-1.38 -1.12,-2.5 -2.5,-2.5c-1.38,0 -2.5,1.12 -2.5,2.5v2.44C8.45,8.31 8.07,7.45 8.07,6.5z"
- android:fillColor="#FFFFFF"/>
+ android:pathData="M13.2217 21.7734C12.8857 22.1094 12.288 22.712 12 23C12 23 11.1143 22.1094 10.7783 21.7734L8.33494 19.3301L9.55662 18.1084L12 20.5518L14.4434 18.1084L15.665 19.3301L13.2217 21.7734ZM19.3301 15.665L18.1084 14.4433L20.5518 12L18.1084 9.5566L19.3301 8.33492L21.7735 10.7783C22.1094 11.1142 22.4241 11.4241 23 12C22.4241 12.5759 22.3963 12.5988 21.7735 13.2217L19.3301 15.665ZM14.4434 14.4433C13.7714 15.1153 12.957 15.4512 12 15.4512C11.043 15.4512 10.2285 15.1153 9.55662 14.4433C8.88469 13.7714 8.54873 12.957 8.54873 12C8.54873 11.043 8.88469 10.2285 9.55662 9.5566C10.2285 8.88468 11.043 8.54871 12 8.54871C12.957 8.54871 13.7714 8.88467 14.4434 9.5566C15.1153 10.2285 15.4512 11.043 15.4512 12C15.4512 12.957 15.1153 13.7714 14.4434 14.4433ZM4.66988 15.665L2.22651 13.2217C1.89055 12.8857 1.28791 12.288 1 12C1.28791 11.712 1.89055 11.1143 2.22651 10.7783L4.66988 8.33492L5.89157 9.5566L3.4482 12L5.89157 14.4433L4.66988 15.665ZM14.4434 5.89155L12 3.44818L9.55662 5.89155L8.33494 4.66987L10.7783 2.2265C11.1389 1.86592 11.2963 1.70369 12 1C12.5758 1.57585 12.8857 1.89053 13.2217 2.2265L15.665 4.66986L14.4434 5.89155Z"
+ android:fillColor="#ffffff" />
</vector>
</item>
</layer-list>
diff --git a/packages/SystemUI/res/drawable/ic_open_in_new_fullscreen.xml b/packages/SystemUI/res/drawable/ic_open_in_new_fullscreen.xml
index 36a1224..c7434f5 100644
--- a/packages/SystemUI/res/drawable/ic_open_in_new_fullscreen.xml
+++ b/packages/SystemUI/res/drawable/ic_open_in_new_fullscreen.xml
@@ -13,35 +13,19 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item>
- <shape android:shape="rectangle">
- <solid android:color="@color/magnification_switch_button_color" />
- <size
- android:width="48dp"
- android:height="48dp" />
- </shape>
- </item>
-
- <item
- android:gravity="center">
- <vector
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="24"
- android:viewportHeight="24">
- <group>
- <clip-path
- android:pathData="M0,0h24v24h-24z"/>
- <path
- android:pathData="M11,6.05V8.05H14.59L8,14.64V11.05H6V18.05H13V16.05H9.41L16,9.46V13.05H18V6.05H11Z"
- android:fillColor="#ffffff"/>
- <path
- android:pathData="M20,4.05V20.05H4V4.05H20ZM22,2.05H2V22.05H22V2.05Z"
- android:fillColor="#ffffff"/>
- </group>
- </vector>
- </item>
-
-</layer-list>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="36dp"
+ android:height="36dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <group>
+ <clip-path
+ android:pathData="M0,0h24v24h-24z"/>
+ <path
+ android:pathData="M11,6.05V8.05H14.59L8,14.64V11.05H6V18.05H13V16.05H9.41L16,9.46V13.05H18V6.05H11Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M20,4.05V20.05H4V4.05H20ZM22,2.05H2V22.05H22V2.05Z"
+ android:fillColor="#000000"/>
+ </group>
+</vector>
diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml
index e1b294f..d633803 100644
--- a/packages/SystemUI/res/layout/auth_biometric_contents.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml
@@ -49,11 +49,11 @@
<FrameLayout
android:id="@+id/biometric_icon_frame"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal">
- <ImageView
+ <com.airbnb.lottie.LottieAnimationView
android:id="@+id/biometric_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml b/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml
index ce53e27..01ea31f 100644
--- a/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml
@@ -17,7 +17,7 @@
<com.android.systemui.biometrics.AuthBiometricFingerprintView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/contents"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
diff --git a/packages/SystemUI/res/layout/window_magnification_settings_view.xml b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
new file mode 100644
index 0000000..6d8847c
--- /dev/null
+++ b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/magnifier_panel_view"
+ android:layout_width="@dimen/magnification_max_size"
+ android:layout_height="match_parent"
+ android:background="@drawable/accessibility_magnification_setting_view_bg"
+ android:orientation="vertical">
+ <LinearLayout
+ android:layout_width="@dimen/magnification_max_size"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/accessibility_magnifier_size"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:textColor="?android:attr/textColorAlertDialogListItem"
+ android:focusable="true"
+ android:layout_gravity="center_vertical|left"
+ android:layout_marginStart="20dp"/>
+
+ <Button
+ android:id="@+id/magnifier_edit_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/accessibility_magnifier_edit"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:textColor="?android:attr/textColorAlertDialogListItem"
+ android:focusable="true"
+ android:layout_gravity="right"
+ android:layout_marginEnd="20dp"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="@dimen/magnification_max_size"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <ImageButton
+ android:id="@+id/magnifier_small_button"
+ android:layout_width="0dp"
+ android:layout_height="56dp"
+ android:scaleType="center"
+ android:layout_weight="1"
+ android:layout_marginStart="12dp"/>
+
+ <ImageButton
+ android:id="@+id/magnifier_medium_button"
+ android:layout_width="0dp"
+ android:layout_height="56dp"
+ android:scaleType="center"
+ android:layout_weight="1"/>
+
+ <ImageButton
+ android:id="@+id/magnifier_large_button"
+ android:layout_width="0dp"
+ android:layout_height="56dp"
+ android:scaleType="center"
+ android:layout_weight="1"/>
+
+ <ImageButton
+ android:id="@+id/magnifier_full_button"
+ android:layout_width="0dp"
+ android:layout_height="56dp"
+ android:scaleType="center"
+ android:layout_weight="1"
+ android:layout_marginEnd="12dp"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingTop="8dp"
+ android:paddingEnd="20dp"
+ android:paddingStart="20dp"
+ android:focusable="true">
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:background="?android:attr/selectableItemBackground"
+ android:ellipsize="marquee"
+ android:gravity="center_vertical"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:text="@string/accessibility_allow_diagonal_scrolling"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:textColor="?android:attr/textColorAlertDialogListItem" />
+ </LinearLayout>
+
+ <Switch
+ android:id="@+id/magnifier_horizontal_lock_switch"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="right|center"
+ android:theme="@android:style/Theme.DeviceDefault.DayNight"/>
+ </LinearLayout>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/accessibility_magnification_zoom"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:textColor="?android:attr/textColorAlertDialogListItem"
+ android:focusable="true"
+ android:layout_marginStart="20dp"
+ android:paddingTop="2dp"
+ android:paddingBottom="10dp"/>
+
+ <SeekBar
+ android:id="@+id/magnifier_zoom_seekbar"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:progress="0"
+ android:max="6"
+ android:layout_marginEnd="20dp"
+ android:theme="@android:style/Theme.DeviceDefault.DayNight"/>
+
+
+ <Button
+ android:id="@+id/magnifier_close_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/accessibility_magnification_close"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:textColor="?android:attr/textColorAlertDialogListItem"
+ android:focusable="true"
+ android:layout_gravity="center_horizontal"
+ android:paddingBottom="24dp"/>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/window_magnifier_view.xml b/packages/SystemUI/res/layout/window_magnifier_view.xml
index 7c755e5..0bff47c 100644
--- a/packages/SystemUI/res/layout/window_magnifier_view.xml
+++ b/packages/SystemUI/res/layout/window_magnifier_view.xml
@@ -16,33 +16,25 @@
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:screenReaderFocusable="true">
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
<View
+ android:id="@+id/magnification_inner_border"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/magnification_outer_border_margin"
- android:importantForAccessibility="no"
- android:background="@android:color/black"/>
-
- <View
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_margin="@dimen/magnification_inner_border_margin"
- android:importantForAccessibility="no"
android:background="@color/magnification_border_color"/>
<RelativeLayout
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:importantForAccessibility="noHideDescendants">
+ android:layout_height="match_parent">
<View
android:id="@+id/left_handle"
android:layout_width="@dimen/magnification_border_drag_size"
android:layout_height="match_parent"
- android:layout_above="@+id/bottom_handle"/>
+ android:layout_alignParentStart="true"/>
<View
android:id="@+id/top_handle"
@@ -54,7 +46,6 @@
android:id="@+id/right_handle"
android:layout_width="@dimen/magnification_border_drag_size"
android:layout_height="match_parent"
- android:layout_above="@+id/bottom_handle"
android:layout_alignParentEnd="true"/>
<View
@@ -68,7 +59,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/magnification_mirror_surface_margin"/>
-
</RelativeLayout>
<ImageView
@@ -79,7 +69,17 @@
android:layout_gravity="right|bottom"
android:padding="@dimen/magnifier_drag_handle_padding"
android:scaleType="center"
- android:importantForAccessibility="no"
android:src="@drawable/ic_move_magnification"/>
-</FrameLayout>
\ No newline at end of file
+ <ImageView
+ android:id="@+id/close_button"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:layout_margin="30dp"
+ android:padding="@dimen/magnification_switch_button_padding"
+ android:layout_gravity="right|bottom"
+ android:scaleType="center"
+ android:visibility="gone"
+ android:background="@drawable/accessibility_magnifier_btn_bg"
+ android:src="@drawable/ic_magnification_menu_close" />
+</FrameLayout>
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_fingerprint_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_fingerprint_lottie.json
new file mode 100644
index 0000000..cc68a83
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_fingerprint_lottie.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":25,"w":80,"h":80,"nm":"error_to_fingerprint","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.701,0.42],[-1.757,0],[-1.577,-0.381],[-1.485,-0.816]],"o":[[1.455,-0.799],[1.608,-0.397],[1.719,0],[1.739,0.42],[0,0]],"v":[[-9.818,1.227],[-5.064,-0.618],[0,-1.227],[4.96,-0.643],[9.818,1.227]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-2.446,1.161],[-1.168,0.275],[-1.439,0],[-1.301,-0.304],[-1.225,-0.66],[-1.11,-1.844]],"o":[[1.23,-2.044],[1.024,-0.486],[1.312,-0.31],[1.425,0],[1.454,0.34],[2.122,1.143],[0,0]],"v":[[-13.091,3.273],[-7.438,-1.646],[-4.14,-2.797],[0,-3.273],[4.104,-2.805],[8.141,-1.29],[13.091,3.273]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-6.53,0],[0,-5.793],[0,0],[2.159,0],[0.59,1.489],[0,0],[1.587,0],[0,-2.16],[-0.81,-1.363],[-0.844,-0.674],[0,0]],"o":[[-0.753,-2.095],[0,-5.793],[6.529,0],[0,0],[0,2.16],[-1.604,0],[0,0],[-0.589,-1.489],[-2.161,0],[0,1.62],[0.54,0.909],[0,0],[0,0]],"v":[[-10.702,5.728],[-11.454,1.506],[0.001,-9],[11.454,1.506],[11.454,1.817],[7.544,5.728],[3.926,3.273],[2.618,0],[-0.997,-2.454],[-4.91,1.457],[-3.657,6.014],[-1.57,8.412],[-0.818,9]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.307,-0.561],[0.894,-0.16],[0.706,0],[0.844,0.193],[0.728,0.334],[0.967,0.901]],"o":[[-1.038,0.967],[-0.817,0.351],[-0.673,0.12],[-0.9,0],[-0.794,-0.182],[-1.203,-0.551],[0,0]],"v":[[8.182,-1.636],[4.642,0.681],[2.07,1.453],[-0.001,1.636],[-2.621,1.341],[-4.909,0.563],[-8.182,-1.636]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.299],"y":[1]},"o":{"x":[0.543],"y":[0]},"t":5,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":5,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[39.95,40,0],"ix":2,"l":2},"a":{"a":0,"k":[30,30,0],"ix":1,"l":2},"s":{"a":0,"k":[120,120,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-7.5],[1.2,-7.5],[1.2,7.5],[-1.2,7.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.002,32.488],"ix":2},"a":{"a":0,"k":[0.002,7.488],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.659,0.6],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.6,0.92],"y":[1,1.096]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":4,"s":[100,110]},{"t":10,"s":[100,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top!","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-1.25],[1.2,-1.25],[1.2,1.25],[-1.2,1.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30,38.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.6,0.6],"y":[1,1]},"o":{"x":[0.853,0.853],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.92,0.92],"y":[1.06,1.06]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":4,"s":[110,110]},{"t":10,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom!","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":25,"st":-30,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[97.5,97.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[2.5]},{"t":10,"s":[0]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":10,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"t":14,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_unlock_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_unlock_lottie.json
new file mode 100644
index 0000000..aaf7e58
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_unlock_lottie.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":41,"w":80,"h":80,"nm":"error_to_unlock","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.091,40,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[1.4,1.4,0]},"t":10,"s":[50,50,100]},{"t":30,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":14,"s":[{"i":[[0,0],[0,0],[-3.452,0],[0,-3.375],[0,0]],"o":[[0,0],[0,-3.375],[3.452,0],[0,0],[0,0]],"v":[[-6.217,12.558],[-6.234,6.669],[0.016,0.558],[6.266,6.669],[6.283,12.558]],"c":false}]},{"t":30,"s":[{"i":[[0,0],[0,0],[3.292,0.021],[0,-3.375],[0,0]],"o":[[0,0],[0,-3.375],[-3.393,-0.022],[0,0],[0,0]],"v":[[18.568,12.573],[18.552,6.684],[12.516,0.553],[6.266,6.669],[6.283,12.558]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-0.307,0.352],[-0.601,0],[0,0],[0,-1.104],[0,0]],"o":[[0,0],[0,-0.503],[0.367,-0.42],[0,0],[1.104,0],[0,0],[0,0]],"v":[[-11.2,14.15],[-11.198,6.146],[-10.705,4.831],[-9.198,4.146],[9.302,4.146],[11.302,6.146],[11.3,14.07]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-0.736,0],[0,-0.741],[0,0],[0.243,0],[0.066,0.191],[0,0],[0.179,0],[0,-0.276],[-0.162,-0.273],[-0.755,0.357],[0,0]],"o":[[-1.273,-0.008],[0,-0.741],[0.736,0],[0,0],[0,0.276],[-0.181,0],[0,0],[-0.066,-0.191],[-0.243,0],[-0.002,0.139],[0.109,0.182],[0.727,-0.402],[0,0]],"v":[[0.082,3.187],[-1.235,1.986],[0.055,0.642],[1.346,1.986],[1.346,2.026],[0.905,2.527],[0.498,2.212],[0.35,1.794],[-0.057,1.479],[-0.733,1.951],[-0.58,2.686],[0.619,3.071],[1.351,2]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0.446,-0.367],[0.481,0],[0,0],[0,1.104],[0,0]],"o":[[0,0],[0,0.623],[-0.345,0.284],[0,0],[-1.104,0],[0,0],[0,0]],"v":[[11.302,-10.469],[11.302,-2.469],[10.57,-0.923],[9.302,-0.469],[-9.198,-0.469],[-11.198,-2.469],[-11.198,-10.469]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":10,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[39.95,40,0],"ix":2,"l":2},"a":{"a":0,"k":[30,30,0],"ix":1,"l":2},"s":{"a":0,"k":[120,120,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-7.5],[1.2,-7.5],[1.2,7.5],[-1.2,7.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.002,32.488],"ix":2},"a":{"a":0,"k":[0.002,7.488],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.659,0.6],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.6,0.92],"y":[1,1.096]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":4,"s":[100,110]},{"t":10,"s":[100,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top!","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-1.25],[1.2,-1.25],[1.2,1.25],[-1.2,1.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30,38.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.6,0.6],"y":[1,1]},"o":{"x":[0.853,0.853],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.92,0.92],"y":[1.06,1.06]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":4,"s":[110,110]},{"t":10,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom!","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":86,"st":-30,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[97.5,97.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[2.5]},{"t":10,"s":[0]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":10,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"t":14,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_error_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_error_lottie.json
new file mode 100644
index 0000000..78bccba
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_error_lottie.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":80,"h":80,"nm":"fingerprint_to_error","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.701,0.42],[-1.757,0],[-1.577,-0.381],[-1.485,-0.816]],"o":[[1.455,-0.799],[1.608,-0.397],[1.719,0],[1.739,0.42],[0,0]],"v":[[-9.818,1.227],[-5.064,-0.618],[0,-1.227],[4.96,-0.643],[9.818,1.227]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-2.446,1.161],[-1.168,0.275],[-1.439,0],[-1.301,-0.304],[-1.225,-0.66],[-1.11,-1.844]],"o":[[1.23,-2.044],[1.024,-0.486],[1.312,-0.31],[1.425,0],[1.454,0.34],[2.122,1.143],[0,0]],"v":[[-13.091,3.273],[-7.438,-1.646],[-4.14,-2.797],[0,-3.273],[4.104,-2.805],[8.141,-1.29],[13.091,3.273]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-6.53,0],[0,-5.793],[0,0],[2.159,0],[0.59,1.489],[0,0],[1.587,0],[0,-2.16],[-0.81,-1.363],[-0.844,-0.674],[0,0]],"o":[[-0.753,-2.095],[0,-5.793],[6.529,0],[0,0],[0,2.16],[-1.604,0],[0,0],[-0.589,-1.489],[-2.161,0],[0,1.62],[0.54,0.909],[0,0],[0,0]],"v":[[-10.702,5.728],[-11.454,1.506],[0.001,-9],[11.454,1.506],[11.454,1.817],[7.544,5.728],[3.926,3.273],[2.618,0],[-0.997,-2.454],[-4.91,1.457],[-3.657,6.014],[-1.57,8.412],[-0.818,9]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.307,-0.561],[0.894,-0.16],[0.706,0],[0.844,0.193],[0.728,0.334],[0.967,0.901]],"o":[[-1.038,0.967],[-0.817,0.351],[-0.673,0.12],[-0.9,0],[-0.794,-0.182],[-1.203,-0.551],[0,0]],"v":[[8.182,-1.636],[4.642,0.681],[2.07,1.453],[-0.001,1.636],[-2.621,1.341],[-4.909,0.563],[-8.182,-1.636]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":0,"s":[0]},{"t":10,"s":[100]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":5,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[39.95,40,0],"ix":2,"l":2},"a":{"a":0,"k":[30,30,0],"ix":1,"l":2},"s":{"a":0,"k":[120,120,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-7.5],[1.2,-7.5],[1.2,7.5],[-1.2,7.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019610882,0.721568644047,0.709803938866,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.002,32.488],"ix":2},"a":{"a":0,"k":[0.002,7.488],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.4,0.08],"y":[0,0.096]},"t":10,"s":[100,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.341,0.4],"y":[0,0]},"t":16,"s":[100,110]},{"t":20,"s":[100,100]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top!","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-1.25],[1.2,-1.25],[1.2,1.25],[-1.2,1.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019610882,0.721568644047,0.709803938866,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30,38.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.08,0.08],"y":[0.06,0.06]},"t":10,"s":[0,0]},{"i":{"x":[0.147,0.147],"y":[1,1]},"o":{"x":[0.4,0.4],"y":[0,0]},"t":16,"s":[110,110]},{"t":20,"s":[100,100]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom!","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":21,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"t":9,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[97.5,97.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"t":9,"s":[100]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"t":9,"s":[100]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[0]},{"t":18,"s":[2.5]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":4,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":5,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_unlock_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_unlock_lottie.json
new file mode 100644
index 0000000..313c6c5
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_unlock_lottie.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":31,"w":80,"h":80,"nm":"fingerprint_to_unlock","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":2,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.107,46,0],"ix":2,"l":2},"a":{"a":0,"k":[2.75,2.75,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.43,0.43,0.2],"y":[1,1,1]},"o":{"x":[0.001,0.001,0.001],"y":[0,0,0]},"t":7.199,"s":[141.866,141.866,100]},{"t":15,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.381,0],[0,1.381],[1.381,0],[0,-1.381]],"o":[[1.381,0],[0,-1.381],[-1.381,0],[0,1.381]],"v":[[0,2.5],[2.5,0],[0,-2.5],[-2.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":7.199,"s":[0]},{"t":8.400390625,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[2.75,2.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.091,40,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.767},"o":{"x":0.541,"y":0},"t":0,"s":[{"i":[[0,0],[-1.701,0.42],[-1.757,0],[-1.577,-0.381],[-1.485,-0.816]],"o":[[1.455,-0.799],[1.608,-0.397],[1.719,0],[1.739,0.42],[0,0]],"v":[[-9.818,1.227],[-5.064,-0.618],[0,-1.227],[4.96,-0.643],[9.818,1.227]],"c":false}]},{"i":{"x":0.833,"y":0.767},"o":{"x":0.167,"y":0.233},"t":5.715,"s":[{"i":[[0,0],[-1.323,1.591],[-2.674,0],[-1.207,-1.781],[0,0]],"o":[[0,0],[1.298,-1.562],[2.657,0],[1.206,1.781],[0,0]],"v":[[-7.87,7.358],[-5.804,2.36],[0.009,-0.261],[5.845,2.706],[7.905,7.358]],"c":false}]},{"i":{"x":0.261,"y":1},"o":{"x":0.167,"y":0.233},"t":7.143,"s":[{"i":[[0,0],[-0.549,1.21],[-2.975,0],[-0.74,-2.398],[0,0]],"o":[[0,0],[0.796,-2.263],[2.964,0],[0.258,0.927],[0,0]],"v":[[-7.231,9.37],[-5.97,4.027],[0.012,0.056],[6.008,4.239],[7.277,9.37]],"c":false}]},{"i":{"x":0.23,"y":1},"o":{"x":0.123,"y":0},"t":12,"s":[{"i":[[0,0],[0,0],[-3.452,0],[0,-3.375],[0,0]],"o":[[0,0],[0,-3.375],[3.452,0],[0,0],[0,0]],"v":[[-6.217,12.558],[-6.234,6.669],[0.016,0.558],[6.266,6.669],[6.283,12.558]],"c":false}]},{"t":26,"s":[{"i":[[0,0],[0,0],[3.292,0.021],[0,-3.375],[0,0]],"o":[[0,0],[0,-3.375],[-3.393,-0.022],[0,0],[0,0]],"v":[[18.568,12.573],[18.552,6.684],[12.516,0.553],[6.266,6.669],[6.283,12.558]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.189,"y":1},"o":{"x":0.541,"y":0},"t":0,"s":[{"i":[[0,0],[-2.446,1.161],[-1.168,0.275],[-1.439,0],[-1.301,-0.304],[-1.225,-0.66],[-1.11,-1.844]],"o":[[1.23,-2.044],[1.024,-0.486],[1.312,-0.31],[1.425,0],[1.454,0.34],[2.122,1.143],[0,0]],"v":[[-13.091,3.273],[-7.438,-1.646],[-4.14,-2.797],[0,-3.273],[4.104,-2.805],[8.141,-1.29],[13.091,3.273]],"c":false}]},{"t":15,"s":[{"i":[[0,0],[0,0],[-0.307,0.352],[-0.601,0],[0,0],[0,-1.104],[0,0]],"o":[[0,0],[0,-0.503],[0.367,-0.42],[0,0],[1.104,0],[0,0],[0,0]],"v":[[-11.2,14.15],[-11.198,6.146],[-10.705,4.831],[-9.198,4.146],[9.302,4.146],[11.302,6.146],[11.3,14.07]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.767},"o":{"x":0.541,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[-6.53,0],[0,-5.793],[0,0],[2.159,0],[0.59,1.489],[0,0],[1.587,0],[0,-2.16],[-0.81,-1.363],[-0.844,-0.674],[0,0]],"o":[[-0.753,-2.095],[0,-5.793],[6.529,0],[0,0],[0,2.16],[-1.604,0],[0,0],[-0.589,-1.489],[-2.161,0],[0,1.62],[0.54,0.909],[0,0],[0,0]],"v":[[-10.702,5.728],[-11.454,1.506],[0.001,-9],[11.454,1.506],[11.454,1.817],[7.544,5.728],[3.926,3.273],[2.618,0],[-0.997,-2.454],[-4.91,1.457],[-3.657,6.014],[-1.57,8.412],[-0.818,9]],"c":false}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.233},"t":6.428,"s":[{"i":[[0,0],[0,0],[-1.576,0],[0,-1.474],[0,0],[1.541,0.347],[0.142,0.379],[0,0],[0.383,0],[0,-0.549],[-0.256,-0.431],[-0.768,0.207],[0,0]],"o":[[-1.823,0.497],[0,-1.474],[1.576,0],[0,0],[0,0.549],[-0.378,-0.085],[0,0],[-0.142,-0.379],[-0.521,0],[-0.002,0.353],[0.171,0.288],[0.622,-0.344],[0,0]],"v":[[-0.41,3.841],[-2.717,1.917],[0.047,-0.756],[2.811,1.917],[2.811,1.996],[0.225,3.848],[0.995,2.366],[0.679,1.534],[-0.193,0.909],[-1.338,1.879],[-1.026,3.169],[0.445,3.702],[1.036,3.015]],"c":false}]},{"t":12.857421875,"s":[{"i":[[0,0],[0,0],[-0.736,0],[0,-0.741],[0,0],[0.243,0],[0.066,0.191],[0,0],[0.179,0],[0,-0.276],[-0.162,-0.273],[-0.755,0.357],[0,0]],"o":[[-1.273,-0.008],[0,-0.741],[0.736,0],[0,0],[0,0.276],[-0.181,0],[0,0],[-0.066,-0.191],[-0.243,0],[-0.002,0.139],[0.109,0.182],[0.727,-0.402],[0,0]],"v":[[0.082,3.187],[-1.235,1.986],[0.055,0.642],[1.346,1.986],[1.346,2.026],[0.905,2.527],[0.498,2.212],[0.35,1.794],[-0.057,1.479],[-0.733,1.951],[-0.58,2.686],[0.619,3.071],[1.351,2]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":8.4,"s":[100]},{"t":11.3984375,"s":[0]}],"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.767},"o":{"x":0.541,"y":0},"t":0,"s":[{"i":[[0,0],[1.307,-0.561],[0.894,-0.16],[0.706,0],[0.844,0.193],[0.728,0.334],[0.967,0.901]],"o":[[-1.038,0.967],[-0.817,0.351],[-0.673,0.12],[-0.9,0],[-0.794,-0.182],[-1.203,-0.551],[0,0]],"v":[[8.182,-1.636],[4.642,0.681],[2.07,1.453],[-0.001,1.636],[-2.621,1.341],[-4.909,0.563],[-8.182,-1.636]],"c":false}]},{"i":{"x":0.331,"y":1},"o":{"x":0.167,"y":0.233},"t":6.428,"s":[{"i":[[0,0],[0.313,-0.134],[0.554,-0.317],[0.535,0],[0.203,0.046],[0.175,0.919],[0.232,0.216]],"o":[[-0.249,0.232],[-0.196,0.557],[-0.424,0.245],[-0.216,0],[-1.03,-0.044],[-0.288,-0.132],[0,0]],"v":[[11.468,-8.353],[10.62,-1.716],[9.232,-0.353],[7.057,0.034],[-7.634,-0.037],[-10.453,-1.739],[-11.238,-8.347]],"c":false}]},{"t":15,"s":[{"i":[[0,0],[0,0],[0.446,-0.367],[0.481,0],[0,0],[0,1.104],[0,0]],"o":[[0,0],[0,0.623],[-0.345,0.284],[0,0],[-1.104,0],[0,0],[0,0]],"v":[[11.302,-10.469],[11.302,-2.469],[10.57,-0.923],[9.302,-0.469],[-9.198,-0.469],[-11.198,-2.469],[-11.198,-10.469]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[{"tm":30,"cm":"1","dr":0},{"tm":51,"cm":"350ms\r","dr":0},{"tm":69,"cm":"650ms\r","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 1eece4c..26bf103 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -166,8 +166,12 @@
<!-- Window magnification colors -->
<color name="magnification_border_color">#FF9900</color>
+ <color name="magnification_border_color_change">#0000FF</color>
<color name="magnification_switch_button_color">#7F000000</color>
<color name="magnification_drag_handle_color">#B3000000</color>
+ <color name="accessibility_magnifier_bg">#FCFCFC</color>
+ <color name="accessibility_magnifier_bg_stroke">#E0E0E0</color>
+ <color name="accessibility_magnifier_icon_color">#252525</color>
<!-- Volume dialog colors -->
<color name="volume_dialog_background_color">@android:color/transparent</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 786b6b8..1168378 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -926,7 +926,8 @@
<!-- Biometric Dialog values -->
<dimen name="biometric_dialog_face_icon_size">64dp</dimen>
- <dimen name="biometric_dialog_fingerprint_icon_size">80dp</dimen>
+ <dimen name="biometric_dialog_fingerprint_icon_width">80dp</dimen>
+ <dimen name="biometric_dialog_fingerprint_icon_height">80dp</dimen>
<dimen name="biometric_dialog_button_negative_max_width">160dp</dimen>
<dimen name="biometric_dialog_button_positive_max_width">136dp</dimen>
<dimen name="biometric_dialog_corner_size">4dp</dimen>
@@ -1065,8 +1066,10 @@
<dimen name="magnification_frame_move_long">25dp</dimen>
<dimen name="magnification_drag_view_size">36dp</dimen>
<dimen name="magnification_controls_size">90dp</dimen>
- <dimen name="magnification_switch_button_size">48dp</dimen>
+ <dimen name="magnification_switch_button_size">56dp</dimen>
+ <dimen name="magnification_switch_button_padding">6dp</dimen>
<dimen name="magnification_switch_button_margin">16dp</dimen>
+ <dimen name="magnification_close_button_padding">15dp</dimen>
<dimen name="magnifier_left_right_controls_width">35dp</dimen>
<dimen name="magnifier_left_right_controls_height">45dp</dimen>
<dimen name="magnifier_up_down_controls_width">45dp</dimen>
@@ -1074,10 +1077,15 @@
<!-- The extra padding to show the whole outer border -->
<dimen name="magnifier_drag_handle_padding">3dp</dimen>
<dimen name="magnification_max_frame_size">300dp</dimen>
+
<!-- How far from the right edge of the screen you need to drag the window before the button
repositions to the other side. -->
<dimen name="magnification_button_reposition_threshold_from_edge">32dp</dimen>
+ <dimen name="magnification_drag_size">15dp</dimen>
+ <dimen name="magnification_max_size">360dp</dimen>
+ <dimen name="magnifier_panel_size">265dp</dimen>
+
<!-- Home Controls -->
<dimen name="controls_header_menu_size">48dp</dimen>
<dimen name="controls_header_bottom_margin">24dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7f3caec..b844f11 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2109,6 +2109,41 @@
<!-- Click action label for magnification switch. [CHAR LIMIT=NONE] -->
<string name="magnification_mode_switch_click_label">Switch</string>
+ <!-- Title of the magnification option button allow diagonal scrolling [CHAR LIMIT=NONE]-->
+ <string name="accessibility_allow_diagonal_scrolling">Allow diagonal scrolling</string>
+ <!-- Title of the magnification option button Resize [CHAR LIMIT=NONE]-->
+ <string name="accessibility_resize">Resize</string>
+ <!-- Title of the magnification option button Change type [CHAR LIMIT=NONE]-->
+ <string name="accessibility_change_magnification_type">Change magnification type</string>
+ <!-- Title of the magnification option button End resizing [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnification_end_resizing">End resizing</string>
+
+ <!-- Description of the window magnification Top handle [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnification_top_handle">Top handle</string>
+ <!-- Description of the window magnification Left handle [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnification_left_handle">Left handle</string>
+ <!-- Description of the window magnification Right handle [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnification_right_handle">Right handle</string>
+ <!-- Description of the window magnification Bottom handle [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnification_bottom_handle">Bottom handle</string>
+
+ <!-- Title of the window magnification panel option Magnifier size [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnifier_size">Magnifier size</string>
+ <!-- Title of the window magnification panel option Zoom [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnification_zoom">Zoom</string>
+ <!-- Click action label for magnification panel medium size [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnification_medium">Medium</string>
+ <!-- Click action label for magnification panel small size [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnification_small">Small</string>
+ <!-- Click action label for magnification panel large size [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnification_large">Large</string>
+ <!-- Click action label for magnification panel Close [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnification_close">Close</string>
+ <!-- Click action label for edit magnification size [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnifier_edit">Edit</string>
+ <!-- Click action label for magnification panel settings [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnification_magnifier_window_settings">Magnifier window settings</string>
+
<!-- Accessibility floating menu strings -->
<!-- Message for the accessibility floating button migration tooltip. It shows when the user use gestural navigation then upgrade their system. It will tell the user they could customize or replace the floating button in Settings. [CHAR LIMIT=100] -->
<string name="accessibility_floating_button_migration_tooltip">Tap to open accessibility features. Customize or replace this button in Settings.\n\n<annotation id="link">View settings</annotation></string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index d27b9ce..2f9bc1c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -54,7 +54,6 @@
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.hardware.SensorPrivacyManager;
-import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
@@ -87,7 +86,6 @@
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
-import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -715,7 +713,7 @@
private void handleFingerprintAuthFailed() {
Assert.isMainThread();
if (mHandler.hasCallbacks(mFpCancelNotReceived)) {
- Log.d(TAG, "handleFingerprintAuthFailed()"
+ mLogger.d("handleFingerprintAuthFailed()"
+ " triggered while waiting for cancellation, removing watchdog");
mHandler.removeCallbacks(mFpCancelNotReceived);
}
@@ -750,7 +748,7 @@
private void handleFingerprintAuthenticated(int authUserId, boolean isStrongBiometric) {
Trace.beginSection("KeyGuardUpdateMonitor#handlerFingerPrintAuthenticated");
if (mHandler.hasCallbacks(mFpCancelNotReceived)) {
- Log.d(TAG, "handleFingerprintAuthenticated()"
+ mLogger.d("handleFingerprintAuthenticated()"
+ " triggered while waiting for cancellation, removing watchdog");
mHandler.removeCallbacks(mFpCancelNotReceived);
}
@@ -825,7 +823,7 @@
if (msgId == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE
|| msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED) {
- Log.d(TAG, "Fingerprint retrying auth due to(" + msgId + ") -> " + errString);
+ mLogger.logRetryAfterFpError(msgId, errString);
mHandler.postDelayed(mRetryFingerprintAuthentication, HAL_ERROR_RETRY_TIMEOUT);
}
@@ -2020,13 +2018,12 @@
// in case authenticators aren't registered yet at this point:
mAuthController.addCallback(new AuthController.Callback() {
@Override
- public void onAllAuthenticatorsRegistered(
- @BiometricAuthenticator.Modality int modality) {
+ public void onAllAuthenticatorsRegistered() {
mainExecutor.execute(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE));
}
@Override
- public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) {
+ public void onEnrollmentsChanged() {
mainExecutor.execute(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE));
}
});
@@ -2567,8 +2564,8 @@
}
private boolean isOnlyFaceEnrolled() {
- return isFaceAuthEnabledForUser(getCurrentUser())
- && !isUnlockWithFingerprintPossible(getCurrentUser());
+ return isFaceEnrolled()
+ && !getCachedIsUnlockWithFingerprintPossible(sCurrentUser);
}
private void maybeLogListenerModelData(KeyguardListenModel model) {
@@ -2683,7 +2680,9 @@
return isUnlockWithFacePossible(userId) || isUnlockWithFingerprintPossible(userId);
}
- private boolean isUnlockWithFingerprintPossible(int userId) {
+ @VisibleForTesting
+ boolean isUnlockWithFingerprintPossible(int userId) {
+ // TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once.
mIsUnlockWithFingerprintPossible.put(userId, mFpm != null && mFpm.isHardwareDetected()
&& !isFingerprintDisabled(userId) && mFpm.hasEnrolledTemplates(userId));
return mIsUnlockWithFingerprintPossible.get(userId);
@@ -2705,6 +2704,7 @@
* If face hardware is available, user has enrolled and enabled auth via setting.
*/
public boolean isFaceAuthEnabledForUser(int userId) {
+ // TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once.
updateFaceEnrolled(userId);
return mIsFaceEnrolled;
}
@@ -3404,7 +3404,7 @@
mHandler.sendMessage(mHandler.obtainMessage(MSG_ASSISTANT_STACK_CHANGED,
info.visible));
} catch (RemoteException e) {
- Log.e(TAG, "unable to check task stack", e);
+ mLogger.logException(e, "unable to check task stack ");
}
}
};
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index d6974df..06e1828 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -16,7 +16,6 @@
package com.android.keyguard;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
import static com.android.keyguard.LockIconView.ICON_FINGERPRINT;
@@ -30,7 +29,6 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.AnimatedStateListDrawable;
-import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricSourceType;
import android.os.Process;
import android.os.VibrationAttributes;
@@ -703,17 +701,13 @@
private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
@Override
- public void onAllAuthenticatorsRegistered(@BiometricAuthenticator.Modality int modality) {
- if (modality == TYPE_FINGERPRINT) {
- updateUdfpsConfig();
- }
+ public void onAllAuthenticatorsRegistered() {
+ updateUdfpsConfig();
}
@Override
- public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) {
- if (modality == TYPE_FINGERPRINT) {
- updateUdfpsConfig();
- }
+ public void onEnrollmentsChanged() {
+ updateUdfpsConfig();
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 035b7f0..d718a24 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -198,6 +198,15 @@
{ "Retrying face after HW unavailable, attempt $int1" })
}
+ fun logRetryAfterFpError(msgId: Int, errString: String?) {
+ logBuffer.log(TAG, DEBUG, {
+ int1 = msgId
+ str1 = "$errString"
+ }, {
+ "Fingerprint retrying auth due to($int1) -> $str1"
+ })
+ }
+
fun logRetryAfterFpHwUnavailable(retryCount: Int) {
logBuffer.log(TAG, WARNING,
{ int1 = retryCount },
@@ -270,12 +279,12 @@
{ str1 = newTimeFormat },
{ "handleTimeFormatUpdate timeFormat=$str1" })
}
-
fun logUdfpsPointerDown(sensorId: Int) {
logBuffer.log(TAG, DEBUG,
{ int1 = sensorId },
{ "onUdfpsPointerDown, sensorId: $int1" })
}
+
fun logUdfpsPointerUp(sensorId: Int) {
logBuffer.log(TAG, DEBUG,
{ int1 = sensorId },
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationGestureDetector.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationGestureDetector.java
index 4b30ec3..c91082c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationGestureDetector.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationGestureDetector.java
@@ -23,6 +23,7 @@
import android.os.Handler;
import android.view.Display;
import android.view.MotionEvent;
+import android.view.View;
import android.view.ViewConfiguration;
/**
@@ -41,7 +42,7 @@
*
* @return {@code true} if this gesture is handled.
*/
- boolean onSingleTap();
+ boolean onSingleTap(View view);
/**
* Called when the user is performing dragging gesture. It is started after the offset
@@ -52,7 +53,7 @@
* @param offsetY The Y offset in screen coordinate.
* @return {@code true} if this gesture is handled.
*/
- boolean onDrag(float offsetX, float offsetY);
+ boolean onDrag(View view, float offsetX, float offsetY);
/**
* Notified when a tap occurs with the down {@link MotionEvent} that triggered it. This will
@@ -109,7 +110,7 @@
* @param event The current motion event.
* @return {@code True} if the {@link OnGestureListener} consumes the event, else false.
*/
- boolean onTouch(MotionEvent event) {
+ boolean onTouch(View view, MotionEvent event) {
final float rawX = event.getRawX();
final float rawY = event.getRawY();
boolean handled = false;
@@ -125,12 +126,12 @@
break;
case MotionEvent.ACTION_MOVE:
stopSingleTapDetectionIfNeeded(rawX, rawY);
- handled |= notifyDraggingGestureIfNeeded(rawX, rawY);
+ handled |= notifyDraggingGestureIfNeeded(view, rawX, rawY);
break;
case MotionEvent.ACTION_UP:
stopSingleTapDetectionIfNeeded(rawX, rawY);
if (mDetectSingleTap) {
- handled |= mOnGestureListener.onSingleTap();
+ handled |= mOnGestureListener.onSingleTap(view);
}
// Fall through
case MotionEvent.ACTION_CANCEL:
@@ -163,7 +164,7 @@
mDetectSingleTap = false;
}
- private boolean notifyDraggingGestureIfNeeded(float x, float y) {
+ private boolean notifyDraggingGestureIfNeeded(View view, float x, float y) {
if (!mDraggingDetected) {
return false;
}
@@ -173,7 +174,7 @@
final float offsetX = x - mPointerLocation.x;
final float offsetY = y - mPointerLocation.y;
mPointerLocation.set(x, y);
- return mOnGestureListener.onDrag(offsetX, offsetY);
+ return mOnGestureListener.onDrag(view, offsetX, offsetY);
}
private void reset() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
index dbd215d..59a5b15 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
@@ -213,18 +213,18 @@
if (!mIsVisible) {
return false;
}
- return mGestureDetector.onTouch(event);
+ return mGestureDetector.onTouch(v, event);
}
@Override
- public boolean onSingleTap() {
+ public boolean onSingleTap(View v) {
mSingleTapDetected = true;
handleSingleTap();
return true;
}
@Override
- public boolean onDrag(float offsetX, float offsetY) {
+ public boolean onDrag(View v, float offsetX, float offsetY) {
moveButton(offsetX, offsetY);
return true;
}
@@ -292,9 +292,12 @@
* @param resetPosition if the button position needs be reset
*/
private void showButton(int mode, boolean resetPosition) {
+ if (mode != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
+ return;
+ }
if (mMagnificationMode != mode) {
mMagnificationMode = mode;
- mImageView.setImageResource(getIconResId(mode));
+ mImageView.setImageResource(getIconResId(mMagnificationMode));
}
if (!mIsVisible) {
onConfigurationChanged(mContext.getResources().getConfiguration());
@@ -408,6 +411,7 @@
private static ImageView createView(Context context) {
ImageView imageView = new ImageView(context);
+ imageView.setScaleType(ImageView.ScaleType.CENTER);
imageView.setClickable(true);
imageView.setFocusable(true);
imageView.setAlpha(0f);
@@ -415,10 +419,8 @@
}
@VisibleForTesting
- static int getIconResId(int mode) {
- return (mode == Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)
- ? R.drawable.ic_open_in_new_window
- : R.drawable.ic_open_in_new_fullscreen;
+ static int getIconResId(int mode) { // TODO(b/242233514): delete non used param
+ return R.drawable.ic_open_in_new_window;
}
private static LayoutParams createLayoutParams(Context context) {
@@ -461,4 +463,4 @@
new Rect(0, 0, mImageView.getWidth(), mImageView.getHeight())));
});
}
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index d7fead1..f4701ed 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -233,6 +233,13 @@
}
@Override
+ public void onModeSwitch(int displayId, int newMode) {
+ if (mWindowMagnificationConnectionImpl != null) {
+ mWindowMagnificationConnectionImpl.onChangeMagnificationMode(displayId, newMode);
+ }
+ }
+
+ @Override
public void requestWindowMagnificationConnection(boolean connect) {
if (connect) {
setWindowMagnificationConnection();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 1eedae6..813f4dd 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -19,8 +19,11 @@
import static android.view.WindowInsets.Type.systemGestures;
import static android.view.WindowManager.LayoutParams;
+import static com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
+import static java.lang.Math.abs;
+
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.annotation.NonNull;
@@ -41,6 +44,8 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.util.Log;
import android.util.Range;
import android.util.Size;
@@ -62,6 +67,7 @@
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.IRemoteMagnificationAnimationCallback;
import android.widget.FrameLayout;
+import android.widget.ImageView;
import androidx.core.math.MathUtils;
@@ -93,6 +99,7 @@
private static final Range<Float> A11Y_ACTION_SCALE_RANGE = new Range<>(2.0f, 8.0f);
private static final float A11Y_CHANGE_SCALE_DIFFERENCE = 1.0f;
private static final float ANIMATION_BOUNCE_EFFECT_SCALE = 1.05f;
+ private static final float[] MAGNIFICATION_SCALE_OPTIONS = {1.0f, 1.4f, 1.8f, 2.5f};
private final Context mContext;
private final Resources mResources;
@@ -145,7 +152,8 @@
// The root of the mirrored content
private SurfaceControl mMirrorSurface;
- private View mDragView;
+ private ImageView mDragView;
+ private ImageView mCloseView;
private View mLeftDrag;
private View mTopDrag;
private View mRightDrag;
@@ -162,6 +170,7 @@
private final Runnable mWindowInsetChangeRunnable;
// MirrorView is the mirror window which displays the magnified content.
private View mMirrorView;
+ private View mMirrorBorderView;
private SurfaceView mMirrorSurfaceView;
private int mMirrorSurfaceMargin;
private int mBorderDragSize;
@@ -172,6 +181,7 @@
* repositions to the other side.
*/
private int mButtonRepositionThresholdFromEdge;
+
// The boundary of magnification frame.
private final Rect mMagnificationFrameBoundary = new Rect();
// The top Y of the system gesture rect at the bottom. Set to -1 if it is invalid.
@@ -192,6 +202,18 @@
private boolean mOverlapWithGestureInsets;
private boolean mIsDragging;
+ // Window Magnification Setting view
+ private WindowMagnificationSettings mWindowMagnificationSettings;
+
+ private static final int MAX_HORIZONTAL_MOVE_ANGLE = 50;
+ private static final int HORIZONTAL = 1;
+ private static final int VERTICAL = 0;
+ private static final double HORIZONTAL_LOCK_BASE =
+ Math.tan(Math.toRadians(MAX_HORIZONTAL_MOVE_ANGLE));
+
+ private boolean mAllowDiagonalScrolling = false;
+ private boolean mEditSizeEnable = false;
+
@Nullable
private MirrorWindowControl mMirrorWindowControl;
@@ -223,7 +245,12 @@
mWindowBounds = new Rect(mWm.getCurrentWindowMetrics().getBounds());
mResources = mContext.getResources();
- mScale = mResources.getInteger(R.integer.magnification_default_scale);
+ mScale = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+ mResources.getInteger(R.integer.magnification_default_scale),
+ UserHandle.USER_CURRENT);
+
+
mBounceEffectDuration = mResources.getInteger(
com.android.internal.R.integer.config_shortAnimTime);
updateDimensions();
@@ -241,6 +268,10 @@
mGestureDetector =
new MagnificationGestureDetector(mContext, handler, this);
+ mWindowMagnificationSettings =
+ new WindowMagnificationSettings(mContext, mWindowMagnificationSettingsCallback,
+ mSfVsyncFrameProvider);
+
// Initialize listeners.
mMirrorViewRunnable = () -> {
if (mMirrorView != null) {
@@ -326,6 +357,26 @@
return false;
}
+ private void changeMagnificationSize(@MagnificationSize int index) {
+ final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3;
+ int size = (int) (initSize * MAGNIFICATION_SCALE_OPTIONS[index]);
+ setWindowSize(size, size);
+ }
+
+ private void setEditMagnifierSizeMode(boolean enable) {
+ mEditSizeEnable = enable;
+ applyResourcesValues();
+
+ if (isWindowVisible()) {
+ updateDimensions();
+ applyTapExcludeRegion();
+ }
+ }
+
+ private void setDiagonalScrolling(boolean enable) {
+ mAllowDiagonalScrolling = enable;
+ }
+
/**
* Wraps {@link WindowMagnificationController#deleteWindowMagnification()}} with transition
* animation. If the window magnification is enabling, it runs the animation in reverse.
@@ -346,6 +397,9 @@
if (!isWindowVisible()) {
return;
}
+
+ closeMagnificationSettings();
+
if (mMirrorSurface != null) {
mTransaction.remove(mMirrorSurface).apply();
mMirrorSurface = null;
@@ -368,6 +422,8 @@
mMirrorViewBounds.setEmpty();
mSourceBounds.setEmpty();
updateSystemUIStateIfNeeded();
+ setEditMagnifierSizeMode(false);
+
mContext.unregisterComponentCallbacks(this);
// Notify source bounds empty when magnification is deleted.
mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId, new Rect());
@@ -505,7 +561,7 @@
/** Returns the rotation degree change of two {@link Surface.Rotation} */
private int getDegreeFromRotation(@Surface.Rotation int newRotation,
- @Surface.Rotation int oldRotation) {
+ @Surface.Rotation int oldRotation) {
final int rotationDiff = oldRotation - newRotation;
final int degree = (rotationDiff + 4) % 4 * 90;
return degree;
@@ -534,6 +590,8 @@
mMirrorView = LayoutInflater.from(mContext).inflate(R.layout.window_magnifier_view, null);
mMirrorSurfaceView = mMirrorView.findViewById(R.id.surface_view);
+ mMirrorBorderView = mMirrorView.findViewById(R.id.magnification_inner_border);
+
// Allow taps to go through to the mirror SurfaceView below.
mMirrorSurfaceView.addOnLayoutChangeListener(mMirrorSurfaceViewLayoutChangeListener);
@@ -600,6 +658,18 @@
}
}
+ private void showMagnificationSettings() {
+ if (mWindowMagnificationSettings != null) {
+ mWindowMagnificationSettings.showSettingPanel();
+ }
+ }
+
+ private void closeMagnificationSettings() {
+ if (mWindowMagnificationSettings != null) {
+ mWindowMagnificationSettings.hideSettingPanel();
+ }
+ }
+
/**
* Sets the window size with given width and height in pixels without changing the
* window center. The width or the height will be clamped in the range
@@ -668,12 +738,14 @@
mTopDrag = mMirrorView.findViewById(R.id.top_handle);
mRightDrag = mMirrorView.findViewById(R.id.right_handle);
mBottomDrag = mMirrorView.findViewById(R.id.bottom_handle);
+ mCloseView = mMirrorView.findViewById(R.id.close_button);
mDragView.setOnTouchListener(this);
mLeftDrag.setOnTouchListener(this);
mTopDrag.setOnTouchListener(this);
mRightDrag.setOnTouchListener(this);
mBottomDrag.setOnTouchListener(this);
+ mCloseView.setOnTouchListener(this);
}
/**
@@ -743,8 +815,8 @@
@Override
public boolean onTouch(View v, MotionEvent event) {
if (v == mDragView || v == mLeftDrag || v == mTopDrag || v == mRightDrag
- || v == mBottomDrag) {
- return mGestureDetector.onTouch(event);
+ || v == mBottomDrag || v == mCloseView) {
+ return mGestureDetector.onTouch(v, event);
}
return false;
}
@@ -768,6 +840,7 @@
int right = displayFrame.right - (halfWidth - (int) (halfWidth / scale));
int top = displayFrame.top + (halfHeight - (int) (halfHeight / scale));
int bottom = displayFrame.bottom - (halfHeight - (int) (halfHeight / scale));
+
mSourceBounds.set(left, top, right, bottom);
// SourceBound's center is equal to center[X,Y] but calculated from MagnificationFrame's
@@ -950,7 +1023,7 @@
* or {@link Float#NaN} to leave unchanged.
*/
void enableWindowMagnificationInternal(float scale, float centerX, float centerY,
- float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY) {
+ float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY) {
if (Float.compare(scale, 1.0f) <= 0) {
deleteWindowMagnification();
return;
@@ -983,6 +1056,7 @@
if (!isWindowVisible()) {
createMirrorWindow();
showControls();
+ applyResourcesValues();
} else {
modifyWindowMagnification(false);
}
@@ -997,6 +1071,7 @@
if (mAnimationController.isAnimating() || !isWindowVisible() || mScale == scale) {
return;
}
+
enableWindowMagnificationInternal(scale, Float.NaN, Float.NaN);
mHandler.removeCallbacks(mUpdateStateDescriptionRunnable);
mHandler.postDelayed(mUpdateStateDescriptionRunnable, UPDATE_STATE_DESCRIPTION_DELAY_MS);
@@ -1014,19 +1089,42 @@
if (mAnimationController.isAnimating() || mMirrorSurfaceView == null) {
return;
}
+
+ if (!mAllowDiagonalScrolling) {
+ int direction = selectDirectionForMove(abs(offsetX), abs(offsetY));
+
+ if (direction == HORIZONTAL) {
+ offsetY = 0;
+ } else {
+ offsetX = 0;
+ }
+ }
+
if (updateMagnificationFramePosition((int) offsetX, (int) offsetY)) {
modifyWindowMagnification(false);
}
}
void moveWindowMagnifierToPosition(float positionX, float positionY,
- IRemoteMagnificationAnimationCallback callback) {
+ IRemoteMagnificationAnimationCallback callback) {
if (mMirrorSurfaceView == null) {
return;
}
mAnimationController.moveWindowMagnifierToPosition(positionX, positionY, callback);
}
+ private int selectDirectionForMove(float diffX, float diffY) {
+ int direction = 0;
+ float result = diffY / diffX;
+
+ if (result <= HORIZONTAL_LOCK_BASE) {
+ direction = HORIZONTAL; // horizontal move
+ } else {
+ direction = VERTICAL; // vertical move
+ }
+ return direction;
+ }
+
/**
* Gets the scale.
*
@@ -1072,17 +1170,143 @@
}
@Override
- public boolean onSingleTap() {
- animateBounceEffect();
+ public boolean onSingleTap(View view) {
+ handleSingleTap(view);
return true;
}
@Override
- public boolean onDrag(float offsetX, float offsetY) {
- move((int) offsetX, (int) offsetY);
+ public boolean onDrag(View view, float offsetX, float offsetY) {
+ if (mEditSizeEnable) {
+ changeWindowSize(view, offsetX, offsetY);
+ } else {
+ move((int) offsetX, (int) offsetY);
+ }
return true;
}
+ private void handleSingleTap(View view) {
+ int id = view.getId();
+ if (id == R.id.drag_handle) {
+ showMagnificationSettings();
+ } else if (id == R.id.close_button) {
+ setEditMagnifierSizeMode(false);
+ } else {
+ animateBounceEffect();
+ }
+ }
+
+ private void applyResourcesValues() {
+ mMirrorBorderView.setBackgroundColor(mResources.getColor(mEditSizeEnable
+ ? R.color.magnification_border_color_change : R.color.magnification_border_color));
+
+ if (mEditSizeEnable) {
+ mDragView.setVisibility(View.GONE);
+ mCloseView.setVisibility(View.VISIBLE);
+ } else {
+ mDragView.setVisibility(View.VISIBLE);
+ mCloseView.setVisibility(View.GONE);
+ }
+ }
+
+ public boolean changeWindowSize(View view, float offsetX, float offsetY) {
+ boolean bRTL = isRTL(mContext);
+ final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3;
+
+ final int maxHeightSize = mWindowBounds.height() - 2 * mMirrorSurfaceMargin;
+ final int maxWidthSize = mWindowBounds.width() - 2 * mMirrorSurfaceMargin;
+
+ Rect tempRect = new Rect();
+ tempRect.set(mMagnificationFrame);
+
+ if (view == mLeftDrag) {
+ if (bRTL) {
+ tempRect.right += offsetX;
+ if (tempRect.right > mWindowBounds.width()) {
+ return false;
+ }
+ } else {
+ tempRect.left += offsetX;
+ if (tempRect.left < 0) {
+ return false;
+ }
+ }
+ } else if (view == mRightDrag) {
+ if (bRTL) {
+ tempRect.left += offsetX;
+ if (tempRect.left < 0) {
+ return false;
+ }
+ } else {
+ tempRect.right += offsetX;
+ if (tempRect.right > mWindowBounds.width()) {
+ return false;
+ }
+ }
+ } else if (view == mTopDrag) {
+ tempRect.top += offsetY;
+ if (tempRect.top < 0) {
+ return false;
+ }
+ } else if (view == mBottomDrag) {
+ tempRect.bottom += offsetY;
+ if (tempRect.bottom > mWindowBounds.height()) {
+ return false;
+ }
+ }
+
+ if (tempRect.width() < initSize || tempRect.height() < initSize
+ || tempRect.width() > maxWidthSize || tempRect.height() > maxHeightSize) {
+ return false;
+ }
+
+ mMagnificationFrame.set(tempRect);
+
+ computeBounceAnimationScale();
+ calculateMagnificationFrameBoundary();
+
+ modifyWindowMagnification(true);
+ return true;
+ }
+
+ private static boolean isRTL(Context context) {
+ Configuration config = context.getResources().getConfiguration();
+ if (config == null) {
+ return false;
+ }
+ return (config.screenLayout & Configuration.SCREENLAYOUT_LAYOUTDIR_MASK)
+ == Configuration.SCREENLAYOUT_LAYOUTDIR_RTL;
+ }
+
+ private WindowMagnificationSettingsCallback mWindowMagnificationSettingsCallback =
+ new WindowMagnificationSettingsCallback() {
+ @Override
+ public void onSetDiagonalScrolling(boolean enable) {
+ setDiagonalScrolling(enable);
+ }
+
+ @Override
+ public void onModeSwitch(int newMode) {
+ mWindowMagnifierCallback.onModeSwitch(mDisplayId, newMode);
+ }
+
+ @Override
+ public void onSetMagnifierSize(@MagnificationSize int index) {
+ changeMagnificationSize(index);
+ }
+
+ @Override
+ public void onEditMagnifierSizeMode(boolean enable) {
+ setEditMagnifierSizeMode(enable);
+ }
+
+ @Override
+ public void onMagnifierScale(float scale) {
+ mWindowMagnifierCallback.onPerformScaleAction(mDisplayId,
+ A11Y_ACTION_SCALE_RANGE.clamp(scale));
+ }
+ };
+
@Override
public boolean onStart(float x, float y) {
mIsDragging = true;
@@ -1138,7 +1362,7 @@
pw.println(" mMagnificationFrame:"
+ (isWindowVisible() ? mMagnificationFrame : "empty"));
pw.println(" mSourceBounds:"
- + (mSourceBounds.isEmpty() ? "empty" : mSourceBounds));
+ + (mSourceBounds.isEmpty() ? "empty" : mSourceBounds));
pw.println(" mSystemGestureTop:" + mSystemGestureTop);
pw.println(" mMagnificationFrameOffsetX:" + mMagnificationFrameOffsetX);
pw.println(" mMagnificationFrameOffsetY:" + mMagnificationFrameOffsetY);
@@ -1149,6 +1373,11 @@
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
+ final AccessibilityAction clickAction = new AccessibilityAction(
+ AccessibilityAction.ACTION_CLICK.getId(), mContext.getResources().getString(
+ R.string.magnification_mode_switch_click_label));
+ info.addAction(clickAction);
+ info.setClickable(true);
info.addAction(
new AccessibilityAction(R.id.accessibility_action_zoom_in,
mContext.getString(R.string.accessibility_control_zoom_in)));
@@ -1176,7 +1405,10 @@
}
private boolean performA11yAction(int action) {
- if (action == R.id.accessibility_action_zoom_in) {
+ if (action == AccessibilityAction.ACTION_CLICK.getId()) {
+ // Simulate tapping the drag view so it opens the Settings.
+ handleSingleTap(mDragView);
+ } else if (action == R.id.accessibility_action_zoom_in) {
final float scale = mScale + A11Y_CHANGE_SCALE_DIFFERENCE;
mWindowMagnifierCallback.onPerformScaleAction(mDisplayId,
A11Y_ACTION_SCALE_RANGE.clamp(scale));
@@ -1199,4 +1431,4 @@
return true;
}
}
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
new file mode 100644
index 0000000..9cffd5d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -0,0 +1,592 @@
+/*
+ * Copyright (C) 2022 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.accessibility;
+
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import android.annotation.IntDef;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.graphics.Insets;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.MathUtils;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.AccessibilityDelegate;
+import android.view.ViewGroup;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.view.WindowMetrics;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+import android.widget.Switch;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.systemui.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+
+/**
+ * Class to set value about WindowManificationSettings.
+ */
+class WindowMagnificationSettings implements MagnificationGestureDetector.OnGestureListener {
+ private static final String TAG = "WindowMagnificationSettings";
+ private final Context mContext;
+ private final AccessibilityManager mAccessibilityManager;
+ private final WindowManager mWindowManager;
+
+ private final Runnable mWindowInsetChangeRunnable;
+ private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
+
+ private final LayoutParams mParams;
+ @VisibleForTesting
+ final Rect mDraggableWindowBounds = new Rect();
+ private boolean mIsVisible = false;
+ private final MagnificationGestureDetector mGestureDetector;
+ private boolean mSingleTapDetected = false;
+
+ private SeekBar mZoomSeekbar;
+ private Switch mAllowDiagonalScrollingSwitch;
+ private LinearLayout mPanelView;
+ private LinearLayout mSettingView;
+ private LinearLayout mButtonView;
+ private ImageButton mSmallButton;
+ private ImageButton mMediumButton;
+ private ImageButton mLargeButton;
+ private Button mCloseButton;
+ private Button mEditButton;
+ private ImageButton mChangeModeButton;
+ private boolean mAllowDiagonalScrolling = false;
+ private static final float A11Y_CHANGE_SCALE_DIFFERENCE = 1.0f;
+ private static final float A11Y_SCALE_MIN_VALUE = 2.0f;
+ private WindowMagnificationSettingsCallback mCallback;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ MagnificationSize.NONE,
+ MagnificationSize.SMALL,
+ MagnificationSize.MEDIUM,
+ MagnificationSize.LARGE,
+ })
+ /** Denotes the Magnification size type. */
+ @interface MagnificationSize {
+ int NONE = 0;
+ int SMALL = 1;
+ int MEDIUM = 2;
+ int LARGE = 3;
+ }
+
+ @VisibleForTesting
+ WindowMagnificationSettings(Context context, WindowMagnificationSettingsCallback callback,
+ SfVsyncFrameCallbackProvider sfVsyncFrameProvider) {
+ mContext = context;
+ mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
+ mWindowManager = mContext.getSystemService(WindowManager.class);
+ mSfVsyncFrameProvider = sfVsyncFrameProvider;
+ mCallback = callback;
+
+ mAllowDiagonalScrolling = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING, 0,
+ UserHandle.USER_CURRENT) == 1;
+
+ float scale = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 0,
+ UserHandle.USER_CURRENT);
+
+ mSettingView = (LinearLayout) View.inflate(context,
+ R.layout.window_magnification_settings_view, null);
+
+ mSettingView.setClickable(true);
+ mSettingView.setFocusable(true);
+ mSettingView.setOnTouchListener(this::onTouch);
+
+ mPanelView = mSettingView.findViewById(R.id.magnifier_panel_view);
+ mSmallButton = mSettingView.findViewById(R.id.magnifier_small_button);
+ mMediumButton = mSettingView.findViewById(R.id.magnifier_medium_button);
+ mLargeButton = mSettingView.findViewById(R.id.magnifier_large_button);
+ mCloseButton = mSettingView.findViewById(R.id.magnifier_close_button);
+ mEditButton = mSettingView.findViewById(R.id.magnifier_edit_button);
+ mChangeModeButton = mSettingView.findViewById(R.id.magnifier_full_button);
+
+ mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_seekbar);
+ mZoomSeekbar.setOnSeekBarChangeListener(new ZoomSeekbarChangeListener());
+ setSeekbarProgress(scale);
+ mAllowDiagonalScrollingSwitch =
+ (Switch) mSettingView.findViewById(R.id.magnifier_horizontal_lock_switch);
+ mAllowDiagonalScrollingSwitch.setChecked(mAllowDiagonalScrolling);
+ mAllowDiagonalScrollingSwitch.setOnCheckedChangeListener((view, checked) -> {
+ toggleDiagonalScrolling();
+ });
+
+ mParams = createLayoutParams(context);
+ applyResourcesValues();
+
+ mSmallButton.setAccessibilityDelegate(mButtonDelegate);
+ mSmallButton.setOnClickListener(mButtonClickListener);
+
+ mMediumButton.setAccessibilityDelegate(mButtonDelegate);
+ mMediumButton.setOnClickListener(mButtonClickListener);
+
+ mLargeButton.setAccessibilityDelegate(mButtonDelegate);
+ mLargeButton.setOnClickListener(mButtonClickListener);
+
+ mCloseButton.setAccessibilityDelegate(mButtonDelegate);
+ mCloseButton.setOnClickListener(mButtonClickListener);
+
+ mChangeModeButton.setAccessibilityDelegate(mButtonDelegate);
+ mChangeModeButton.setOnClickListener(mButtonClickListener);
+
+ mEditButton.setAccessibilityDelegate(mButtonDelegate);
+ mEditButton.setOnClickListener(mButtonClickListener);
+
+ mWindowInsetChangeRunnable = this::onWindowInsetChanged;
+ mSettingView.setOnApplyWindowInsetsListener((v, insets) -> {
+ // Adds a pending post check to avoiding redundant calculation because this callback
+ // is sent frequently when the switch icon window dragged by the users.
+ if (!mSettingView.getHandler().hasCallbacks(mWindowInsetChangeRunnable)) {
+ mSettingView.getHandler().post(mWindowInsetChangeRunnable);
+ }
+ return v.onApplyWindowInsets(insets);
+ });
+
+ mGestureDetector = new MagnificationGestureDetector(context,
+ context.getMainThreadHandler(), this);
+ }
+
+ private class ZoomSeekbarChangeListener implements SeekBar.OnSeekBarChangeListener {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ float scale = progress * A11Y_CHANGE_SCALE_DIFFERENCE + A11Y_SCALE_MIN_VALUE;
+ Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale,
+ UserHandle.USER_CURRENT);
+ mCallback.onMagnifierScale(scale);
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ // Do nothing
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ // Do nothing
+ }
+ }
+
+ private CharSequence formatContentDescription(int viewId) {
+ if (viewId == R.id.magnifier_small_button) {
+ return mContext.getResources().getString(
+ R.string.accessibility_magnification_small);
+ } else if (viewId == R.id.magnifier_medium_button) {
+ return mContext.getResources().getString(
+ R.string.accessibility_magnification_medium);
+ } else if (viewId == R.id.magnifier_large_button) {
+ return mContext.getResources().getString(
+ R.string.accessibility_magnification_large);
+ } else if (viewId == R.id.magnifier_close_button) {
+ return mContext.getResources().getString(
+ R.string.accessibility_magnification_close);
+ } else if (viewId == R.id.magnifier_edit_button) {
+ return mContext.getResources().getString(
+ R.string.accessibility_resize);
+ } else {
+ return mContext.getResources().getString(
+ R.string.magnification_mode_switch_description);
+ }
+ }
+
+ private void applyResourcesValues() {
+ final int padding = mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnification_switch_button_padding);
+
+ PorterDuffColorFilter filter = new PorterDuffColorFilter(mContext.getColor(
+ R.color.accessibility_magnifier_icon_color), PorterDuff.Mode.SRC_ATOP);
+
+ mSmallButton.setImageResource(R.drawable.ic_magnification_menu_small);
+ mSmallButton.setColorFilter(filter);
+ mSmallButton.setBackgroundResource(
+ R.drawable.accessibility_magnification_setting_view_btn_bg);
+ mSmallButton.setPadding(padding, padding, padding, padding);
+
+ mMediumButton.setImageResource(R.drawable.ic_magnification_menu_medium);
+ mMediumButton.setColorFilter(filter);
+ mMediumButton.setBackgroundResource(
+ R.drawable.accessibility_magnification_setting_view_btn_bg);
+ mMediumButton.setPadding(padding, padding, padding, padding);
+
+ mLargeButton.setImageResource(R.drawable.ic_magnification_menu_large);
+ mLargeButton.setColorFilter(filter);
+ mLargeButton.setBackgroundResource(
+ R.drawable.accessibility_magnification_setting_view_btn_bg);
+ mLargeButton.setPadding(padding, padding, padding, padding);
+
+ mChangeModeButton.setImageResource(R.drawable.ic_open_in_new_fullscreen);
+ mChangeModeButton.setColorFilter(filter);
+ mChangeModeButton.setBackgroundResource(
+ R.drawable.accessibility_magnification_setting_view_btn_bg);
+ mChangeModeButton.setPadding(padding, padding, padding, padding);
+
+ mCloseButton.setBackgroundResource(
+ R.drawable.accessibility_magnification_setting_view_btn_bg);
+ mEditButton.setBackgroundResource(
+ R.drawable.accessibility_magnification_setting_view_btn_bg);
+ }
+
+ private final AccessibilityDelegate mButtonDelegate = new AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+
+ info.setContentDescription(formatContentDescription(host.getId()));
+ final AccessibilityAction clickAction = new AccessibilityAction(
+ AccessibilityAction.ACTION_CLICK.getId(), mContext.getResources().getString(
+ R.string.magnification_mode_switch_click_label));
+ info.addAction(clickAction);
+ info.setClickable(true);
+ info.addAction(new AccessibilityAction(R.id.accessibility_action_move_up,
+ mContext.getString(R.string.accessibility_control_move_up)));
+ info.addAction(new AccessibilityAction(R.id.accessibility_action_move_down,
+ mContext.getString(R.string.accessibility_control_move_down)));
+ info.addAction(new AccessibilityAction(R.id.accessibility_action_move_left,
+ mContext.getString(R.string.accessibility_control_move_left)));
+ info.addAction(new AccessibilityAction(R.id.accessibility_action_move_right,
+ mContext.getString(R.string.accessibility_control_move_right)));
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (performA11yAction(host, action)) {
+ return true;
+ }
+ return super.performAccessibilityAction(host, action, args);
+ }
+
+ private boolean performA11yAction(View view, int action) {
+ final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+ if (action == AccessibilityAction.ACTION_CLICK.getId()) {
+ handleSingleTap(view);
+ } else if (action == R.id.accessibility_action_move_up) {
+ moveButton(0, -windowBounds.height());
+ } else if (action == R.id.accessibility_action_move_down) {
+ moveButton(0, windowBounds.height());
+ } else if (action == R.id.accessibility_action_move_left) {
+ moveButton(-windowBounds.width(), 0);
+ } else if (action == R.id.accessibility_action_move_right) {
+ moveButton(windowBounds.width(), 0);
+ } else {
+ return false;
+ }
+ return true;
+ }
+ };
+
+ private void applyResourcesValuesWithDensityChanged() {
+ if (mIsVisible) {
+ // Reset button to make its window layer always above the mirror window.
+ hideSettingPanel();
+ showSettingPanel(false);
+ }
+ }
+
+ private boolean onTouch(View v, MotionEvent event) {
+ if (!mIsVisible) {
+ return false;
+ }
+ return mGestureDetector.onTouch(v, event);
+ }
+
+ private View.OnClickListener mButtonClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ int id = view.getId();
+ if (id == R.id.magnifier_small_button) {
+ setMagnifierSize(MagnificationSize.SMALL);
+ } else if (id == R.id.magnifier_medium_button) {
+ setMagnifierSize(MagnificationSize.MEDIUM);
+ } else if (id == R.id.magnifier_large_button) {
+ setMagnifierSize(MagnificationSize.LARGE);
+ } else if (id == R.id.magnifier_edit_button) {
+ editMagnifierSizeMode(true);
+ } else if (id == R.id.magnifier_close_button) {
+ hideSettingPanel();
+ } else if (id == R.id.magnifier_full_button) {
+ hideSettingPanel();
+ toggleMagnificationMode();
+ } else {
+ hideSettingPanel();
+ }
+ }
+ };
+
+ @Override
+ public boolean onSingleTap(View view) {
+ mSingleTapDetected = true;
+ handleSingleTap(view);
+ return true;
+ }
+
+ @Override
+ public boolean onDrag(View v, float offsetX, float offsetY) {
+ moveButton(offsetX, offsetY);
+ return true;
+ }
+
+ @Override
+ public boolean onStart(float x, float y) {
+ return true;
+ }
+
+ @Override
+ public boolean onFinish(float xOffset, float yOffset) {
+ if (!mSingleTapDetected) {
+ showSettingPanel();
+ }
+ mSingleTapDetected = false;
+ return true;
+ }
+
+ @VisibleForTesting
+ public ViewGroup getSettingView() {
+ return mSettingView;
+ }
+
+ private void moveButton(float offsetX, float offsetY) {
+ mSfVsyncFrameProvider.postFrameCallback(l -> {
+ mParams.x += offsetX;
+ mParams.y += offsetY;
+ updateButtonViewLayoutIfNeeded();
+ });
+ }
+
+ public void hideSettingPanel() {
+ if (!mIsVisible) {
+ return;
+ }
+
+ // Reset button status.
+ mWindowManager.removeView(mSettingView);
+ mIsVisible = false;
+ mParams.x = 0;
+ mParams.y = 0;
+
+ mContext.unregisterReceiver(mScreenOffReceiver);
+ }
+
+ public void showSettingPanel() {
+ showSettingPanel(true);
+ }
+
+ public void setScaleSeekbar(float scale) {
+ setSeekbarProgress(scale);
+ }
+
+ private void toggleMagnificationMode() {
+ mCallback.onModeSwitch(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+ }
+
+ /**
+ * Shows magnification panel for set window magnification.
+ * When the panel is going to be visible by calling this method, the layout position can be
+ * reset depending on the flag.
+ *
+ * @param resetPosition if the button position needs be reset
+ */
+ private void showSettingPanel(boolean resetPosition) {
+ if (!mIsVisible) {
+ if (resetPosition) {
+ mDraggableWindowBounds.set(getDraggableWindowBounds());
+ mParams.x = mDraggableWindowBounds.right;
+ mParams.y = mDraggableWindowBounds.bottom;
+ }
+
+ mWindowManager.addView(mSettingView, mParams);
+
+ // Exclude magnification switch button from system gesture area.
+ setSystemGestureExclusion();
+ mIsVisible = true;
+ }
+ mContext.registerReceiver(mScreenOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
+ }
+
+ private final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ hideSettingPanel();
+ }
+ };
+
+ private void setSeekbarProgress(float scale) {
+ int index = (int) ((scale - A11Y_SCALE_MIN_VALUE) / A11Y_CHANGE_SCALE_DIFFERENCE);
+ if (index < 0) {
+ index = 0;
+ }
+ mZoomSeekbar.setProgress(index);
+ }
+
+ void onConfigurationChanged(int configDiff) {
+ if ((configDiff & ActivityInfo.CONFIG_UI_MODE) != 0) {
+ applyResourcesValues();
+ return;
+ }
+ if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
+ final Rect previousDraggableBounds = new Rect(mDraggableWindowBounds);
+ mDraggableWindowBounds.set(getDraggableWindowBounds());
+ // Keep the Y position with the same height ratio before the window bounds and
+ // draggable bounds are changed.
+ final float windowHeightFraction = (float) (mParams.y - previousDraggableBounds.top)
+ / previousDraggableBounds.height();
+ mParams.y = (int) (windowHeightFraction * mDraggableWindowBounds.height())
+ + mDraggableWindowBounds.top;
+ return;
+ }
+ if ((configDiff & ActivityInfo.CONFIG_DENSITY) != 0) {
+ applyResourcesValuesWithDensityChanged();
+ return;
+ }
+ if ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0) {
+ updateAccessibilityWindowTitle();
+ return;
+ }
+ }
+
+ private void onWindowInsetChanged() {
+ final Rect newBounds = getDraggableWindowBounds();
+ if (mDraggableWindowBounds.equals(newBounds)) {
+ return;
+ }
+ mDraggableWindowBounds.set(newBounds);
+ }
+
+ private void updateButtonViewLayoutIfNeeded() {
+ if (mIsVisible) {
+ mParams.x = MathUtils.constrain(mParams.x, mDraggableWindowBounds.left,
+ mDraggableWindowBounds.right);
+ mParams.y = MathUtils.constrain(mParams.y, mDraggableWindowBounds.top,
+ mDraggableWindowBounds.bottom);
+ mWindowManager.updateViewLayout(mSettingView, mParams);
+ }
+ }
+
+ private void updateAccessibilityWindowTitle() {
+ mParams.accessibilityTitle = getAccessibilityWindowTitle(mContext);
+ if (mIsVisible) {
+ mWindowManager.updateViewLayout(mSettingView, mParams);
+ }
+ }
+
+ private void handleSingleTap(View view) {
+ int id = view.getId();
+ if (id == R.id.magnifier_small_button) {
+ setMagnifierSize(MagnificationSize.SMALL);
+ } else if (id == R.id.magnifier_medium_button) {
+ setMagnifierSize(MagnificationSize.MEDIUM);
+ } else if (id == R.id.magnifier_large_button) {
+ setMagnifierSize(MagnificationSize.LARGE);
+ } else if (id == R.id.magnifier_full_button) {
+ hideSettingPanel();
+ toggleMagnificationMode();
+ } else {
+ hideSettingPanel();
+ }
+ }
+
+ public void editMagnifierSizeMode(boolean enable) {
+ setEditMagnifierSizeMode(enable);
+ hideSettingPanel();
+ }
+
+ private void setMagnifierSize(@MagnificationSize int index) {
+ mCallback.onSetMagnifierSize(index);
+ }
+
+ private void toggleDiagonalScrolling() {
+ boolean enabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING, 0,
+ UserHandle.USER_CURRENT) == 1;
+
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING, enabled ? 0 : 1,
+ UserHandle.USER_CURRENT);
+
+ mCallback.onSetDiagonalScrolling(!enabled);
+ }
+
+ private void setEditMagnifierSizeMode(boolean enable) {
+ mCallback.onEditMagnifierSizeMode(enable);
+ }
+
+ private static LayoutParams createLayoutParams(Context context) {
+ final LayoutParams params = new LayoutParams(
+ LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT,
+ LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY,
+ LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSPARENT);
+ params.gravity = Gravity.TOP | Gravity.START;
+ params.accessibilityTitle = getAccessibilityWindowTitle(context);
+ params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ return params;
+ }
+
+ private Rect getDraggableWindowBounds() {
+ final int layoutMargin = mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnification_switch_button_margin);
+ final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
+ final Insets windowInsets = windowMetrics.getWindowInsets().getInsetsIgnoringVisibility(
+ WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
+ final Rect boundRect = new Rect(windowMetrics.getBounds());
+ boundRect.offsetTo(0, 0);
+ boundRect.inset(0, 0, mParams.width, mParams.height);
+ boundRect.inset(windowInsets);
+ boundRect.inset(layoutMargin, layoutMargin);
+
+ return boundRect;
+ }
+
+ private static String getAccessibilityWindowTitle(Context context) {
+ return context.getString(com.android.internal.R.string.android_system_label);
+ }
+
+ private void setSystemGestureExclusion() {
+ mSettingView.post(() -> {
+ mSettingView.setSystemGestureExclusionRects(
+ Collections.singletonList(
+ new Rect(0, 0, mSettingView.getWidth(), mSettingView.getHeight())));
+ });
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettingsCallback.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettingsCallback.java
new file mode 100644
index 0000000..22ec650
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettingsCallback.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 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.accessibility;
+
+import static com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize;
+
+/**
+ * A callback to inform WindowMagnificationController about
+ * the setting value change or the user interaction.
+ */
+public interface WindowMagnificationSettingsCallback {
+
+ /**
+ * Called when change magnification size.
+ *
+ * @param index Magnification size index.
+ * 0 : MagnificationSize.NONE, 1 : MagnificationSize.SMALL,
+ * 2 : MagnificationSize.MEDIUM, 3: MagnificationSize.LARGE
+ */
+ void onSetMagnifierSize(@MagnificationSize int index);
+
+ /**
+ * Called when set allow diagonal scrolling.
+ *
+ * @param enable Allow diagonal scrolling enable value.
+ */
+ void onSetDiagonalScrolling(boolean enable);
+
+ /**
+ * Called when change magnification size on free mode.
+ *
+ * @param enable Free mode enable value.
+ */
+ void onEditMagnifierSizeMode(boolean enable);
+
+ /**
+ * Called when set magnification scale.
+ *
+ * @param scale Magnification scale value.
+ */
+ void onMagnifierScale(float scale);
+
+ /**
+ * Called when magnification mode changed.
+ *
+ * @param newMode Magnification mode
+ * 1 : ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, 2 : ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
+ */
+ void onModeSwitch(int newMode);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
index c334ca6..19caaf4 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
@@ -60,4 +60,12 @@
* @param displayId The logical display id.
*/
void onMove(int displayId);
+
+ /**
+ * Called when magnification mode changed.
+ *
+ * @param displayId The logical display id.
+ * @param newMode Magnification mode.
+ */
+ void onModeSwitch(int displayId, int newMode);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
index 55611f7..e60d4e1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
@@ -18,7 +18,7 @@
import android.content.Context
import android.graphics.drawable.Drawable
import android.util.Log
-import android.widget.ImageView
+import com.airbnb.lottie.LottieAnimationView
import com.android.systemui.R
import com.android.systemui.biometrics.AuthBiometricView.BiometricState
import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
@@ -33,8 +33,8 @@
/** Face only icon animator for BiometricPrompt. */
class AuthBiometricFaceIconController(
- context: Context,
- iconView: ImageView
+ context: Context,
+ iconView: LottieAnimationView
) : AuthIconController(context, iconView) {
// false = dark to light, true = light to dark
@@ -76,44 +76,44 @@
if (newState == STATE_AUTHENTICATING_ANIMATING_IN) {
showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light)
iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_authenticating
+ R.string.biometric_dialog_face_icon_description_authenticating
)
} else if (newState == STATE_AUTHENTICATING) {
startPulsing()
iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_authenticating
+ R.string.biometric_dialog_face_icon_description_authenticating
)
} else if (oldState == STATE_PENDING_CONFIRMATION && newState == STATE_AUTHENTICATED) {
animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_confirmed
+ R.string.biometric_dialog_face_icon_description_confirmed
)
} else if (lastStateIsErrorIcon && newState == STATE_IDLE) {
animateIconOnce(R.drawable.face_dialog_error_to_idle)
iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_idle
+ R.string.biometric_dialog_face_icon_description_idle
)
} else if (lastStateIsErrorIcon && newState == STATE_AUTHENTICATED) {
animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_authenticated
+ R.string.biometric_dialog_face_icon_description_authenticated
)
} else if (newState == STATE_ERROR && oldState != STATE_ERROR) {
animateIconOnce(R.drawable.face_dialog_dark_to_error)
} else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_authenticated
+ R.string.biometric_dialog_face_icon_description_authenticated
)
} else if (newState == STATE_PENDING_CONFIRMATION) {
animateIconOnce(R.drawable.face_dialog_wink_from_dark)
iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_authenticated
+ R.string.biometric_dialog_face_icon_description_authenticated
)
} else if (newState == STATE_IDLE) {
showStaticDrawable(R.drawable.face_dialog_idle_static)
iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_idle
+ R.string.biometric_dialog_face_icon_description_idle
)
} else {
Log.w(TAG, "Unhandled state: $newState")
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
index 3e4e573..40d1eff 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
@@ -16,42 +16,43 @@
package com.android.systemui.biometrics
+import android.annotation.RawRes
import android.content.Context
-import android.graphics.drawable.Drawable
-import android.widget.ImageView
+import com.airbnb.lottie.LottieAnimationView
import com.android.systemui.R
import com.android.systemui.biometrics.AuthBiometricView.BiometricState
-import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR
import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
+import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
/** Face/Fingerprint combined icon animator for BiometricPrompt. */
class AuthBiometricFingerprintAndFaceIconController(
- context: Context,
- iconView: ImageView
+ context: Context,
+ iconView: LottieAnimationView
) : AuthBiometricFingerprintIconController(context, iconView) {
override val actsAsConfirmButton: Boolean = true
override fun shouldAnimateForTransition(
- @BiometricState oldState: Int,
- @BiometricState newState: Int
+ @BiometricState oldState: Int,
+ @BiometricState newState: Int
): Boolean = when (newState) {
STATE_PENDING_CONFIRMATION -> true
STATE_AUTHENTICATED -> false
else -> super.shouldAnimateForTransition(oldState, newState)
}
+ @RawRes
override fun getAnimationForTransition(
- @BiometricState oldState: Int,
- @BiometricState newState: Int
- ): Drawable? = when (newState) {
+ @BiometricState oldState: Int,
+ @BiometricState newState: Int
+ ): Int? = when (newState) {
STATE_PENDING_CONFIRMATION -> {
if (oldState == STATE_ERROR || oldState == STATE_HELP) {
- context.getDrawable(R.drawable.fingerprint_dialog_error_to_unlock)
+ R.raw.fingerprint_dialogue_error_to_unlock_lottie
} else {
- context.getDrawable(R.drawable.fingerprint_dialog_fp_to_unlock)
+ R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
}
}
STATE_AUTHENTICATED -> null
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index 606a73a..589ec0e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -16,10 +16,9 @@
package com.android.systemui.biometrics
+import android.annotation.RawRes
import android.content.Context
-import android.graphics.drawable.AnimatedVectorDrawable
-import android.graphics.drawable.Drawable
-import android.widget.ImageView
+import com.airbnb.lottie.LottieAnimationView
import com.android.systemui.R
import com.android.systemui.biometrics.AuthBiometricView.BiometricState
import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
@@ -32,42 +31,42 @@
/** Fingerprint only icon animator for BiometricPrompt. */
open class AuthBiometricFingerprintIconController(
- context: Context,
- iconView: ImageView
+ context: Context,
+ iconView: LottieAnimationView
) : AuthIconController(context, iconView) {
- var iconLayoutParamsSize = 0
+ var iconLayoutParamSize: Pair<Int, Int> = Pair(1, 1)
set(value) {
if (field == value) {
return
}
- iconView.layoutParams.width = value
- iconView.layoutParams.height = value
+ iconView.layoutParams.width = value.first
+ iconView.layoutParams.height = value.second
field = value
}
init {
- iconLayoutParamsSize = context.resources.getDimensionPixelSize(
- R.dimen.biometric_dialog_fingerprint_icon_size
- )
+ iconLayoutParamSize = Pair(context.resources.getDimensionPixelSize(
+ R.dimen.biometric_dialog_fingerprint_icon_width),
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_dialog_fingerprint_icon_height))
}
override fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) {
val icon = getAnimationForTransition(lastState, newState) ?: return
- iconView.setImageDrawable(icon)
+ if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) {
+ iconView.setAnimation(icon)
+ }
val iconContentDescription = getIconContentDescription(newState)
if (iconContentDescription != null) {
iconView.contentDescription = iconContentDescription
}
- (icon as? AnimatedVectorDrawable)?.apply {
- reset()
- if (shouldAnimateForTransition(lastState, newState)) {
- forceAnimationOnUI()
- start()
- }
+ iconView.frame = 0
+ if (shouldAnimateForTransition(lastState, newState)) {
+ iconView.playAnimation()
}
}
@@ -86,8 +85,8 @@
}
protected open fun shouldAnimateForTransition(
- @BiometricState oldState: Int,
- @BiometricState newState: Int
+ @BiometricState oldState: Int,
+ @BiometricState newState: Int
) = when (newState) {
STATE_HELP,
STATE_ERROR -> true
@@ -97,24 +96,27 @@
else -> false
}
+ @RawRes
protected open fun getAnimationForTransition(
- @BiometricState oldState: Int,
- @BiometricState newState: Int
- ): Drawable? {
+ @BiometricState oldState: Int,
+ @BiometricState newState: Int
+ ): Int? {
val id = when (newState) {
STATE_HELP,
- STATE_ERROR -> R.drawable.fingerprint_dialog_fp_to_error
+ STATE_ERROR -> {
+ R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+ }
STATE_AUTHENTICATING_ANIMATING_IN,
STATE_AUTHENTICATING -> {
if (oldState == STATE_ERROR || oldState == STATE_HELP) {
- R.drawable.fingerprint_dialog_error_to_fp
+ R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
} else {
- R.drawable.fingerprint_dialog_fp_to_error
+ R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
}
}
- STATE_AUTHENTICATED -> R.drawable.fingerprint_dialog_fp_to_error
+ STATE_AUTHENTICATED -> R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
else -> return null
}
- return if (id != null) context.getDrawable(id) else null
+ return if (id != null) return id else null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
index 24046f0..31baa0f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
@@ -90,8 +90,9 @@
fun updateOverrideIconLayoutParamsSize() {
udfpsAdapter?.let {
- (mIconController as? AuthBiometricFingerprintIconController)?.iconLayoutParamsSize =
- it.getSensorDiameter(scaleFactorProvider?.provide() ?: 1.0f)
+ val sensorDiameter = it.getSensorDiameter(scaleFactorProvider?.provide() ?: 1.0f)
+ (mIconController as? AuthBiometricFingerprintIconController)?.iconLayoutParamSize =
+ Pair(sensorDiameter, sensorDiameter)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
index ce5e600..15f487b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
@@ -22,15 +22,15 @@
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
import android.util.Log
-import android.widget.ImageView
+import com.airbnb.lottie.LottieAnimationView
import com.android.systemui.biometrics.AuthBiometricView.BiometricState
private const val TAG = "AuthIconController"
/** Controller for animating the BiometricPrompt icon/affordance. */
abstract class AuthIconController(
- protected val context: Context,
- protected val iconView: ImageView
+ protected val context: Context,
+ protected val iconView: LottieAnimationView
) : Animatable2.AnimationCallback() {
/** If this controller should ignore events and pause. */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index d7ae9ef..e866b9c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -41,14 +41,14 @@
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
import android.widget.Button;
-import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.R;
-import com.android.systemui.util.LargeScreenUtils;
+
+import com.airbnb.lottie.LottieAnimationView;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -133,7 +133,7 @@
private TextView mSubtitleView;
private TextView mDescriptionView;
private View mIconHolderView;
- protected ImageView mIconView;
+ protected LottieAnimationView mIconView;
protected TextView mIndicatorView;
@VisibleForTesting @NonNull AuthIconController mIconController;
@@ -824,25 +824,12 @@
return new AuthDialog.LayoutParams(width, totalHeight);
}
- private boolean isLargeDisplay() {
- return LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
- }
-
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int height = MeasureSpec.getSize(heightMeasureSpec);
- final boolean isLargeDisplay = isLargeDisplay();
-
- final int newWidth;
- if (isLargeDisplay) {
- // TODO(b/201811580): Unless we can come up with a one-size-fits-all equation, we may
- // want to consider moving this to an overlay.
- newWidth = 2 * Math.min(width, height) / 3;
- } else {
- newWidth = Math.min(width, height);
- }
+ final int newWidth = Math.min(width, height);
// Use "newWidth" instead, so the landscape dialog width is the same as the portrait
// width.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 282f251..47ff59c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -46,7 +46,6 @@
import android.hardware.display.DisplayManager;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorPropertiesInternal;
-import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
@@ -157,6 +156,25 @@
}
};
+ private final IFingerprintAuthenticatorsRegisteredCallback
+ mFingerprintAuthenticatorsRegisteredCallback =
+ new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+ @Override
+ public void onAllAuthenticatorsRegistered(
+ List<FingerprintSensorPropertiesInternal> sensors) {
+ mHandler.post(() -> handleAllFingerprintAuthenticatorsRegistered(sensors));
+ }
+ };
+
+ private final BiometricStateListener mBiometricStateListener =
+ new BiometricStateListener() {
+ @Override
+ public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
+ mHandler.post(
+ () -> handleEnrollmentsChanged(userId, sensorId, hasEnrollments));
+ }
+ };
+
@VisibleForTesting
final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
@@ -231,8 +249,8 @@
List<FingerprintSensorPropertiesInternal> sensors) {
mExecution.assertIsMainThread();
if (DEBUG) {
- Log.d(TAG, "handleAllFingerprintAuthenticatorsRegistered | sensors: "
- + Arrays.toString(sensors.toArray()));
+ Log.d(TAG, "handleAllAuthenticatorsRegistered | sensors: " + Arrays.toString(
+ sensors.toArray()));
}
mAllFingerprintAuthenticatorsRegistered = true;
mFpProps = sensors;
@@ -274,42 +292,15 @@
mSidefpsController = mSidefpsControllerFactory.get();
}
- mFingerprintManager.registerBiometricStateListener(new BiometricStateListener() {
- @Override
- public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
- mHandler.post(() -> handleEnrollmentsChanged(
- TYPE_FINGERPRINT, userId, sensorId, hasEnrollments));
- }
- });
+ mFingerprintManager.registerBiometricStateListener(mBiometricStateListener);
updateFingerprintLocation();
for (Callback cb : mCallbacks) {
- cb.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT);
+ cb.onAllAuthenticatorsRegistered();
}
}
- private void handleAllFaceAuthenticatorsRegistered(List<FaceSensorPropertiesInternal> sensors) {
- mExecution.assertIsMainThread();
- if (DEBUG) {
- Log.d(TAG, "handleAllFaceAuthenticatorsRegistered | sensors: " + Arrays.toString(
- sensors.toArray()));
- }
-
- mFaceManager.registerBiometricStateListener(new BiometricStateListener() {
- @Override
- public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
- mHandler.post(() -> handleEnrollmentsChanged(
- TYPE_FACE, userId, sensorId, hasEnrollments));
- }
- });
-
- for (Callback cb : mCallbacks) {
- cb.onAllAuthenticatorsRegistered(TYPE_FACE);
- }
- }
-
- private void handleEnrollmentsChanged(@Modality int modality, int userId, int sensorId,
- boolean hasEnrollments) {
+ private void handleEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
mExecution.assertIsMainThread();
Log.d(TAG, "handleEnrollmentsChanged, userId: " + userId + ", sensorId: " + sensorId
+ ", hasEnrollments: " + hasEnrollments);
@@ -323,7 +314,7 @@
}
}
for (Callback cb : mCallbacks) {
- cb.onEnrollmentsChanged(modality);
+ cb.onEnrollmentsChanged();
}
}
@@ -709,26 +700,7 @@
if (mFingerprintManager != null) {
mFingerprintManager.addAuthenticatorsRegisteredCallback(
- new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
- @Override
- public void onAllAuthenticatorsRegistered(
- List<FingerprintSensorPropertiesInternal> sensors) {
- mHandler.post(() ->
- handleAllFingerprintAuthenticatorsRegistered(sensors));
- }
- });
- }
- if (mFaceManager != null) {
- mFaceManager.addAuthenticatorsRegisteredCallback(
- new IFaceAuthenticatorsRegisteredCallback.Stub() {
- @Override
- public void onAllAuthenticatorsRegistered(
- List<FaceSensorPropertiesInternal> sensors) {
- mHandler.post(() ->
- handleAllFaceAuthenticatorsRegistered(sensors));
- }
- }
- );
+ mFingerprintAuthenticatorsRegisteredCallback);
}
mStableDisplaySize = mDisplayManager.getStableDisplaySize();
@@ -1144,13 +1116,13 @@
* Called when authenticators are registered. If authenticators are already
* registered before this call, this callback will never be triggered.
*/
- default void onAllAuthenticatorsRegistered(@Modality int modality) {}
+ default void onAllAuthenticatorsRegistered() {}
/**
- * Called when enrollments have changed. This is called after boot and on changes to
+ * Called when UDFPS enrollments have changed. This is called after boot and on changes to
* enrollment.
*/
- default void onEnrollmentsChanged(@Modality int modality) {}
+ default void onEnrollmentsChanged() {}
/**
* Called when the biometric prompt starts showing.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index fd3f600..38fab8f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -308,7 +308,7 @@
private val authControllerCallback =
object : AuthController.Callback {
- override fun onAllAuthenticatorsRegistered(modality: Int) {
+ override fun onAllAuthenticatorsRegistered() {
updateUdfpsDependentParams()
updateSensorLocation()
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt b/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt
index 6f3beac..a0b19dc 100644
--- a/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt
@@ -35,7 +35,7 @@
* " - downstream canceled or failed.",
* it,
* )
- *}
+ * }
* ```
*/
fun <T> SendChannel<T>.trySendWithFailureLogging(
diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/model/Position.kt b/packages/SystemUI/src/com/android/systemui/common/domain/model/Position.kt
deleted file mode 100644
index f697c0a..0000000
--- a/packages/SystemUI/src/com/android/systemui/common/domain/model/Position.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2022 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.common.domain.model
-
-import com.android.systemui.common.data.model.Position as DataLayerPosition
-
-/** Models a two-dimensional position */
-data class Position(
- val x: Int,
- val y: Int,
-) {
- companion object {
- fun DataLayerPosition.toDomainLayer(): Position {
- return Position(
- x = x,
- y = y,
- )
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/Position.kt
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
rename to packages/SystemUI/src/com/android/systemui/common/shared/model/Position.kt
index 7c9df10..52f6167 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/Position.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.common.data.model
+package com.android.systemui.common.shared.model
/** Models a two-dimensional position */
data class Position(
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index 7da2cf1..a9e310d 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -16,15 +16,12 @@
package com.android.systemui.doze;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
-
import static com.android.systemui.doze.DozeMachine.State.DOZE;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING;
import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE;
-import android.hardware.biometrics.BiometricAuthenticator;
import android.os.Handler;
import android.util.Log;
import android.view.Display;
@@ -235,17 +232,13 @@
private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
@Override
- public void onAllAuthenticatorsRegistered(@BiometricAuthenticator.Modality int modality) {
- if (modality == TYPE_FINGERPRINT) {
- updateUdfpsController();
- }
+ public void onAllAuthenticatorsRegistered() {
+ updateUdfpsController();
}
@Override
- public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) {
- if (modality == TYPE_FINGERPRINT) {
- updateUdfpsController();
- }
+ public void onEnrollmentsChanged() {
+ updateUdfpsController();
}
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 997a6e5..da6c163 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -16,8 +16,6 @@
package com.android.systemui.doze;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
-
import static com.android.systemui.doze.DozeLog.REASON_SENSOR_QUICK_PICKUP;
import static com.android.systemui.doze.DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS;
import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_DISPLAY;
@@ -31,7 +29,6 @@
import android.hardware.SensorManager;
import android.hardware.TriggerEvent;
import android.hardware.TriggerEventListener;
-import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.display.AmbientDisplayConfiguration;
import android.net.Uri;
import android.os.Handler;
@@ -838,17 +835,13 @@
private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
@Override
- public void onAllAuthenticatorsRegistered(@BiometricAuthenticator.Modality int modality) {
- if (modality == TYPE_FINGERPRINT) {
- updateUdfpsEnrolled();
- }
+ public void onAllAuthenticatorsRegistered() {
+ updateUdfpsEnrolled();
}
@Override
- public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) {
- if (modality == TYPE_FINGERPRINT) {
- updateUdfpsEnrolled();
- }
+ public void onEnrollmentsChanged() {
+ updateUdfpsEnrolled();
}
private void updateUdfpsEnrolled() {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 6eb77bd..b29176b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -92,7 +92,7 @@
* Whether the KeyguardBottomArea(View|Controller) should use the modern architecture or the old
* one.
*/
- public static final UnreleasedFlag MODERN_BOTTOM_AREA = new UnreleasedFlag(206, true);
+ public static final ReleasedFlag MODERN_BOTTOM_AREA = new ReleasedFlag(206, true);
public static final UnreleasedFlag LOCKSCREEN_CUSTOM_CLOCKS = new UnreleasedFlag(207);
@@ -237,6 +237,9 @@
public static final UnreleasedFlag SCREENSHOT_REQUEST_PROCESSOR = new UnreleasedFlag(1300);
+ // 1400 - columbus, b/242800729
+ public static final UnreleasedFlag QUICK_TAP_IN_PCC = new UnreleasedFlag(1400);
+
// Pay no attention to the reflection behind the curtain.
// ========================== Curtain ==========================
// | |
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 430b59c..56f1ac4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -44,7 +44,6 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
-import com.android.systemui.keyguard.domain.usecase.KeyguardUseCaseModule;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -73,7 +72,6 @@
FalsingModule.class,
KeyguardQuickAffordanceModule.class,
KeyguardRepositoryModule.class,
- KeyguardUseCaseModule.class,
})
public class KeyguardModule {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 62cf1a6..e52d9ee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -18,7 +18,7 @@
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.common.data.model.Position
+import com.android.systemui.common.shared.model.Position
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
new file mode 100644
index 0000000..ede50b0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 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.keyguard.domain.interactor
+
+import com.android.systemui.common.shared.model.Position
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Encapsulates business-logic specifically related to the keyguard bottom area. */
+@SysUISingleton
+class KeyguardBottomAreaInteractor
+@Inject
+constructor(
+ private val repository: KeyguardRepository,
+) {
+ /** Whether to animate the next doze mode transition. */
+ val animateDozingTransitions: Flow<Boolean> = repository.animateBottomAreaDozingTransitions
+ /** The amount of alpha for the UI components of the bottom area. */
+ val alpha: Flow<Float> = repository.bottomAreaAlpha
+ /** The position of the keyguard clock. */
+ val clockPosition: Flow<Position> = repository.clockPosition
+
+ fun setClockPosition(x: Int, y: Int) {
+ repository.setClockPosition(x, y)
+ }
+
+ fun setAlpha(alpha: Float) {
+ repository.setBottomAreaAlpha(alpha)
+ }
+
+ fun setAnimateDozingTransitions(animate: Boolean) {
+ repository.setAnimateDozingTransitions(animate)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
new file mode 100644
index 0000000..dccc941
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 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.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Encapsulates business-logic related to the keyguard but not to a more specific part within it.
+ */
+@SysUISingleton
+class KeyguardInteractor
+@Inject
+constructor(
+ repository: KeyguardRepository,
+) {
+ /**
+ * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
+ * all.
+ */
+ val dozeAmount: Flow<Float> = repository.dozeAmount
+ /** Whether the system is in doze mode. */
+ val isDozing: Flow<Boolean> = repository.isDozing
+ /** Whether the keyguard is showing ot not. */
+ val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
new file mode 100644
index 0000000..9a69e26
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2022 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.keyguard.domain.interactor
+
+import android.content.Intent
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import javax.inject.Inject
+import kotlin.reflect.KClass
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+@SysUISingleton
+class KeyguardQuickAffordanceInteractor
+@Inject
+constructor(
+ private val keyguardInteractor: KeyguardInteractor,
+ private val registry: KeyguardQuickAffordanceRegistry<out KeyguardQuickAffordanceConfig>,
+ private val lockPatternUtils: LockPatternUtils,
+ private val keyguardStateController: KeyguardStateController,
+ private val userTracker: UserTracker,
+ private val activityStarter: ActivityStarter,
+) {
+ /** Returns an observable for the quick affordance at the given position. */
+ fun quickAffordance(
+ position: KeyguardQuickAffordancePosition
+ ): Flow<KeyguardQuickAffordanceModel> {
+ return combine(
+ quickAffordanceInternal(position),
+ keyguardInteractor.isDozing,
+ keyguardInteractor.isKeyguardShowing,
+ ) { affordance, isDozing, isKeyguardShowing ->
+ if (!isDozing && isKeyguardShowing) {
+ affordance
+ } else {
+ KeyguardQuickAffordanceModel.Hidden
+ }
+ }
+ }
+
+ /**
+ * Notifies that a quick affordance has been clicked by the user.
+ *
+ * @param configKey The configuration key corresponding to the [KeyguardQuickAffordanceModel] of
+ * the affordance that was clicked
+ * @param animationController An optional controller for the activity-launch animation
+ */
+ fun onQuickAffordanceClicked(
+ configKey: KClass<out KeyguardQuickAffordanceConfig>,
+ animationController: ActivityLaunchAnimator.Controller?,
+ ) {
+ @Suppress("UNCHECKED_CAST") val config = registry.get(configKey as KClass<Nothing>)
+ when (val result = config.onQuickAffordanceClicked(animationController)) {
+ is KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity ->
+ launchQuickAffordance(
+ intent = result.intent,
+ canShowWhileLocked = result.canShowWhileLocked,
+ animationController = animationController
+ )
+ is KeyguardQuickAffordanceConfig.OnClickedResult.Handled -> Unit
+ }
+ }
+
+ private fun quickAffordanceInternal(
+ position: KeyguardQuickAffordancePosition
+ ): Flow<KeyguardQuickAffordanceModel> {
+ val configs = registry.getAll(position)
+ return combine(configs.map { config -> config.state }) { states ->
+ val index = states.indexOfFirst { it is KeyguardQuickAffordanceConfig.State.Visible }
+ if (index != -1) {
+ val visibleState = states[index] as KeyguardQuickAffordanceConfig.State.Visible
+ KeyguardQuickAffordanceModel.Visible(
+ configKey = configs[index]::class,
+ icon = visibleState.icon,
+ contentDescriptionResourceId = visibleState.contentDescriptionResourceId,
+ )
+ } else {
+ KeyguardQuickAffordanceModel.Hidden
+ }
+ }
+ }
+
+ private fun launchQuickAffordance(
+ intent: Intent,
+ canShowWhileLocked: Boolean,
+ animationController: ActivityLaunchAnimator.Controller?,
+ ) {
+ @LockPatternUtils.StrongAuthTracker.StrongAuthFlags
+ val strongAuthFlags =
+ lockPatternUtils.getStrongAuthForUser(userTracker.userHandle.identifier)
+ val needsToUnlockFirst =
+ when {
+ strongAuthFlags ==
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT -> true
+ !canShowWhileLocked && !keyguardStateController.isUnlocked -> true
+ else -> false
+ }
+ if (needsToUnlockFirst) {
+ activityStarter.postStartActivityDismissingKeyguard(
+ intent,
+ 0 /* delay */,
+ animationController
+ )
+ } else {
+ activityStarter.startActivity(
+ intent,
+ true /* dismissShade */,
+ animationController,
+ true /* showOverLockscreenWhenLocked */,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
index 411a2ca..eff1469 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
@@ -42,21 +42,4 @@
*/
@StringRes val contentDescriptionResourceId: Int,
) : KeyguardQuickAffordanceModel()
-
- companion object {
- fun from(
- state: KeyguardQuickAffordanceConfig.State?,
- configKey: KClass<out KeyguardQuickAffordanceConfig>,
- ): KeyguardQuickAffordanceModel {
- return when (state) {
- is KeyguardQuickAffordanceConfig.State.Visible ->
- Visible(
- configKey = configKey,
- icon = state.icon,
- contentDescriptionResourceId = state.contentDescriptionResourceId,
- )
- else -> Hidden
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
index a7b3828..94024d4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
@@ -25,5 +25,5 @@
@Binds
fun keyguardQuickAffordanceRegistry(
impl: KeyguardQuickAffordanceRegistryImpl
- ): KeyguardQuickAffordanceRegistry
+ ): KeyguardQuickAffordanceRegistry<out KeyguardQuickAffordanceConfig>
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
index 2c37f93..ad40ee7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
@@ -22,9 +22,9 @@
import kotlin.reflect.KClass
/** Central registry of all known quick affordance configs. */
-interface KeyguardQuickAffordanceRegistry {
- fun getAll(position: KeyguardQuickAffordancePosition): List<KeyguardQuickAffordanceConfig>
- fun get(configClass: KClass<out KeyguardQuickAffordanceConfig>): KeyguardQuickAffordanceConfig
+interface KeyguardQuickAffordanceRegistry<T : KeyguardQuickAffordanceConfig> {
+ fun getAll(position: KeyguardQuickAffordancePosition): List<T>
+ fun get(configClass: KClass<out T>): T
}
class KeyguardQuickAffordanceRegistryImpl
@@ -33,7 +33,7 @@
homeControls: HomeControlsKeyguardQuickAffordanceConfig,
quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
-) : KeyguardQuickAffordanceRegistry {
+) : KeyguardQuickAffordanceRegistry<KeyguardQuickAffordanceConfig> {
private val configsByPosition =
mapOf(
KeyguardQuickAffordancePosition.BOTTOM_START to
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt
deleted file mode 100644
index 403d343..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2022 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.keyguard.domain.usecase
-
-import dagger.Binds
-import dagger.Module
-
-@Module
-interface KeyguardUseCaseModule {
-
- @Binds
- fun launchQuickAffordance(
- impl: LaunchKeyguardQuickAffordanceUseCaseImpl
- ): LaunchKeyguardQuickAffordanceUseCase
-
- @Binds
- fun observeKeyguardQuickAffordance(
- impl: ObserveKeyguardQuickAffordanceUseCaseImpl
- ): ObserveKeyguardQuickAffordanceUseCase
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCase.kt
deleted file mode 100644
index 3d60399..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCase.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2022 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.keyguard.domain.usecase
-
-import android.content.Intent
-import com.android.internal.widget.LockPatternUtils
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.StrongAuthFlags
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import javax.inject.Inject
-
-/** Defines interface for classes that can launch a quick affordance. */
-interface LaunchKeyguardQuickAffordanceUseCase {
- operator fun invoke(
- intent: Intent,
- canShowWhileLocked: Boolean,
- animationController: ActivityLaunchAnimator.Controller?,
- )
-}
-
-/** Real implementation of [LaunchKeyguardQuickAffordanceUseCase] */
-class LaunchKeyguardQuickAffordanceUseCaseImpl
-@Inject
-constructor(
- private val lockPatternUtils: LockPatternUtils,
- private val keyguardStateController: KeyguardStateController,
- private val userTracker: UserTracker,
- private val activityStarter: ActivityStarter,
-) : LaunchKeyguardQuickAffordanceUseCase {
- override operator fun invoke(
- intent: Intent,
- canShowWhileLocked: Boolean,
- animationController: ActivityLaunchAnimator.Controller?,
- ) {
- @StrongAuthFlags
- val strongAuthFlags =
- lockPatternUtils.getStrongAuthForUser(userTracker.userHandle.identifier)
- val needsToUnlockFirst =
- when {
- strongAuthFlags ==
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT -> true
- !canShowWhileLocked && !keyguardStateController.isUnlocked -> true
- else -> false
- }
- if (needsToUnlockFirst) {
- activityStarter.postStartActivityDismissingKeyguard(
- intent,
- 0 /* delay */,
- animationController
- )
- } else {
- activityStarter.startActivity(
- intent,
- true /* dismissShade */,
- animationController,
- true /* showOverLockscreenWhenLocked */,
- )
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveAnimateBottomAreaTransitionsUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveAnimateBottomAreaTransitionsUseCase.kt
deleted file mode 100644
index ca37727..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveAnimateBottomAreaTransitionsUseCase.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2022 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.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/** Use-case for observing whether doze state transitions should animate the bottom area */
-class ObserveAnimateBottomAreaTransitionsUseCase
-@Inject
-constructor(
- private val repository: KeyguardRepository,
-) {
- operator fun invoke(): Flow<Boolean> {
- return repository.animateBottomAreaDozingTransitions
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveBottomAreaAlphaUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveBottomAreaAlphaUseCase.kt
deleted file mode 100644
index 151b704..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveBottomAreaAlphaUseCase.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2022 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.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/** Use-case for observing the alpha of the bottom area */
-class ObserveBottomAreaAlphaUseCase
-@Inject
-constructor(
- private val repository: KeyguardRepository,
-) {
- operator fun invoke(): Flow<Float> {
- return repository.bottomAreaAlpha
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveClockPositionUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveClockPositionUseCase.kt
deleted file mode 100644
index 02c5737..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveClockPositionUseCase.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2022 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.keyguard.domain.usecase
-
-import com.android.systemui.common.domain.model.Position
-import com.android.systemui.common.domain.model.Position.Companion.toDomainLayer
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
-
-/** Use-case for observing the position of the clock. */
-class ObserveClockPositionUseCase
-@Inject
-constructor(
- private val repository: KeyguardRepository,
-) {
- operator fun invoke(): Flow<Position> {
- return repository.clockPosition.map { it.toDomainLayer() }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveDozeAmountUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveDozeAmountUseCase.kt
deleted file mode 100644
index 56d6182..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveDozeAmountUseCase.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2022 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.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/** Use-case for observing the amount of doze the system is in. */
-class ObserveDozeAmountUseCase
-@Inject
-constructor(
- private val repository: KeyguardRepository,
-) {
- operator fun invoke(): Flow<Float> {
- return repository.dozeAmount
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsDozingUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsDozingUseCase.kt
deleted file mode 100644
index 1d241d9..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsDozingUseCase.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2022 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.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/** Use-case for observing whether we are dozing. */
-class ObserveIsDozingUseCase
-@Inject
-constructor(
- private val repository: KeyguardRepository,
-) {
- operator fun invoke(): Flow<Boolean> {
- return repository.isDozing
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsKeyguardShowingUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsKeyguardShowingUseCase.kt
deleted file mode 100644
index 11af123..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsKeyguardShowingUseCase.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2022 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.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/**
- * Use-case for observing whether the keyguard is currently being shown.
- *
- * Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in the
- * z-order (which is not really above the system UI window, but rather - the lock-screen becomes
- * invisible to reveal the "occluding activity").
- */
-class ObserveIsKeyguardShowingUseCase
-@Inject
-constructor(
- private val repository: KeyguardRepository,
-) {
- operator fun invoke(): Flow<Boolean> {
- return repository.isKeyguardShowing
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt
deleted file mode 100644
index 8dee8b3..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2022 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.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-
-/** Defines interface for use-case for observing the model of a quick affordance in the keyguard. */
-interface ObserveKeyguardQuickAffordanceUseCase {
- operator fun invoke(
- position: KeyguardQuickAffordancePosition
- ): Flow<KeyguardQuickAffordanceModel>
-}
-
-class ObserveKeyguardQuickAffordanceUseCaseImpl
-@Inject
-constructor(
- private val registry: KeyguardQuickAffordanceRegistry,
- private val isDozingUseCase: ObserveIsDozingUseCase,
- private val isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase,
-) : ObserveKeyguardQuickAffordanceUseCase {
- override fun invoke(
- position: KeyguardQuickAffordancePosition
- ): Flow<KeyguardQuickAffordanceModel> {
- return combine(
- affordance(position),
- isDozingUseCase(),
- isKeyguardShowingUseCase(),
- ) { affordance, isDozing, isKeyguardShowing ->
- if (!isDozing && isKeyguardShowing) {
- affordance
- } else {
- KeyguardQuickAffordanceModel.Hidden
- }
- }
- }
-
- private fun affordance(
- position: KeyguardQuickAffordancePosition
- ): Flow<KeyguardQuickAffordanceModel> {
- val configs = registry.getAll(position)
- return combine(configs.map { config -> config.state }) { states ->
- val index =
- states.indexOfFirst { state ->
- state is KeyguardQuickAffordanceConfig.State.Visible
- }
- val visibleState =
- if (index != -1) {
- states[index] as KeyguardQuickAffordanceConfig.State.Visible
- } else {
- null
- }
- KeyguardQuickAffordanceModel.from(visibleState, configs[index]::class)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt
deleted file mode 100644
index 9315339..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2022 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.keyguard.domain.usecase
-
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
-import javax.inject.Inject
-import kotlin.reflect.KClass
-
-/** Use-case for handling a click on a keyguard quick affordance (e.g. bottom button). */
-class OnKeyguardQuickAffordanceClickedUseCase
-@Inject
-constructor(
- private val registry: KeyguardQuickAffordanceRegistry,
- private val launchAffordanceUseCase: LaunchKeyguardQuickAffordanceUseCase,
-) {
- operator fun invoke(
- configKey: KClass<*>,
- animationController: ActivityLaunchAnimator.Controller?,
- ) {
- @Suppress("UNCHECKED_CAST")
- val config = registry.get(configKey as KClass<out KeyguardQuickAffordanceConfig>)
- when (val result = config.onQuickAffordanceClicked(animationController)) {
- is OnClickedResult.StartActivity ->
- launchAffordanceUseCase(
- intent = result.intent,
- canShowWhileLocked = result.canShowWhileLocked,
- animationController = animationController
- )
- is OnClickedResult.Handled -> Unit
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetClockPositionUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetClockPositionUseCase.kt
deleted file mode 100644
index 8f746e5..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetClockPositionUseCase.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2022 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.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-
-/** Use-case for setting the updated clock position. */
-class SetClockPositionUseCase
-@Inject
-constructor(
- private val keyguardRepository: KeyguardRepository,
-) {
- operator fun invoke(x: Int, y: Int) {
- keyguardRepository.setClockPosition(x, y)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAlphaUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAlphaUseCase.kt
deleted file mode 100644
index 90be1ec..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAlphaUseCase.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2022 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.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-
-/** Use-case for setting the alpha that the keyguard bottom area should use */
-class SetKeyguardBottomAreaAlphaUseCase
-@Inject
-constructor(
- private val repository: KeyguardRepository,
-) {
- operator fun invoke(alpha: Float) {
- repository.setBottomAreaAlpha(alpha)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAnimateDozingTransitionsUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAnimateDozingTransitionsUseCase.kt
deleted file mode 100644
index 007780a..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAnimateDozingTransitionsUseCase.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2022 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.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-
-/**
- * Use-case for setting whether the keyguard bottom area should animate the next doze transitions
- */
-class SetKeyguardBottomAreaAnimateDozingTransitionsUseCase
-@Inject
-constructor(
- private val repository: KeyguardRepository,
-) {
- operator fun invoke(animate: Boolean) {
- repository.setAnimateDozingTransitions(animate)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index 04d30bf..19c6249 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -95,35 +95,23 @@
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
- combine(viewModel.startButton, viewModel.animateButtonReveal) {
- buttonModel,
- animateReveal ->
- Pair(buttonModel, animateReveal)
- }
- .collect { (buttonModel, animateReveal) ->
- updateButton(
- view = startButton,
- viewModel = buttonModel,
- animateReveal = animateReveal,
- falsingManager = falsingManager,
- )
- }
+ viewModel.startButton.collect { buttonModel ->
+ updateButton(
+ view = startButton,
+ viewModel = buttonModel,
+ falsingManager = falsingManager,
+ )
+ }
}
launch {
- combine(viewModel.endButton, viewModel.animateButtonReveal) {
- buttonModel,
- animateReveal ->
- Pair(buttonModel, animateReveal)
- }
- .collect { (buttonModel, animateReveal) ->
- updateButton(
- view = endButton,
- viewModel = buttonModel,
- animateReveal = animateReveal,
- falsingManager = falsingManager,
- )
- }
+ viewModel.endButton.collect { buttonModel ->
+ updateButton(
+ view = endButton,
+ viewModel = buttonModel,
+ falsingManager = falsingManager,
+ )
+ }
}
launch {
@@ -226,7 +214,6 @@
private fun updateButton(
view: ImageView,
viewModel: KeyguardQuickAffordanceViewModel,
- animateReveal: Boolean,
falsingManager: FalsingManager,
) {
if (!viewModel.isVisible) {
@@ -236,7 +223,7 @@
if (!view.isVisible) {
view.isVisible = true
- if (animateReveal) {
+ if (viewModel.animateReveal) {
view.alpha = 0f
view.translationY = view.height / 2f
view
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index d296e76..01d5e5c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -17,15 +17,11 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.domain.usecase.ObserveAnimateBottomAreaTransitionsUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveBottomAreaAlphaUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveClockPositionUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveDozeAmountUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveIsDozingUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveKeyguardQuickAffordanceUseCase
-import com.android.systemui.keyguard.domain.usecase.OnKeyguardQuickAffordanceClickedUseCase
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -36,13 +32,9 @@
class KeyguardBottomAreaViewModel
@Inject
constructor(
- private val observeQuickAffordanceUseCase: ObserveKeyguardQuickAffordanceUseCase,
- private val onQuickAffordanceClickedUseCase: OnKeyguardQuickAffordanceClickedUseCase,
- observeBottomAreaAlphaUseCase: ObserveBottomAreaAlphaUseCase,
- observeIsDozingUseCase: ObserveIsDozingUseCase,
- observeAnimateBottomAreaTransitionsUseCase: ObserveAnimateBottomAreaTransitionsUseCase,
- private val observeDozeAmountUseCase: ObserveDozeAmountUseCase,
- observeClockPositionUseCase: ObserveClockPositionUseCase,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor,
+ private val bottomAreaInteractor: KeyguardBottomAreaInteractor,
private val burnInHelperWrapper: BurnInHelperWrapper,
) {
/** An observable for the view-model of the "start button" quick affordance. */
@@ -51,17 +43,11 @@
/** An observable for the view-model of the "end button" quick affordance. */
val endButton: Flow<KeyguardQuickAffordanceViewModel> =
button(KeyguardQuickAffordancePosition.BOTTOM_END)
- /**
- * An observable for whether the next time a quick action button becomes visible, it should
- * animate.
- */
- val animateButtonReveal: Flow<Boolean> =
- observeAnimateBottomAreaTransitionsUseCase().distinctUntilChanged()
/** An observable for whether the overlay container should be visible. */
val isOverlayContainerVisible: Flow<Boolean> =
- observeIsDozingUseCase().map { !it }.distinctUntilChanged()
+ keyguardInteractor.isDozing.map { !it }.distinctUntilChanged()
/** An observable for the alpha level for the entire bottom area. */
- val alpha: Flow<Float> = observeBottomAreaAlphaUseCase().distinctUntilChanged()
+ val alpha: Flow<Float> = bottomAreaInteractor.alpha.distinctUntilChanged()
/** An observable for whether the indication area should be padded. */
val isIndicationAreaPadded: Flow<Boolean> =
combine(startButton, endButton) { startButtonModel, endButtonModel ->
@@ -70,11 +56,11 @@
.distinctUntilChanged()
/** An observable for the x-offset by which the indication area should be translated. */
val indicationAreaTranslationX: Flow<Float> =
- observeClockPositionUseCase().map { it.x.toFloat() }.distinctUntilChanged()
+ bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
/** Returns an observable for the y-offset by which the indication area should be translated. */
fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
- return observeDozeAmountUseCase()
+ return keyguardInteractor.dozeAmount
.map { dozeAmount ->
dozeAmount *
(burnInHelperWrapper.burnInOffset(
@@ -88,21 +74,28 @@
private fun button(
position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceViewModel> {
- return observeQuickAffordanceUseCase(position)
- .map { model -> model.toViewModel() }
+ return combine(
+ quickAffordanceInteractor.quickAffordance(position),
+ bottomAreaInteractor.animateDozingTransitions.distinctUntilChanged(),
+ ) { model, animateReveal ->
+ model.toViewModel(animateReveal)
+ }
.distinctUntilChanged()
}
- private fun KeyguardQuickAffordanceModel.toViewModel(): KeyguardQuickAffordanceViewModel {
+ private fun KeyguardQuickAffordanceModel.toViewModel(
+ animateReveal: Boolean,
+ ): KeyguardQuickAffordanceViewModel {
return when (this) {
is KeyguardQuickAffordanceModel.Visible ->
KeyguardQuickAffordanceViewModel(
configKey = configKey,
isVisible = true,
+ animateReveal = animateReveal,
icon = icon,
contentDescriptionResourceId = contentDescriptionResourceId,
onClicked = { parameters ->
- onQuickAffordanceClickedUseCase(
+ quickAffordanceInteractor.onQuickAffordanceClicked(
configKey = parameters.configKey,
animationController = parameters.animationController,
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
index 2417998..985ab62 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
@@ -19,18 +19,21 @@
import androidx.annotation.StringRes
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
import kotlin.reflect.KClass
/** Models the UI state of a keyguard quick affordance button. */
data class KeyguardQuickAffordanceViewModel(
- val configKey: KClass<*>? = null,
+ val configKey: KClass<out KeyguardQuickAffordanceConfig>? = null,
val isVisible: Boolean = false,
+ /** Whether to animate the transition of the quick affordance from invisible to visible. */
+ val animateReveal: Boolean = false,
val icon: ContainedDrawable = ContainedDrawable.WithResource(0),
@StringRes val contentDescriptionResourceId: Int = 0,
val onClicked: (OnClickedParameters) -> Unit = {},
) {
data class OnClickedParameters(
- val configKey: KClass<*>,
+ val configKey: KClass<out KeyguardQuickAffordanceConfig>,
val animationController: ActivityLaunchAnimator.Controller?,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
index 8757904..00b0ff9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -68,7 +68,7 @@
.addFeature("feature")
val useAppIcon = !(args.size >= 3 && args[2] == "useAppIcon=false")
if (useAppIcon) {
- routeInfo.setPackageName(TEST_PACKAGE_NAME)
+ routeInfo.setClientPackageName(TEST_PACKAGE_NAME)
}
statusBarManager.updateMediaTapToTransferSenderDisplay(
@@ -134,7 +134,7 @@
.addFeature("feature")
val useAppIcon = !(args.size >= 2 && args[1] == "useAppIcon=false")
if (useAppIcon) {
- routeInfo.setPackageName(TEST_PACKAGE_NAME)
+ routeInfo.setClientPackageName(TEST_PACKAGE_NAME)
}
statusBarManager.updateMediaTapToTransferReceiverDisplay(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index 9ab83b8..2278938 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -56,7 +56,7 @@
internal val logger: MediaTttLogger,
internal val windowManager: WindowManager,
private val viewUtil: ViewUtil,
- @Main internal val mainExecutor: DelayableExecutor,
+ @Main private val mainExecutor: DelayableExecutor,
private val accessibilityManager: AccessibilityManager,
private val configurationController: ConfigurationController,
private val powerManager: PowerManager,
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
index d3b5bc6..aa10f7e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
@@ -18,7 +18,6 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel
-import com.android.systemui.log.dagger.MediaTttSenderLogBuffer
/**
* A logger for media tap-to-transfer events.
@@ -27,7 +26,7 @@
*/
class MediaTttLogger(
private val deviceTypeTag: String,
- @MediaTttSenderLogBuffer private val buffer: LogBuffer
+ private val buffer: LogBuffer
){
/** Logs a change in the chip state for the given [mediaRouteId]. */
fun logStateChange(stateName: String, mediaRouteId: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 0f1ae00..196ea22 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -143,7 +143,7 @@
super.updateChipView(newChipInfo, currentChipView)
setIcon(
currentChipView,
- newChipInfo.routeInfo.packageName,
+ newChipInfo.routeInfo.clientPackageName,
newChipInfo.appIconDrawableOverride,
newChipInfo.appNameOverride
)
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index b94b8bf..9335489 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -122,7 +122,7 @@
val chipState = newChipInfo.state
// App icon
- val iconName = setIcon(currentChipView, newChipInfo.routeInfo.packageName)
+ val iconName = setIcon(currentChipView, newChipInfo.routeInfo.clientPackageName)
// Text
val otherDeviceName = newChipInfo.routeInfo.name.toString()
@@ -160,12 +160,8 @@
duration = ANIMATION_DURATION,
includeMargins = true,
includeFadeIn = true,
- )
-
- // We can only request focus once the animation finishes.
- mainExecutor.executeDelayed(
- { chipInnerView.requestAccessibilityFocus() },
- ANIMATION_DURATION
+ // We can only request focus once the animation finishes.
+ onAnimationEnd = { chipInnerView.requestAccessibilityFocus() },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 7f0e76b..37a948d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -127,9 +127,7 @@
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.domain.usecase.SetClockPositionUseCase;
-import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAlphaUseCase;
-import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
+import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.media.MediaDataManager;
@@ -700,11 +698,7 @@
private final CameraGestureHelper mCameraGestureHelper;
private final Provider<KeyguardBottomAreaViewModel> mKeyguardBottomAreaViewModelProvider;
- private final Provider<SetClockPositionUseCase> mSetClockPositionUseCaseProvider;
- private final Provider<SetKeyguardBottomAreaAlphaUseCase>
- mSetKeyguardBottomAreaAlphaUseCaseProvider;
- private final Provider<SetKeyguardBottomAreaAnimateDozingTransitionsUseCase>
- mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider;
+ private final Provider<KeyguardBottomAreaInteractor> mKeyguardBottomAreaInteractorProvider;
@Inject
public NotificationPanelViewController(NotificationPanelView view,
@@ -776,10 +770,7 @@
SystemClock systemClock,
CameraGestureHelper cameraGestureHelper,
Provider<KeyguardBottomAreaViewModel> keyguardBottomAreaViewModelProvider,
- Provider<SetClockPositionUseCase> setClockPositionUseCaseProvider,
- Provider<SetKeyguardBottomAreaAlphaUseCase> setKeyguardBottomAreaAlphaUseCaseProvider,
- Provider<SetKeyguardBottomAreaAnimateDozingTransitionsUseCase>
- setKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider) {
+ Provider<KeyguardBottomAreaInteractor> keyguardBottomAreaInteractorProvider) {
super(view,
falsingManager,
dozeLog,
@@ -961,10 +952,7 @@
}
});
mCameraGestureHelper = cameraGestureHelper;
- mSetClockPositionUseCaseProvider = setClockPositionUseCaseProvider;
- mSetKeyguardBottomAreaAlphaUseCaseProvider = setKeyguardBottomAreaAlphaUseCaseProvider;
- mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider =
- setKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider;
+ mKeyguardBottomAreaInteractorProvider = keyguardBottomAreaInteractorProvider;
}
@VisibleForTesting
@@ -1475,7 +1463,7 @@
mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard),
mKeyguardStatusViewController.isClockTopAligned());
mClockPositionAlgorithm.run(mClockPositionResult);
- mSetClockPositionUseCaseProvider.get().invoke(
+ mKeyguardBottomAreaInteractorProvider.get().setClockPosition(
mClockPositionResult.clockX, mClockPositionResult.clockY);
boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
@@ -3249,7 +3237,7 @@
float alpha = Math.min(expansionAlpha, 1 - computeQsExpansionFraction());
alpha *= mBottomAreaShadeAlpha;
mKeyguardBottomArea.setComponentAlphas(alpha);
- mSetKeyguardBottomAreaAlphaUseCaseProvider.get().invoke(alpha);
+ mKeyguardBottomAreaInteractorProvider.get().setAlpha(alpha);
mLockIconViewController.setAlpha(alpha);
}
@@ -3449,7 +3437,7 @@
private void updateDozingVisibilities(boolean animate) {
mKeyguardBottomArea.setDozing(mDozing, animate);
- mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider.get().invoke(animate);
+ mKeyguardBottomAreaInteractorProvider.get().setAnimateDozingTransitions(animate);
if (!mDozing && animate) {
mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
}
@@ -3752,7 +3740,7 @@
mDozing = dozing;
mNotificationStackScrollLayoutController.setDozing(mDozing, animate, wakeUpTouchLocation);
mKeyguardBottomArea.setDozing(mDozing, animate);
- mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider.get().invoke(animate);
+ mKeyguardBottomAreaInteractorProvider.get().setAnimateDozingTransitions(animate);
mKeyguardStatusBarViewController.setDozing(mDozing);
if (dozing) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt
index c71eade..0c49713 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar.notification.collection
+import android.app.Notification
import android.content.Context
+import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.service.notification.StatusBarNotification
import android.util.Log
@@ -39,17 +41,28 @@
}
private fun resolveNotificationSdk(sbn: StatusBarNotification): Int {
+ val applicationInfo = getApplicationInfoFromExtras(sbn.notification)
+ ?: getApplicationInfoFromPackageManager(sbn)
+
+ return applicationInfo?.targetSdkVersion ?: 0
+ }
+
+ private fun getApplicationInfoFromExtras(notification: Notification): ApplicationInfo? =
+ notification.extras.getParcelable(
+ Notification.EXTRA_BUILDER_APPLICATION_INFO,
+ ApplicationInfo::class.java
+ )
+
+ private fun getApplicationInfoFromPackageManager(sbn: StatusBarNotification): ApplicationInfo? {
val pmUser = CentralSurfaces.getPackageManagerForUser(context, sbn.user.identifier)
- var targetSdk = 0
- // Extract target SDK version.
- try {
- val info = pmUser.getApplicationInfo(sbn.packageName, 0)
- targetSdk = info.targetSdkVersion
+
+ return try {
+ pmUser.getApplicationInfo(sbn.packageName, 0)
} catch (ex: PackageManager.NameNotFoundException) {
Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.packageName, ex)
+ null
}
- return targetSdk
}
private val TAG = "TargetSdkResolver"
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index 0b6b929..c956a2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -1,6 +1,7 @@
package com.android.systemui.statusbar.notification.interruption
import android.app.Notification
+import android.app.Notification.VISIBILITY_SECRET
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -172,6 +173,8 @@
!lockscreenUserManager.shouldShowLockscreenNotifications() -> true
// User settings do not allow this notification on the lockscreen, so hide it.
userSettingsDisallowNotification(entry) -> true
+ // Entry is explicitly marked SECRET, so hide it.
+ entry.sbn.notification.visibility == VISIBILITY_SECRET -> true
// if entry is silent, apply custom logic to see if should hide
shouldHideIfEntrySilent(entry) -> true
else -> false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java
index 2b782b6..3f4fd50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java
@@ -165,7 +165,7 @@
}
private void positiveFeedback(View v) {
- mGutsContainer.closeControls(v, false);
+ mGutsContainer.closeControls(v, /* save= */ false);
handleFeedback(true);
}
@@ -176,7 +176,7 @@
menuItem = mMenuRowPlugin.getLongpressMenuItem(mContext);
}
- mGutsContainer.closeControls(v, false);
+ mGutsContainer.closeControls(v, /* save= */ false);
mNotificationGutsManager.openGuts(mExpandableNotificationRow, 0, 0, menuItem);
handleFeedback(false);
}
@@ -203,7 +203,7 @@
}
private void closeControls(View v) {
- mGutsContainer.closeControls(v, false);
+ mGutsContainer.closeControls(v, /* save= */ false);
}
@Override
@@ -232,7 +232,7 @@
}
@Override
- public boolean shouldBeSaved() {
+ public boolean shouldBeSavedOnClose() {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 7120fe5..0ce9656 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -157,7 +157,7 @@
mShadeController.animateCollapsePanels();
mPeopleSpaceWidgetManager.requestPinAppWidget(mShortcutInfo, new Bundle());
}
- mGutsContainer.closeControls(v, true);
+ mGutsContainer.closeControls(v, /* save= */ true);
};
public NotificationConversationInfo(Context context, AttributeSet attrs) {
@@ -186,7 +186,6 @@
}
public void bindNotification(
- @Action int selectedAction,
ShortcutManager shortcutManager,
PackageManager pm,
PeopleSpaceWidgetManager peopleSpaceWidgetManager,
@@ -205,8 +204,6 @@
OnConversationSettingsClickListener onConversationSettingsClickListener,
Optional<BubblesManager> bubblesManagerOptional,
ShadeController shadeController) {
- mPressedApply = false;
- mSelectedAction = selectedAction;
mINotificationManager = iNotificationManager;
mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
mOnUserInteractionCallback = onUserInteractionCallback;
@@ -417,9 +414,7 @@
}
@Override
- public void onFinishedClosing() {
- mSelectedAction = -1;
- }
+ public void onFinishedClosing() { }
@Override
public boolean needsFalsingProtection() {
@@ -564,7 +559,7 @@
}
@Override
- public boolean shouldBeSaved() {
+ public boolean shouldBeSavedOnClose() {
return mPressedApply;
}
@@ -578,6 +573,12 @@
if (save && mSelectedAction > -1) {
updateChannel();
}
+
+ // Clear the selected importance when closing, so when when we open again,
+ // we starts from a clean state.
+ mSelectedAction = -1;
+ mPressedApply = false;
+
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
index fc296e1..93f0812 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
@@ -76,7 +76,7 @@
switch (action) {
case AccessibilityNodeInfo.ACTION_LONG_CLICK:
- closeControls(host, false);
+ closeControls(host, /* save= */ false);
return true;
}
@@ -123,7 +123,7 @@
/**
* Return whether something changed and needs to be saved, possibly requiring a bouncer.
*/
- boolean shouldBeSaved();
+ boolean shouldBeSavedOnClose();
/**
* Called when the guts view has finished its close animation.
@@ -259,7 +259,7 @@
if (mGutsContent != null) {
if ((mGutsContent.isLeavebehind() && leavebehinds)
|| (!mGutsContent.isLeavebehind() && controls)) {
- closeControls(x, y, mGutsContent.shouldBeSaved(), force);
+ closeControls(x, y, mGutsContent.shouldBeSavedOnClose(), force);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 7b0b0ce..ea12b82 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -463,7 +463,6 @@
R.dimen.notification_guts_conversation_icon_size));
notificationInfoView.bindNotification(
- notificationInfoView.getSelectedAction(),
mShortcutManager,
pmUser,
mPeopleSpaceWidgetManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index 8b01a47..ea0060a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -158,7 +158,7 @@
// used by standard ui
private OnClickListener mOnDismissSettings = v -> {
mPressedApply = true;
- mGutsContainer.closeControls(v, true);
+ mGutsContainer.closeControls(v, /* save= */ true);
};
public NotificationInfo(Context context, AttributeSet attrs) {
@@ -541,10 +541,6 @@
@Override
public void onFinishedClosing() {
- if (mChosenImportance != null) {
- mStartingChannelImportance = mChosenImportance;
- }
-
bindInlineControls();
logUiEvent(NotificationControlsEvent.NOTIFICATION_CONTROLS_CLOSE);
@@ -604,7 +600,7 @@
}
@Override
- public boolean shouldBeSaved() {
+ public boolean shouldBeSavedOnClose() {
return mPressedApply;
}
@@ -627,6 +623,12 @@
if (save) {
saveImportance();
}
+
+ // Clear the selected importance when closing, so when when we open again,
+ // we starts from a clean state.
+ mChosenImportance = null;
+ mPressedApply = false;
+
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
index 512b049..adbfa75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
@@ -384,7 +384,7 @@
private void undoSnooze(View v) {
mSelectedOption = null;
showSnoozeOptions(false);
- mGutsContainer.closeControls(v, false);
+ mGutsContainer.closeControls(v, /* save= */ false);
}
@Override
@@ -433,7 +433,7 @@
}
@Override
- public boolean shouldBeSaved() {
+ public boolean shouldBeSavedOnClose() {
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
index 186ffa6..ac97e77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
@@ -16,22 +16,13 @@
package com.android.systemui.statusbar.notification.row;
-import static android.app.Notification.EXTRA_IS_GROUP_CONVERSATION;
-
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.annotation.IntDef;
import android.app.INotificationManager;
-import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.os.Bundle;
-import android.os.Parcelable;
import android.os.RemoteException;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
@@ -46,8 +37,6 @@
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import java.lang.annotation.Retention;
-import java.util.List;
import java.util.Set;
/**
@@ -71,8 +60,6 @@
private Set<NotificationChannel> mUniqueChannelsInRow;
private Drawable mPkgIcon;
- private @Action int mSelectedAction = -1;
- private boolean mPressedApply;
private boolean mPresentingChannelEditorDialog = false;
private NotificationInfo.OnSettingsClickListener mOnSettingsClickListener;
@@ -82,14 +69,8 @@
@VisibleForTesting
boolean mSkipPost = false;
- @Retention(SOURCE)
- @IntDef({ACTION_SETTINGS})
- private @interface Action {}
- static final int ACTION_SETTINGS = 5;
-
private OnClickListener mOnDone = v -> {
- mPressedApply = true;
- mGutsContainer.closeControls(v, true);
+ mGutsContainer.closeControls(v, /* save= */ false);
};
public PartialConversationInfo(Context context, AttributeSet attrs) {
@@ -107,7 +88,6 @@
NotificationInfo.OnSettingsClickListener onSettingsClick,
boolean isDeviceProvisioned,
boolean isNonBlockable) {
- mSelectedAction = -1;
mINotificationManager = iNotificationManager;
mPackageName = pkg;
mSbn = entry.getSbn();
@@ -286,8 +266,8 @@
}
@Override
- public boolean shouldBeSaved() {
- return mPressedApply;
+ public boolean shouldBeSavedOnClose() {
+ return false;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
index 05d087e..0a44bda 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
@@ -37,5 +37,5 @@
@Provides
@SysUISingleton
@Background
- fun bgDispatcher(): CoroutineDispatcher = Dispatchers.Default
+ fun bgDispatcher(): CoroutineDispatcher = Dispatchers.IO
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index cde30af..c281965 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -16,7 +16,6 @@
package com.android.keyguard;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
@@ -582,6 +581,7 @@
@Test
public void testTriesToAuthenticate_whenBouncer() {
fingerprintIsNotEnrolled();
+ faceAuthEnabled();
setKeyguardBouncerVisibility(true);
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
@@ -1006,7 +1006,7 @@
// WHEN udfps is now enrolled
when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true);
- callback.onEnrollmentsChanged(TYPE_FINGERPRINT);
+ callback.onEnrollmentsChanged();
// THEN isUdfspEnrolled is TRUE
assertThat(mKeyguardUpdateMonitor.isUdfpsEnrolled()).isTrue();
@@ -1219,6 +1219,7 @@
public void testShouldListenForFace_whenFaceIsAlreadyAuthenticated_returnsFalse()
throws RemoteException {
// Face auth should run when the following is true.
+ faceAuthEnabled();
bouncerFullyVisibleAndNotGoingToSleep();
fingerprintIsNotEnrolled();
keyguardNotGoingAway();
@@ -1285,6 +1286,7 @@
public void testShouldListenForFace_whenBiometricsDisabledForUser_returnsFalse()
throws RemoteException {
// Preconditions for face auth to run
+ faceAuthEnabled();
keyguardNotGoingAway();
bouncerFullyVisibleAndNotGoingToSleep();
fingerprintIsNotEnrolled();
@@ -1308,6 +1310,7 @@
public void testShouldListenForFace_whenUserCurrentlySwitching_returnsFalse()
throws RemoteException {
// Preconditions for face auth to run
+ faceAuthEnabled();
keyguardNotGoingAway();
bouncerFullyVisibleAndNotGoingToSleep();
fingerprintIsNotEnrolled();
@@ -1330,6 +1333,7 @@
public void testShouldListenForFace_whenSecureCameraLaunched_returnsFalse()
throws RemoteException {
// Preconditions for face auth to run
+ faceAuthEnabled();
keyguardNotGoingAway();
bouncerFullyVisibleAndNotGoingToSleep();
fingerprintIsNotEnrolled();
@@ -1375,6 +1379,7 @@
public void testShouldListenForFace_whenBouncerShowingAndDeviceIsAwake_returnsTrue()
throws RemoteException {
// Preconditions for face auth to run
+ faceAuthEnabled();
keyguardNotGoingAway();
currentUserIsPrimary();
currentUserDoesNotHaveTrust();
@@ -1540,8 +1545,19 @@
assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(anyBoolean())).isEqualTo(true);
}
+ private void faceAuthEnabled() {
+ // this ensures KeyguardUpdateMonitor updates the cached mIsFaceEnrolled flag using the
+ // face manager mock wire-up in setup()
+ mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(mCurrentUserId);
+ }
+
private void fingerprintIsNotEnrolled() {
when(mFingerprintManager.hasEnrolledTemplates(mCurrentUserId)).thenReturn(false);
+ // This updates the cached fingerprint state.
+ // There is no straightforward API to update the fingerprint state.
+ // It currently works updates after enrollment changes because something else invokes
+ // startListeningForFingerprint(), which internally calls this method.
+ mKeyguardUpdateMonitor.isUnlockWithFingerprintPossible(mCurrentUserId);
}
private void statusBarShadeIsNotLocked() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java
index 6f4846a..36ae3c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java
@@ -19,6 +19,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -27,6 +28,7 @@
import android.os.SystemClock;
import android.testing.AndroidTestingRunner;
import android.view.MotionEvent;
+import android.view.View;
import android.view.ViewConfiguration;
import androidx.test.filters.SmallTest;
@@ -52,6 +54,7 @@
private int mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
private MagnificationGestureDetector mGestureDetector;
private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
+ private View mSpyView;
@Mock
private MagnificationGestureDetector.OnGestureListener mListener;
@Mock
@@ -66,6 +69,7 @@
return null;
}).when(mHandler).postAtTime(any(Runnable.class), anyLong());
mGestureDetector = new MagnificationGestureDetector(mContext, mHandler, mListener);
+ mSpyView = Mockito.spy(new View(mContext));
}
@After
@@ -79,7 +83,7 @@
final MotionEvent downEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
MotionEvent.ACTION_DOWN, ACTION_DOWN_X, ACTION_DOWN_Y);
- mGestureDetector.onTouch(downEvent);
+ mGestureDetector.onTouch(mSpyView, downEvent);
mListener.onStart(ACTION_DOWN_X, ACTION_DOWN_Y);
}
@@ -92,14 +96,14 @@
final MotionEvent upEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
MotionEvent.ACTION_UP, ACTION_DOWN_X, ACTION_DOWN_Y);
- mGestureDetector.onTouch(downEvent);
- mGestureDetector.onTouch(upEvent);
+ mGestureDetector.onTouch(mSpyView, downEvent);
+ mGestureDetector.onTouch(mSpyView, upEvent);
InOrder inOrder = Mockito.inOrder(mListener);
inOrder.verify(mListener).onStart(ACTION_DOWN_X, ACTION_DOWN_Y);
- inOrder.verify(mListener).onSingleTap();
+ inOrder.verify(mListener).onSingleTap(mSpyView);
inOrder.verify(mListener).onFinish(ACTION_DOWN_X, ACTION_DOWN_Y);
- verify(mListener, never()).onDrag(anyFloat(), anyFloat());
+ verify(mListener, never()).onDrag(eq(mSpyView), anyFloat(), anyFloat());
}
@Test
@@ -110,10 +114,10 @@
final MotionEvent cancelEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
MotionEvent.ACTION_CANCEL, ACTION_DOWN_X, ACTION_DOWN_Y);
- mGestureDetector.onTouch(downEvent);
- mGestureDetector.onTouch(cancelEvent);
+ mGestureDetector.onTouch(mSpyView, downEvent);
+ mGestureDetector.onTouch(mSpyView, cancelEvent);
- verify(mListener, never()).onSingleTap();
+ verify(mListener, never()).onSingleTap(mSpyView);
}
@Test
@@ -124,10 +128,10 @@
final MotionEvent upEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
MotionEvent.ACTION_POINTER_DOWN, ACTION_DOWN_X, ACTION_DOWN_Y);
- mGestureDetector.onTouch(downEvent);
- mGestureDetector.onTouch(upEvent);
+ mGestureDetector.onTouch(mSpyView, downEvent);
+ mGestureDetector.onTouch(mSpyView, upEvent);
- verify(mListener, never()).onSingleTap();
+ verify(mListener, never()).onSingleTap(mSpyView);
}
@Test
@@ -138,15 +142,15 @@
final MotionEvent upEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
MotionEvent.ACTION_UP, ACTION_DOWN_X, ACTION_DOWN_Y);
- mGestureDetector.onTouch(downEvent);
+ mGestureDetector.onTouch(mSpyView, downEvent);
// Execute the pending message for stopping single-tap detection.
mCancelSingleTapRunnable.run();
- mGestureDetector.onTouch(upEvent);
+ mGestureDetector.onTouch(mSpyView, upEvent);
InOrder inOrder = Mockito.inOrder(mListener);
inOrder.verify(mListener).onStart(ACTION_DOWN_X, ACTION_DOWN_Y);
inOrder.verify(mListener).onFinish(ACTION_DOWN_X, ACTION_DOWN_Y);
- verify(mListener, never()).onSingleTap();
+ verify(mListener, never()).onSingleTap(mSpyView);
}
@Test
@@ -160,14 +164,14 @@
final MotionEvent upEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
MotionEvent.ACTION_UP, ACTION_DOWN_X, ACTION_DOWN_Y);
- mGestureDetector.onTouch(downEvent);
- mGestureDetector.onTouch(moveEvent);
- mGestureDetector.onTouch(upEvent);
+ mGestureDetector.onTouch(mSpyView, downEvent);
+ mGestureDetector.onTouch(mSpyView, moveEvent);
+ mGestureDetector.onTouch(mSpyView, upEvent);
InOrder inOrder = Mockito.inOrder(mListener);
inOrder.verify(mListener).onStart(ACTION_DOWN_X, ACTION_DOWN_Y);
- inOrder.verify(mListener).onDrag(dragOffset, 0);
+ inOrder.verify(mListener).onDrag(mSpyView, dragOffset, 0);
inOrder.verify(mListener).onFinish(ACTION_DOWN_X, ACTION_DOWN_Y);
- verify(mListener, never()).onSingleTap();
+ verify(mListener, never()).onSingleTap(mSpyView);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
index bc89da7..00cb491 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
@@ -145,7 +145,7 @@
@Test
public void removeButton_buttonIsShowing_removeViewAndUnregisterComponentCallbacks() {
- mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
mMagnificationModeSwitch.removeButton();
@@ -167,7 +167,7 @@
@Test
public void showButton_excludeSystemGestureArea() {
- mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
verify(mSpyImageView).setSystemGestureExclusionRects(any(List.class));
}
@@ -178,7 +178,7 @@
when(mAccessibilityManager.getRecommendedTimeoutMillis(anyInt(), anyInt())).thenReturn(
a11yTimeout);
- mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
verify(mAccessibilityManager).getRecommendedTimeoutMillis(
DEFAULT_FADE_OUT_ANIMATION_DELAY_MS, AccessibilityManager.FLAG_CONTENT_ICONS
@@ -188,7 +188,7 @@
@Test
public void showMagnificationButton_windowModeAndFadingOut_verifyAnimationEndAction() {
- mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
executeFadeOutAnimation();
// Verify the end action after fade-out.
@@ -389,15 +389,15 @@
@Test
public void initializeA11yNode_showWindowModeButton_expectedValues() {
- mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
final AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
mSpyImageView.onInitializeAccessibilityNodeInfo(nodeInfo);
assertEquals(mContext.getString(R.string.magnification_mode_switch_description),
nodeInfo.getContentDescription());
- assertEquals(mContext.getString(R.string.magnification_mode_switch_state_window),
- nodeInfo.getStateDescription());
+ assertEquals(mContext.getString(R.string.magnification_mode_switch_state_full_screen),
+ nodeInfo.getStateDescription().toString());
assertThat(nodeInfo.getActionList(),
hasItems(new AccessibilityNodeInfo.AccessibilityAction(
ACTION_CLICK.getId(), mContext.getResources().getString(
@@ -422,18 +422,18 @@
@Test
public void performClickA11yActions_showWindowModeButton_verifyTapAction() {
- mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
resetAndStubMockImageViewAndAnimator();
mSpyImageView.performAccessibilityAction(
ACTION_CLICK.getId(), null);
- verifyTapAction(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+ verifyTapAction(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
}
@Test
public void performMoveLeftA11yAction_showButtonAtRightEdge_moveToLeftEdge() {
- mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
mSpyImageView.performAccessibilityAction(
R.id.accessibility_action_move_left, null);
@@ -456,7 +456,7 @@
@Test
public void showButton_hasAccessibilityWindowTitle() {
- mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
final WindowManager.LayoutParams layoutPrams =
mWindowManager.getLayoutParamsFromAttachedView();
@@ -468,7 +468,7 @@
@Test
public void showButton_registerComponentCallbacks() {
- mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
verify(mContext).registerComponentCallbacks(mMagnificationModeSwitch);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
index a56218b..82ae6ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
@@ -24,6 +24,7 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.Display;
+import android.view.View;
import androidx.test.filters.SmallTest;
@@ -46,6 +47,7 @@
private FakeSwitchSupplier mSupplier;
private MagnificationModeSwitch mModeSwitch;
private ModeSwitchesController mModeSwitchesController;
+ private View mSpyView;
@Mock
private MagnificationModeSwitch.SwitchListener mListener;
@@ -57,6 +59,7 @@
mModeSwitchesController = new ModeSwitchesController(mSupplier);
mModeSwitchesController.setSwitchListenerDelegate(mListener);
mModeSwitch = Mockito.spy(new MagnificationModeSwitch(mContext, mModeSwitchesController));
+ mSpyView = Mockito.spy(new View(mContext));
}
@After
@@ -94,12 +97,12 @@
@Test
public void testOnSwitchClick_showWindowModeButton_invokeListener() {
mModeSwitchesController.showButton(Display.DEFAULT_DISPLAY,
- Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
- mModeSwitch.onSingleTap();
+ mModeSwitch.onSingleTap(mSpyView);
verify(mListener).onSwitch(mContext.getDisplayId(),
- Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
}
private class FakeSwitchSupplier extends DisplayIdIndexSupplier<MagnificationModeSwitch> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 3d77d64..69ccc8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -19,6 +19,8 @@
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.Choreographer.FrameCallback;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_UP;
import static android.view.WindowInsets.Type.systemGestures;
import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -46,6 +48,7 @@
import static org.mockito.Mockito.when;
import android.animation.ValueAnimator;
+import android.annotation.IdRes;
import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -65,6 +68,7 @@
import android.text.TextUtils;
import android.view.Display;
import android.view.IWindowSession;
+import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.View;
@@ -126,8 +130,13 @@
private WindowMagnificationController mWindowMagnificationController;
private Instrumentation mInstrumentation;
private final ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0, 1.0f).setDuration(0);
+
private IWindowSession mWindowSessionSpy;
+ private View mSpyView;
+ private View.OnTouchListener mTouchListener;
+ private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
+
@Before
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
@@ -165,6 +174,12 @@
verify(mMirrorWindowControl).setWindowDelegate(
any(MirrorWindowControl.MirrorWindowDelegate.class));
+ mSpyView = Mockito.spy(new View(mContext));
+ doAnswer((invocation) -> {
+ mTouchListener = invocation.getArgument(0);
+ return null;
+ }).when(mSpyView).setOnTouchListener(
+ any(View.OnTouchListener.class));
}
@After
@@ -702,7 +717,7 @@
});
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.onSingleTap();
+ mWindowMagnificationController.onSingleTap(mSpyView);
});
final View mirrorView = mWindowManager.getAttachedView();
@@ -910,6 +925,38 @@
assertTrue(magnificationCenterY.get() < bounds.bottom);
}
+ @Test
+ public void performSingleTap_DragHandle() {
+ final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(
+ 1.5f, bounds.centerX(), bounds.centerY());
+ });
+ View dragButton = getInternalView(R.id.drag_handle);
+
+ // Perform a single-tap
+ final long downTime = SystemClock.uptimeMillis();
+ dragButton.dispatchTouchEvent(
+ obtainMotionEvent(downTime, 0, ACTION_DOWN, 100, 100));
+ dragButton.dispatchTouchEvent(
+ obtainMotionEvent(downTime, downTime, ACTION_UP, 100, 100));
+
+ verify(mWindowManager).addView(any(View.class), any());
+ }
+
+ private <T extends View> T getInternalView(@IdRes int idRes) {
+ View mirrorView = mWindowManager.getAttachedView();
+ T view = mirrorView.findViewById(idRes);
+ assertNotNull(view);
+ return view;
+ }
+
+ private MotionEvent obtainMotionEvent(long downTime, long eventTime, int action, float x,
+ float y) {
+ return mMotionEventHelper.obtainMotionEvent(downTime, eventTime, action, x, y);
+ }
+
private CharSequence getAccessibilityWindowTitle() {
final View mirrorView = mWindowManager.getAttachedView();
if (mirrorView == null) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
new file mode 100644
index 0000000..2f94b69
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2022 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.accessibility;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.IdRes;
+import android.content.Context;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.CompoundButton;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class WindowMagnificationSettingsTest extends SysuiTestCase {
+
+ private static final int MAGNIFICATION_SIZE_SMALL = 1;
+ private static final int MAGNIFICATION_SIZE_MEDIUM = 2;
+ private static final int MAGNIFICATION_SIZE_LARGE = 3;
+
+ private ViewGroup mSettingView;
+ @Mock
+ private AccessibilityManager mAccessibilityManager;
+ @Mock
+ private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
+ @Mock
+ private WindowMagnificationSettingsCallback mWindowMagnificationSettingsCallback;
+ private TestableWindowManager mWindowManager;
+ private WindowMagnificationSettings mWindowMagnificationSettings;
+ private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContext = getContext();
+ mContext.setTheme(android.R.style.Theme_DeviceDefault_DayNight);
+ final WindowManager wm = mContext.getSystemService(WindowManager.class);
+ mWindowManager = spy(new TestableWindowManager(wm));
+ mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+ mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager);
+
+ mWindowMagnificationSettings = new WindowMagnificationSettings(mContext,
+ mWindowMagnificationSettingsCallback, mSfVsyncFrameProvider);
+
+ mSettingView = mWindowMagnificationSettings.getSettingView();
+ }
+
+ @After
+ public void tearDown() {
+ mMotionEventHelper.recycleEvents();
+ mWindowMagnificationSettings.hideSettingPanel();
+ }
+
+ @Test
+ public void showSettingPanel_hasAccessibilityWindowTitle() {
+ mWindowMagnificationSettings.showSettingPanel();
+
+ final WindowManager.LayoutParams layoutPrams =
+ mWindowManager.getLayoutParamsFromAttachedView();
+ assertNotNull(layoutPrams);
+ assertEquals(getContext().getResources()
+ .getString(com.android.internal.R.string.android_system_label),
+ layoutPrams.accessibilityTitle.toString());
+ }
+
+ @Test
+ public void performClick_smallSizeButton_changeMagnifierSizeSmall() {
+ // Open view
+ mWindowMagnificationSettings.showSettingPanel();
+
+ verifyOnSetMagnifierSize(R.id.magnifier_small_button, MAGNIFICATION_SIZE_SMALL);
+ }
+
+ @Test
+ public void performClick_mediumSizeButton_changeMagnifierSizeMedium() {
+ // Open view
+ mWindowMagnificationSettings.showSettingPanel();
+
+ verifyOnSetMagnifierSize(R.id.magnifier_medium_button, MAGNIFICATION_SIZE_MEDIUM);
+ }
+
+ @Test
+ public void performClick_largeSizeButton_changeMagnifierSizeLarge() {
+ // Open view
+ mWindowMagnificationSettings.showSettingPanel();
+
+ verifyOnSetMagnifierSize(R.id.magnifier_large_button, MAGNIFICATION_SIZE_LARGE);
+ }
+
+ private void verifyOnSetMagnifierSize(@IdRes int viewId, int expectedSizeIndex) {
+ View changeSizeButton = getInternalView(viewId);
+
+ // Perform click
+ changeSizeButton.performClick();
+
+ verify(mWindowMagnificationSettingsCallback).onSetMagnifierSize(expectedSizeIndex);
+ }
+
+
+ @Test
+ public void performClick_fullScreenModeButton_setEditMagnifierSizeMode() {
+ View fullScreenModeButton = getInternalView(R.id.magnifier_full_button);
+ getInternalView(R.id.magnifier_panel_view);
+
+ // Open view
+ mWindowMagnificationSettings.showSettingPanel();
+
+ // Perform click
+ fullScreenModeButton.performClick();
+
+ verify(mWindowManager).removeView(mSettingView);
+ verify(mWindowMagnificationSettingsCallback)
+ .onModeSwitch(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+ }
+
+ @Test
+ public void performClick_editButton_setEditMagnifierSizeMode() {
+ View editButton = getInternalView(R.id.magnifier_edit_button);
+
+ // Open view
+ mWindowMagnificationSettings.showSettingPanel();
+
+ // Perform click
+ editButton.performClick();
+
+ verify(mWindowMagnificationSettingsCallback).onEditMagnifierSizeMode(true);
+ verify(mWindowManager).removeView(mSettingView);
+ }
+
+ @Test
+ public void performClick_setDiagonalScrollingSwitch_toggleDiagonalScrollingSwitchMode() {
+ CompoundButton diagonalScrollingSwitch =
+ getInternalView(R.id.magnifier_horizontal_lock_switch);
+ final boolean currentCheckedState = diagonalScrollingSwitch.isChecked();
+
+ // Open view
+ mWindowMagnificationSettings.showSettingPanel();
+
+ // Perform click
+ diagonalScrollingSwitch.performClick();
+
+ verify(mWindowMagnificationSettingsCallback).onSetDiagonalScrolling(!currentCheckedState);
+ }
+
+ private <T extends View> T getInternalView(@IdRes int idRes) {
+ T view = mSettingView.findViewById(idRes);
+ assertNotNull(view);
+ return view;
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
index ccf2f8b..e1bd25b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
@@ -158,6 +158,18 @@
}
@Test
+ public void onModeSwitch_enabled_notifyCallback() throws RemoteException {
+ final int magnificationModeFullScreen = 1;
+ mCommandQueue.requestWindowMagnificationConnection(true);
+ waitForIdleSync();
+
+ mWindowMagnification.onModeSwitch(TEST_DISPLAY, magnificationModeFullScreen);
+
+ verify(mConnectionCallback).onChangeMagnificationMode(TEST_DISPLAY,
+ magnificationModeFullScreen);
+ }
+
+ @Test
public void overviewProxyIsConnected_noController_resetFlag() {
mOverviewProxyListener.onConnectionChanged(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
index 273786d..8fc0489 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
@@ -664,6 +664,60 @@
}
@Test
+ fun animateAddition_runnableRunsWhenAnimationEnds() {
+ var runnableRun = false
+ val onAnimationEndRunnable = { runnableRun = true }
+
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.CENTER,
+ includeMargins = true,
+ onAnimationEnd = onAnimationEndRunnable
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ endAnimation(rootView)
+
+ assertEquals(true, runnableRun)
+ }
+
+ @Test
+ fun animateAddition_runnableDoesNotRunWhenAnimationCancelled() {
+ var runnableRun = false
+ val onAnimationEndRunnable = { runnableRun = true }
+
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.CENTER,
+ includeMargins = true,
+ onAnimationEnd = onAnimationEndRunnable
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ cancelAnimation(rootView)
+
+ assertEquals(false, runnableRun)
+ }
+
+ @Test
+ fun animationAddition_runnableDoesNotRunWhenOnlyPartwayThroughAnimation() {
+ var runnableRun = false
+ val onAnimationEndRunnable = { runnableRun = true }
+
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.CENTER,
+ includeMargins = true,
+ onAnimationEnd = onAnimationEndRunnable
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ advanceAnimation(rootView, 0.5f)
+
+ assertEquals(false, runnableRun)
+ }
+
+ @Test
fun animatesViewRemovalFromStartToEnd() {
setUpRootWithChildren()
@@ -1158,6 +1212,16 @@
}
}
+ private fun cancelAnimation(rootView: View) {
+ (rootView.getTag(R.id.tag_animator) as? ObjectAnimator)?.cancel()
+
+ if (rootView is ViewGroup) {
+ for (i in 0 until rootView.childCount) {
+ cancelAnimation(rootView.getChildAt(i))
+ }
+ }
+ }
+
private fun endFadeInAnimation(rootView: View) {
(rootView.getTag(R.id.tag_alpha_animator) as? ObjectAnimator)?.end()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index e0d1f7a..d158892 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -60,9 +60,6 @@
import android.hardware.biometrics.SensorProperties;
import android.hardware.display.DisplayManager;
import android.hardware.face.FaceManager;
-import android.hardware.face.FaceSensorProperties;
-import android.hardware.face.FaceSensorPropertiesInternal;
-import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -157,9 +154,7 @@
@Mock
private InteractionJankMonitor mInteractionJankMonitor;
@Captor
- ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mFpAuthenticatorsRegisteredCaptor;
- @Captor
- ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> mFaceAuthenticatorsRegisteredCaptor;
+ ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mAuthenticatorsRegisteredCaptor;
@Captor
ArgumentCaptor<BiometricStateListener> mBiometricStateCaptor;
@Captor
@@ -198,38 +193,25 @@
when(mDisplayManager.getStableDisplaySize()).thenReturn(new Point());
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
- when(mFaceManager.isHardwareDetected()).thenReturn(true);
- final List<ComponentInfoInternal> fpComponentInfo = List.of(
- new ComponentInfoInternal("faceSensor" /* componentId */,
- "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
- "00000001" /* serialNumber */, "" /* softwareVersion */));
- final List<ComponentInfoInternal> faceComponentInfo = List.of(
- new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
- "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
- "vendor/version/revision" /* softwareVersion */));
+ final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+ componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
+ "00000001" /* serialNumber */, "" /* softwareVersion */));
+ componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
+ "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
+ "vendor/version/revision" /* softwareVersion */));
- final List<FingerprintSensorPropertiesInternal> fpProps = List.of(
- new FingerprintSensorPropertiesInternal(
- 1 /* sensorId */,
- SensorProperties.STRENGTH_STRONG,
- 1 /* maxEnrollmentsPerUser */,
- fpComponentInfo,
- FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
- true /* resetLockoutRequireHardwareAuthToken */));
- when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(fpProps);
-
- final List<FaceSensorPropertiesInternal> faceProps = List.of(
- new FaceSensorPropertiesInternal(
- 2 /* sensorId */,
- SensorProperties.STRENGTH_STRONG,
- 1 /* maxEnrollmentsPerUser */,
- fpComponentInfo,
- FaceSensorProperties.TYPE_RGB,
- true /* supportsFaceDetection */,
- true /* supportsSelfIllumination */,
- true /* resetLockoutRequireHardwareAuthToken */));
- when(mFaceManager.getSensorPropertiesInternal()).thenReturn(faceProps);
+ FingerprintSensorPropertiesInternal prop = new FingerprintSensorPropertiesInternal(
+ 1 /* sensorId */,
+ SensorProperties.STRENGTH_STRONG,
+ 1 /* maxEnrollmentsPerUser */,
+ componentInfo,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+ true /* resetLockoutRequireHardwareAuthToken */);
+ List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
+ props.add(prop);
+ when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
mAuthController = new TestableAuthController(mContextSpy, mExecution, mCommandQueue,
mActivityTaskManager, mWindowManager, mFingerprintManager, mFaceManager,
@@ -237,15 +219,12 @@
mAuthController.start();
verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
- mFpAuthenticatorsRegisteredCaptor.capture());
- verify(mFaceManager).addAuthenticatorsRegisteredCallback(
- mFaceAuthenticatorsRegisteredCaptor.capture());
+ mAuthenticatorsRegisteredCaptor.capture());
when(mStatusBarStateController.isDozing()).thenReturn(false);
verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
- mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(fpProps);
- mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(faceProps);
+ mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(props);
// Ensures that the operations posted on the handler get executed.
mTestableLooper.processAllMessages();
@@ -258,7 +237,6 @@
throws RemoteException {
// This test is sensitive to prior FingerprintManager interactions.
reset(mFingerprintManager);
- reset(mFaceManager);
// This test requires an uninitialized AuthController.
AuthController authController = new TestableAuthController(mContextSpy, mExecution,
@@ -268,27 +246,21 @@
authController.start();
verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
- mFpAuthenticatorsRegisteredCaptor.capture());
- verify(mFaceManager).addAuthenticatorsRegisteredCallback(
- mFaceAuthenticatorsRegisteredCaptor.capture());
+ mAuthenticatorsRegisteredCaptor.capture());
mTestableLooper.processAllMessages();
verify(mFingerprintManager, never()).registerBiometricStateListener(any());
- verify(mFaceManager, never()).registerBiometricStateListener(any());
- mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
- mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
+ mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(new ArrayList<>());
mTestableLooper.processAllMessages();
verify(mFingerprintManager).registerBiometricStateListener(any());
- verify(mFaceManager).registerBiometricStateListener(any());
}
@Test
public void testDoesNotCrash_afterEnrollmentsChangedForUnknownSensor() throws RemoteException {
// This test is sensitive to prior FingerprintManager interactions.
reset(mFingerprintManager);
- reset(mFaceManager);
// This test requires an uninitialized AuthController.
AuthController authController = new TestableAuthController(mContextSpy, mExecution,
@@ -298,25 +270,18 @@
authController.start();
verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
- mFpAuthenticatorsRegisteredCaptor.capture());
- verify(mFaceManager).addAuthenticatorsRegisteredCallback(
- mFaceAuthenticatorsRegisteredCaptor.capture());
+ mAuthenticatorsRegisteredCaptor.capture());
// Emulates a device with no authenticators (empty list).
- mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
- mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
+ mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(new ArrayList<>());
mTestableLooper.processAllMessages();
verify(mFingerprintManager).registerBiometricStateListener(
mBiometricStateCaptor.capture());
- verify(mFaceManager).registerBiometricStateListener(
- mBiometricStateCaptor.capture());
// Enrollments changed for an unknown sensor.
- for (BiometricStateListener listener : mBiometricStateCaptor.getAllValues()) {
- listener.onEnrollmentsChanged(0 /* userId */,
- 0xbeef /* sensorId */, true /* hasEnrollments */);
- }
+ mBiometricStateCaptor.getValue().onEnrollmentsChanged(0 /* userId */,
+ 0xbeef /* sensorId */, true /* hasEnrollments */);
mTestableLooper.processAllMessages();
// Nothing should crash.
@@ -862,3 +827,4 @@
}
}
}
+
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index b33f9a7..9ffc5a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -16,8 +16,6 @@
package com.android.systemui.doze;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
-
import static com.android.systemui.doze.DozeLog.REASON_SENSOR_TAP;
import static com.android.systemui.doze.DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS;
import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_LOCK_SCREEN;
@@ -414,7 +412,7 @@
// WHEN enrollment changes to TRUE
when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true);
- mAuthControllerCallback.onEnrollmentsChanged(TYPE_FINGERPRINT);
+ mAuthControllerCallback.onEnrollmentsChanged();
// THEN mConfigured = TRUE
assertTrue(triggerSensor.mConfigured);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
index 5ec6bdf..24d0515 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -16,8 +16,6 @@
package com.android.systemui.keyguard;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
-
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.keyguard.LockIconView.ICON_LOCK;
import static com.android.keyguard.LockIconView.ICON_UNLOCK;
@@ -221,7 +219,7 @@
Pair<Float, PointF> udfps = setupUdfps();
// WHEN all authenticators are registered
- mAuthControllerCallback.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT);
+ mAuthControllerCallback.onAllAuthenticatorsRegistered();
mDelayableExecutor.runAllReady();
// THEN lock icon view location is updated with the same coordinates as auth controller vals
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 38a3375..11eb4e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -16,11 +16,10 @@
package com.android.systemui.keyguard.data.repository
-import com.android.systemui.common.data.model.Position
+import com.android.systemui.common.shared.model.Position
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.yield
/** Fake implementation of [KeyguardRepository] */
class FakeKeyguardRepository : KeyguardRepository {
@@ -56,30 +55,15 @@
_clockPosition.value = Position(x, y)
}
- suspend fun setKeyguardShowing(isShowing: Boolean) {
+ fun setKeyguardShowing(isShowing: Boolean) {
_isKeyguardShowing.value = isShowing
- // Yield to allow the test's collection coroutine to "catch up" and collect this value
- // before the test continues to the next line.
- // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
- // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
- yield()
}
- suspend fun setDozing(isDozing: Boolean) {
+ fun setDozing(isDozing: Boolean) {
_isDozing.value = isDozing
- // Yield to allow the test's collection coroutine to "catch up" and collect this value
- // before the test continues to the next line.
- // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
- // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
- yield()
}
- suspend fun setDozeAmount(dozeAmount: Float) {
+ fun setDozeAmount(dozeAmount: Float) {
_dozeAmount.value = dozeAmount
- // Yield to allow the test's collection coroutine to "catch up" and collect this value
- // before the test continues to the next line.
- // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
- // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
- yield()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 3d2c51a..3aa2266 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -18,7 +18,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.data.model.Position
+import com.android.systemui.common.shared.model.Position
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.argumentCaptor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
index 1c9902b..e68c43f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
@@ -23,18 +23,18 @@
/** Fake implementation of [FakeKeyguardQuickAffordanceRegistry], for tests. */
class FakeKeyguardQuickAffordanceRegistry(
private val configsByPosition:
- Map<KeyguardQuickAffordancePosition, List<KeyguardQuickAffordanceConfig>>,
-) : KeyguardQuickAffordanceRegistry {
+ Map<KeyguardQuickAffordancePosition, List<FakeKeyguardQuickAffordanceConfig>>,
+) : KeyguardQuickAffordanceRegistry<FakeKeyguardQuickAffordanceConfig> {
override fun getAll(
position: KeyguardQuickAffordancePosition
- ): List<KeyguardQuickAffordanceConfig> {
+ ): List<FakeKeyguardQuickAffordanceConfig> {
return configsByPosition.getValue(position)
}
override fun get(
- configClass: KClass<out KeyguardQuickAffordanceConfig>
- ): KeyguardQuickAffordanceConfig {
+ configClass: KClass<out FakeKeyguardQuickAffordanceConfig>
+ ): FakeKeyguardQuickAffordanceConfig {
return configsByPosition.values
.flatten()
.associateBy { config -> config::class }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeLaunchKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeLaunchKeyguardQuickAffordanceUseCase.kt
deleted file mode 100644
index ba0c31f..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeLaunchKeyguardQuickAffordanceUseCase.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2022 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.keyguard.domain.usecase
-
-import android.content.Intent
-import com.android.systemui.animation.ActivityLaunchAnimator
-
-/** Fake implementation of [LaunchKeyguardQuickAffordanceUseCase], for tests. */
-class FakeLaunchKeyguardQuickAffordanceUseCase : LaunchKeyguardQuickAffordanceUseCase {
-
- data class Invocation(
- val intent: Intent,
- val canShowWhileLocked: Boolean,
- val animationController: ActivityLaunchAnimator.Controller?
- )
-
- private val _invocations = mutableListOf<Invocation>()
- val invocations: List<Invocation> = _invocations
-
- override fun invoke(
- intent: Intent,
- canShowWhileLocked: Boolean,
- animationController: ActivityLaunchAnimator.Controller?
- ) {
- _invocations.add(
- Invocation(
- intent = intent,
- canShowWhileLocked = canShowWhileLocked,
- animationController = animationController,
- )
- )
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeObserveKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeObserveKeyguardQuickAffordanceUseCase.kt
deleted file mode 100644
index 8982752..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeObserveKeyguardQuickAffordanceUseCase.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2022 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.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-
-class FakeObserveKeyguardQuickAffordanceUseCase : ObserveKeyguardQuickAffordanceUseCase {
-
- private val affordanceByPosition =
- mutableMapOf<
- KeyguardQuickAffordancePosition, MutableStateFlow<KeyguardQuickAffordanceModel>>()
-
- init {
- KeyguardQuickAffordancePosition.values().forEach { position ->
- affordanceByPosition[position] = MutableStateFlow(KeyguardQuickAffordanceModel.Hidden)
- }
- }
-
- override fun invoke(
- position: KeyguardQuickAffordancePosition
- ): Flow<KeyguardQuickAffordanceModel> {
- return affordanceByPosition[position] ?: error("Flow unexpectedly missing!")
- }
-
- fun setModel(position: KeyguardQuickAffordancePosition, model: KeyguardQuickAffordanceModel) {
- affordanceByPosition[position]?.value = model
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt
new file mode 100644
index 0000000..c5e828e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2022 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.keyguard.domain.usecase
+
+import android.content.Intent
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameter
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.same
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(Parameterized::class)
+class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
+
+ companion object {
+ private val INTENT = Intent("some.intent.action")
+ private val DRAWABLE = mock<ContainedDrawable>()
+ private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+
+ @Parameters(
+ name =
+ "needStrongAuthAfterBoot={0}, canShowWhileLocked={1}," +
+ " keyguardIsUnlocked={2}, needsToUnlockFirst={3}, startActivity={4}"
+ )
+ @JvmStatic
+ fun data() =
+ listOf(
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ true,
+ ),
+ )
+ }
+
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
+
+ private lateinit var underTest: KeyguardQuickAffordanceInteractor
+
+ @JvmField @Parameter(0) var needStrongAuthAfterBoot: Boolean = false
+ @JvmField @Parameter(1) var canShowWhileLocked: Boolean = false
+ @JvmField @Parameter(2) var keyguardIsUnlocked: Boolean = false
+ @JvmField @Parameter(3) var needsToUnlockFirst: Boolean = false
+ @JvmField @Parameter(4) var startActivity: Boolean = false
+ private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
+ underTest =
+ KeyguardQuickAffordanceInteractor(
+ keyguardInteractor = KeyguardInteractor(repository = FakeKeyguardRepository()),
+ registry =
+ FakeKeyguardQuickAffordanceRegistry(
+ mapOf(
+ KeyguardQuickAffordancePosition.BOTTOM_START to
+ listOf(
+ homeControls,
+ ),
+ KeyguardQuickAffordancePosition.BOTTOM_END to
+ listOf(
+ object : FakeKeyguardQuickAffordanceConfig() {},
+ object : FakeKeyguardQuickAffordanceConfig() {},
+ ),
+ ),
+ ),
+ lockPatternUtils = lockPatternUtils,
+ keyguardStateController = keyguardStateController,
+ userTracker = userTracker,
+ activityStarter = activityStarter,
+ )
+ }
+
+ @Test
+ fun onQuickAffordanceClicked() = runBlockingTest {
+ setUpMocks(
+ needStrongAuthAfterBoot = needStrongAuthAfterBoot,
+ keyguardIsUnlocked = keyguardIsUnlocked,
+ )
+
+ homeControls.setState(
+ state =
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = DRAWABLE,
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ )
+ homeControls.onClickedResult =
+ if (startActivity) {
+ KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+ intent = INTENT,
+ canShowWhileLocked = canShowWhileLocked,
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.OnClickedResult.Handled
+ }
+
+ underTest.onQuickAffordanceClicked(
+ configKey = homeControls::class,
+ animationController = animationController,
+ )
+
+ if (startActivity) {
+ if (needsToUnlockFirst) {
+ verify(activityStarter)
+ .postStartActivityDismissingKeyguard(
+ any(),
+ /* delay= */ eq(0),
+ same(animationController),
+ )
+ } else {
+ verify(activityStarter)
+ .startActivity(
+ any(),
+ /* dismissShade= */ eq(true),
+ same(animationController),
+ /* showOverLockscreenWhenLocked= */ eq(true),
+ )
+ }
+ } else {
+ verifyZeroInteractions(activityStarter)
+ }
+ }
+
+ private fun setUpMocks(
+ needStrongAuthAfterBoot: Boolean = true,
+ keyguardIsUnlocked: Boolean = false,
+ ) {
+ whenever(userTracker.userHandle).thenReturn(mock())
+ whenever(lockPatternUtils.getStrongAuthForUser(any()))
+ .thenReturn(
+ if (needStrongAuthAfterBoot) {
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+ } else {
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
+ }
+ )
+ whenever(keyguardStateController.isUnlocked).thenReturn(keyguardIsUnlocked)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt
similarity index 74%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseImplTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt
index 63eb68f..d3fc29f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt
@@ -17,14 +17,20 @@
package com.android.systemui.keyguard.domain.usecase
import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.containeddrawable.ContainedDrawable
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
@@ -34,33 +40,39 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(JUnit4::class)
-class ObserveKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
+class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
- private lateinit var underTest: ObserveKeyguardQuickAffordanceUseCase
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var activityStarter: ActivityStarter
+
+ private lateinit var underTest: KeyguardQuickAffordanceInteractor
private lateinit var repository: FakeKeyguardRepository
- private lateinit var isDozingUseCase: ObserveIsDozingUseCase
- private lateinit var isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase
private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig
private lateinit var qrCodeScanner: FakeKeyguardQuickAffordanceConfig
@Before
- fun setUp() = runBlockingTest {
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
repository = FakeKeyguardRepository()
repository.setKeyguardShowing(true)
- isDozingUseCase = ObserveIsDozingUseCase(repository)
- isKeyguardShowingUseCase = ObserveIsKeyguardShowingUseCase(repository)
homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
quickAccessWallet = object : FakeKeyguardQuickAffordanceConfig() {}
qrCodeScanner = object : FakeKeyguardQuickAffordanceConfig() {}
underTest =
- ObserveKeyguardQuickAffordanceUseCaseImpl(
+ KeyguardQuickAffordanceInteractor(
+ keyguardInteractor = KeyguardInteractor(repository = repository),
registry =
FakeKeyguardQuickAffordanceRegistry(
mapOf(
@@ -75,13 +87,15 @@
),
),
),
- isDozingUseCase = isDozingUseCase,
- isKeyguardShowingUseCase = isKeyguardShowingUseCase,
+ lockPatternUtils = lockPatternUtils,
+ keyguardStateController = keyguardStateController,
+ userTracker = userTracker,
+ activityStarter = activityStarter,
)
}
@Test
- fun `invoke - bottom start affordance is visible`() = runBlockingTest {
+ fun `quickAffordance - bottom start affordance is visible`() = runBlockingTest {
val configKey = homeControls::class
homeControls.setState(
KeyguardQuickAffordanceConfig.State.Visible(
@@ -92,7 +106,8 @@
var latest: KeyguardQuickAffordanceModel? = null
val job =
- underTest(KeyguardQuickAffordancePosition.BOTTOM_START)
+ underTest
+ .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
.onEach { latest = it }
.launchIn(this)
@@ -106,7 +121,7 @@
}
@Test
- fun `invoke - bottom end affordance is visible`() = runBlockingTest {
+ fun `quickAffordance - bottom end affordance is visible`() = runBlockingTest {
val configKey = quickAccessWallet::class
quickAccessWallet.setState(
KeyguardQuickAffordanceConfig.State.Visible(
@@ -117,7 +132,8 @@
var latest: KeyguardQuickAffordanceModel? = null
val job =
- underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
+ underTest
+ .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
.onEach { latest = it }
.launchIn(this)
@@ -131,7 +147,7 @@
}
@Test
- fun `invoke - bottom start affordance hidden while dozing`() = runBlockingTest {
+ fun `quickAffordance - bottom start affordance hidden while dozing`() = runBlockingTest {
repository.setDozing(true)
homeControls.setState(
KeyguardQuickAffordanceConfig.State.Visible(
@@ -142,7 +158,8 @@
var latest: KeyguardQuickAffordanceModel? = null
val job =
- underTest(KeyguardQuickAffordancePosition.BOTTOM_START)
+ underTest
+ .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
.onEach { latest = it }
.launchIn(this)
assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
@@ -150,7 +167,7 @@
}
@Test
- fun `invoke - bottom start affordance hidden when lockscreen is not showing`() =
+ fun `quickAffordance - bottom start affordance hidden when lockscreen is not showing`() =
runBlockingTest {
repository.setKeyguardShowing(false)
homeControls.setState(
@@ -162,7 +179,8 @@
var latest: KeyguardQuickAffordanceModel? = null
val job =
- underTest(KeyguardQuickAffordancePosition.BOTTOM_START)
+ underTest
+ .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
.onEach { latest = it }
.launchIn(this)
assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCaseImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCaseImplTest.kt
deleted file mode 100644
index b3c1ae0..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCaseImplTest.kt
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2022 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.keyguard.domain.usecase
-
-import android.content.Intent
-import androidx.test.filters.SmallTest
-import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import org.junit.runners.Parameterized.Parameter
-import org.junit.runners.Parameterized.Parameters
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(Parameterized::class)
-class LaunchKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
-
- companion object {
- private val INTENT = Intent("some.intent.action")
-
- @Parameters(
- name =
- "needStrongAuthAfterBoot={0}, canShowWhileLocked={1}," +
- " keyguardIsUnlocked={2}, needsToUnlockFirst={3}"
- )
- @JvmStatic
- fun data() =
- listOf(
- arrayOf(
- /* needStrongAuthAfterBoot= */ false,
- /* canShowWhileLocked= */ false,
- /* keyguardIsUnlocked= */ false,
- /* needsToUnlockFirst= */ true,
- ),
- arrayOf(
- /* needStrongAuthAfterBoot= */ false,
- /* canShowWhileLocked= */ false,
- /* keyguardIsUnlocked= */ true,
- /* needsToUnlockFirst= */ false,
- ),
- arrayOf(
- /* needStrongAuthAfterBoot= */ false,
- /* canShowWhileLocked= */ true,
- /* keyguardIsUnlocked= */ false,
- /* needsToUnlockFirst= */ false,
- ),
- arrayOf(
- /* needStrongAuthAfterBoot= */ false,
- /* canShowWhileLocked= */ true,
- /* keyguardIsUnlocked= */ true,
- /* needsToUnlockFirst= */ false,
- ),
- arrayOf(
- /* needStrongAuthAfterBoot= */ true,
- /* canShowWhileLocked= */ false,
- /* keyguardIsUnlocked= */ false,
- /* needsToUnlockFirst= */ true,
- ),
- arrayOf(
- /* needStrongAuthAfterBoot= */ true,
- /* canShowWhileLocked= */ false,
- /* keyguardIsUnlocked= */ true,
- /* needsToUnlockFirst= */ true,
- ),
- arrayOf(
- /* needStrongAuthAfterBoot= */ true,
- /* canShowWhileLocked= */ true,
- /* keyguardIsUnlocked= */ false,
- /* needsToUnlockFirst= */ true,
- ),
- arrayOf(
- /* needStrongAuthAfterBoot= */ true,
- /* canShowWhileLocked= */ true,
- /* keyguardIsUnlocked= */ true,
- /* needsToUnlockFirst= */ true,
- ),
- )
- }
-
- @Mock private lateinit var lockPatternUtils: LockPatternUtils
- @Mock private lateinit var keyguardStateController: KeyguardStateController
- @Mock private lateinit var userTracker: UserTracker
- @Mock private lateinit var activityStarter: ActivityStarter
- @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
-
- private lateinit var underTest: LaunchKeyguardQuickAffordanceUseCase
-
- @JvmField @Parameter(0) var needStrongAuthAfterBoot: Boolean = false
- @JvmField @Parameter(1) var canShowWhileLocked: Boolean = false
- @JvmField @Parameter(2) var keyguardIsUnlocked: Boolean = false
- @JvmField @Parameter(3) var needsToUnlockFirst: Boolean = false
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- underTest =
- LaunchKeyguardQuickAffordanceUseCaseImpl(
- lockPatternUtils = lockPatternUtils,
- keyguardStateController = keyguardStateController,
- userTracker = userTracker,
- activityStarter = activityStarter,
- )
- }
-
- @Test
- fun invoke() {
- setUpMocks(
- needStrongAuthAfterBoot = needStrongAuthAfterBoot,
- keyguardIsUnlocked = keyguardIsUnlocked,
- )
-
- underTest(
- intent = INTENT,
- canShowWhileLocked = canShowWhileLocked,
- animationController = animationController,
- )
-
- if (needsToUnlockFirst) {
- verify(activityStarter)
- .postStartActivityDismissingKeyguard(
- INTENT,
- /* delay= */ 0,
- animationController,
- )
- } else {
- verify(activityStarter)
- .startActivity(
- INTENT,
- /* dismissShade= */ true,
- animationController,
- /* showOverLockscreenWhenLocked= */ true,
- )
- }
- }
-
- private fun setUpMocks(
- needStrongAuthAfterBoot: Boolean = true,
- keyguardIsUnlocked: Boolean = false,
- ) {
- whenever(userTracker.userHandle).thenReturn(mock())
- whenever(lockPatternUtils.getStrongAuthForUser(any()))
- .thenReturn(
- if (needStrongAuthAfterBoot) {
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
- } else {
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
- }
- )
- whenever(keyguardStateController.isUnlocked).thenReturn(keyguardIsUnlocked)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 8758ce5..19491f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -18,24 +18,22 @@
import android.content.Intent
import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.containeddrawable.ContainedDrawable
import com.android.systemui.doze.util.BurnInHelperWrapper
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.domain.usecase.FakeLaunchKeyguardQuickAffordanceUseCase
-import com.android.systemui.keyguard.domain.usecase.FakeObserveKeyguardQuickAffordanceUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveAnimateBottomAreaTransitionsUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveBottomAreaAlphaUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveClockPositionUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveDozeAmountUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveIsDozingUseCase
-import com.android.systemui.keyguard.domain.usecase.OnKeyguardQuickAffordanceClickedUseCase
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
@@ -43,12 +41,15 @@
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.yield
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -58,17 +59,18 @@
@Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
@Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var activityStarter: ActivityStarter
private lateinit var underTest: KeyguardBottomAreaViewModel
private lateinit var repository: FakeKeyguardRepository
private lateinit var registry: FakeKeyguardQuickAffordanceRegistry
- private lateinit var isDozingUseCase: ObserveIsDozingUseCase
- private lateinit var launchQuickAffordanceUseCase: FakeLaunchKeyguardQuickAffordanceUseCase
private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig
private lateinit var qrCodeScannerAffordanceConfig: FakeKeyguardQuickAffordanceConfig
- private lateinit var observeQuickAffordanceUseCase: FakeObserveKeyguardQuickAffordanceUseCase
@Before
fun setUp() {
@@ -94,57 +96,31 @@
),
)
repository = FakeKeyguardRepository()
- isDozingUseCase =
- ObserveIsDozingUseCase(
- repository = repository,
- )
- launchQuickAffordanceUseCase = FakeLaunchKeyguardQuickAffordanceUseCase()
- observeQuickAffordanceUseCase = FakeObserveKeyguardQuickAffordanceUseCase()
+ val keyguardInteractor = KeyguardInteractor(repository = repository)
+ whenever(userTracker.userHandle).thenReturn(mock())
+ whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
+ .thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
underTest =
KeyguardBottomAreaViewModel(
- observeQuickAffordanceUseCase = observeQuickAffordanceUseCase,
- onQuickAffordanceClickedUseCase =
- OnKeyguardQuickAffordanceClickedUseCase(
- registry =
- FakeKeyguardQuickAffordanceRegistry(
- mapOf(
- KeyguardQuickAffordancePosition.BOTTOM_START to
- listOf(
- homeControlsQuickAffordanceConfig,
- ),
- KeyguardQuickAffordancePosition.BOTTOM_END to
- listOf(
- quickAccessWalletAffordanceConfig,
- qrCodeScannerAffordanceConfig,
- ),
- ),
- ),
- launchAffordanceUseCase = launchQuickAffordanceUseCase,
+ keyguardInteractor = keyguardInteractor,
+ quickAffordanceInteractor =
+ KeyguardQuickAffordanceInteractor(
+ keyguardInteractor = keyguardInteractor,
+ registry = registry,
+ lockPatternUtils = lockPatternUtils,
+ keyguardStateController = keyguardStateController,
+ userTracker = userTracker,
+ activityStarter = activityStarter,
),
- observeBottomAreaAlphaUseCase =
- ObserveBottomAreaAlphaUseCase(
- repository = repository,
- ),
- observeIsDozingUseCase = isDozingUseCase,
- observeAnimateBottomAreaTransitionsUseCase =
- ObserveAnimateBottomAreaTransitionsUseCase(
- repository = repository,
- ),
- observeDozeAmountUseCase =
- ObserveDozeAmountUseCase(
- repository = repository,
- ),
- observeClockPositionUseCase =
- ObserveClockPositionUseCase(
- repository = repository,
- ),
+ bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
burnInHelperWrapper = burnInHelperWrapper,
)
}
@Test
fun `startButton - present - visible model - starts activity on click`() = runBlockingTest {
+ repository.setKeyguardShowing(true)
var latest: KeyguardQuickAffordanceViewModel? = null
val job = underTest.startButton.onEach { latest = it }.launchIn(this)
@@ -171,6 +147,7 @@
@Test
fun `endButton - present - visible model - do nothing on click`() = runBlockingTest {
+ repository.setKeyguardShowing(true)
var latest: KeyguardQuickAffordanceViewModel? = null
val job = underTest.endButton.onEach { latest = it }.launchIn(this)
@@ -220,11 +197,27 @@
@Test
fun animateButtonReveal() = runBlockingTest {
+ repository.setKeyguardShowing(true)
+ val testConfig =
+ TestConfig(
+ isVisible = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ intent = Intent("action"),
+ )
+
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = testConfig,
+ )
+
val values = mutableListOf<Boolean>()
- val job = underTest.animateButtonReveal.onEach(values::add).launchIn(this)
+ val job = underTest.startButton.onEach { values.add(it.animateReveal) }.launchIn(this)
repository.setAnimateDozingTransitions(true)
+ yield()
repository.setAnimateDozingTransitions(false)
+ yield()
assertThat(values).isEqualTo(listOf(false, true, false))
job.cancel()
@@ -357,7 +350,7 @@
private suspend fun setUpQuickAffordanceModel(
position: KeyguardQuickAffordancePosition,
testConfig: TestConfig,
- ): KClass<*> {
+ ): KClass<out FakeKeyguardQuickAffordanceConfig> {
val config =
when (position) {
KeyguardQuickAffordancePosition.BOTTOM_START -> homeControlsQuickAffordanceConfig
@@ -381,20 +374,13 @@
KeyguardQuickAffordanceConfig.State.Hidden
}
config.setState(state)
-
- val configKey = config::class
- observeQuickAffordanceUseCase.setModel(
- position,
- KeyguardQuickAffordanceModel.from(state, configKey)
- )
-
- return configKey
+ return config::class
}
private fun assertQuickAffordanceViewModel(
viewModel: KeyguardQuickAffordanceViewModel?,
testConfig: TestConfig,
- configKey: KClass<*>,
+ configKey: KClass<out FakeKeyguardQuickAffordanceConfig>,
) {
checkNotNull(viewModel)
assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible)
@@ -406,19 +392,11 @@
animationController = animationController,
)
)
- testConfig.intent?.let { intent ->
- assertThat(launchQuickAffordanceUseCase.invocations)
- .isEqualTo(
- listOf(
- FakeLaunchKeyguardQuickAffordanceUseCase.Invocation(
- intent = intent,
- canShowWhileLocked = testConfig.canShowWhileLocked,
- animationController = animationController,
- )
- )
- )
+ if (testConfig.intent != null) {
+ assertThat(Mockito.mockingDetails(activityStarter).invocations).hasSize(1)
+ } else {
+ verifyZeroInteractions(activityStarter)
}
- ?: run { assertThat(launchQuickAffordanceUseCase.invocations).isEmpty() }
} else {
assertThat(viewModel.isVisible).isFalse()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index dbc5f7c..171d893 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -241,5 +241,5 @@
private val routeInfo = MediaRoute2Info.Builder("id", "Test route name")
.addFeature("feature")
- .setPackageName(PACKAGE_NAME)
+ .setClientPackageName(PACKAGE_NAME)
.build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index cd8ee73..1061e3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -686,5 +686,5 @@
private val routeInfo = MediaRoute2Info.Builder("id", OTHER_DEVICE_NAME)
.addFeature("feature")
- .setPackageName(PACKAGE_NAME)
+ .setClientPackageName(PACKAGE_NAME)
.build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index fc28349..3f4e2a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -98,9 +98,7 @@
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.domain.usecase.SetClockPositionUseCase;
-import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAlphaUseCase;
-import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
+import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.media.MediaDataManager;
@@ -379,10 +377,7 @@
@Mock
private ViewTreeObserver mViewTreeObserver;
@Mock private KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
- @Mock private SetClockPositionUseCase mSetClockPositionUseCase;
- @Mock private SetKeyguardBottomAreaAlphaUseCase mSetKeyguardBottomAreaAlphaUseCase;
- @Mock private SetKeyguardBottomAreaAnimateDozingTransitionsUseCase
- mSetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
+ @Mock private KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
private NotificationPanelViewController.PanelEventsEmitter mPanelEventsEmitter;
private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
private SysuiStatusBarStateController mStatusBarStateController;
@@ -577,9 +572,7 @@
mSystemClock,
mock(CameraGestureHelper.class),
() -> mKeyguardBottomAreaViewModel,
- () -> mSetClockPositionUseCase,
- () -> mSetKeyguardBottomAreaAlphaUseCase,
- () -> mSetKeyguardBottomAreaAnimateDozingTransitionsUseCase);
+ () -> mKeyguardBottomAreaInteractor);
mNotificationPanelViewController.initDependencies(
mCentralSurfaces,
() -> {},
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt
new file mode 100644
index 0000000..8275c0c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.collection
+
+import android.app.Notification
+import android.app.NotificationManager
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.os.UserHandle
+import android.service.notification.NotificationListenerService.Ranking
+import android.service.notification.StatusBarNotification
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.withArgCaptor
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when` as whenever
+
+private const val SDK_VERSION = 33
+private const val PACKAGE = "pkg"
+private const val USER_ID = -1
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TargetSdkResolverTest : SysuiTestCase() {
+ private val packageManager: PackageManager = mock()
+ private val applicationInfo = ApplicationInfo().apply { targetSdkVersion = SDK_VERSION }
+
+ private lateinit var targetSdkResolver: TargetSdkResolver
+ private lateinit var notifListener: NotifCollectionListener
+
+ @Before
+ fun setUp() {
+ targetSdkResolver = TargetSdkResolver(mContext)
+ mContext.setMockPackageManager(packageManager)
+
+ val notifCollection: CommonNotifCollection = mock()
+ targetSdkResolver.initialize(notifCollection)
+ notifListener = withArgCaptor {
+ verify(notifCollection).addCollectionListener(capture())
+ }
+ }
+
+ @Test
+ fun resolveFromNotificationExtras() {
+ val extras = Bundle().apply {
+ putParcelable(Notification.EXTRA_BUILDER_APPLICATION_INFO, applicationInfo)
+ }
+ val notification = Notification().apply { this.extras = extras }
+ val sbn = createSbn(notification)
+ val entry = createNotificationEntry(sbn)
+
+ notifListener.onEntryBind(entry, sbn)
+
+ assertEquals(SDK_VERSION, entry.targetSdk)
+ verifyZeroInteractions(packageManager)
+ }
+
+ @Test
+ fun resolveFromPackageManager() {
+ val sbn = createSbn(Notification())
+ val entry = createNotificationEntry(sbn)
+ whenever(packageManager.getApplicationInfo(anyString(), anyInt()))
+ .thenReturn(applicationInfo)
+
+ notifListener.onEntryBind(entry, sbn)
+
+ assertEquals(SDK_VERSION, entry.targetSdk)
+ verify(packageManager).getApplicationInfo(eq(PACKAGE), anyInt())
+ }
+
+ @Test
+ fun resolveFromPackageManager_andPackageManagerCrashes() {
+ val sbn = createSbn(Notification())
+ val entry = createNotificationEntry(sbn)
+ whenever(packageManager.getApplicationInfo(anyString(), anyInt()))
+ .thenThrow(PackageManager.NameNotFoundException())
+
+ notifListener.onEntryBind(entry, sbn)
+
+ assertEquals(0, entry.targetSdk)
+ verify(packageManager).getApplicationInfo(eq(PACKAGE), anyInt())
+ }
+
+ private fun createSbn(notification: Notification) = StatusBarNotification(
+ PACKAGE, "opPkg", 0, "tag", 0, 0,
+ notification, UserHandle(USER_ID), "", 0
+ )
+
+ private fun createNotificationEntry(sbn: StatusBarNotification) =
+ NotificationEntry(sbn, createRanking(sbn.key), 0)
+
+ private fun createRanking(key: String) = Ranking().apply {
+ populate(
+ key,
+ 0,
+ false,
+ 0,
+ 0,
+ NotificationManager.IMPORTANCE_DEFAULT,
+ null, null,
+ null, null, null, true, 0, false, -1, false, null, null, false, false,
+ false, null, 0, false)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
index 541749b4..d59cc54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.interruption;
+import static android.app.Notification.VISIBILITY_PRIVATE;
import static android.app.Notification.VISIBILITY_PUBLIC;
import static android.app.Notification.VISIBILITY_SECRET;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
@@ -449,6 +450,54 @@
}
@Test
+ public void notificationVisibilityPublic() {
+ // GIVEN a VISIBILITY_PUBLIC notification
+ NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
+ .setUser(new UserHandle(NOTIF_USER_ID));
+ entryBuilder.modifyNotification(mContext)
+ .setVisibility(VISIBILITY_PUBLIC);
+ mEntry = entryBuilder.build();
+
+ // WHEN we're in an 'unfiltered-keyguard-showing' state
+ setupUnfilteredState(mEntry);
+
+ // THEN don't hide the entry based on visibility.
+ assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+ }
+
+ @Test
+ public void notificationVisibilityPrivate() {
+ // GIVEN a VISIBILITY_PRIVATE notification
+ NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
+ .setUser(new UserHandle(NOTIF_USER_ID));
+ entryBuilder.modifyNotification(mContext)
+ .setVisibility(VISIBILITY_PRIVATE);
+ mEntry = entryBuilder.build();
+
+ // WHEN we're in an 'unfiltered-keyguard-showing' state
+ setupUnfilteredState(mEntry);
+
+ // THEN don't hide the entry based on visibility. (Redaction is handled elsewhere.)
+ assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+ }
+
+ @Test
+ public void notificationVisibilitySecret() {
+ // GIVEN a VISIBILITY_SECRET notification
+ NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
+ .setUser(new UserHandle(NOTIF_USER_ID));
+ entryBuilder.modifyNotification(mContext)
+ .setVisibility(VISIBILITY_SECRET);
+ mEntry = entryBuilder.build();
+
+ // WHEN we're in an 'unfiltered-keyguard-showing' state
+ setupUnfilteredState(mEntry);
+
+ // THEN hide the entry based on visibility.
+ assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+ }
+
+ @Test
public void summaryExceedsThresholdToShow() {
// GIVEN the notification doesn't exceed the threshold to show on the lockscreen
// but it's part of a group (has a parent)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 381d72f..90adabf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -236,7 +236,6 @@
@Test
public void testBindNotification_SetsShortcutIcon() {
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -261,7 +260,6 @@
public void testBindNotification_SetsTextApplicationName() {
when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -314,7 +312,6 @@
mConversationChannel.setGroup(group.getId());
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -340,7 +337,6 @@
@Test
public void testBindNotification_GroupNameHiddenIfNoGroup() {
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -365,7 +361,6 @@
@Test
public void testBindNotification_noDelegate() {
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -401,7 +396,6 @@
.setShortcutInfo(mShortcutInfo)
.build();
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -427,7 +421,6 @@
public void testBindNotification_SetsOnClickListenerForSettings() {
final CountDownLatch latch = new CountDownLatch(1);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -457,7 +450,6 @@
@Test
public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() {
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -482,7 +474,6 @@
public void testBindNotification_SettingsButtonInvisibleWhenDeviceUnprovisioned() {
final CountDownLatch latch = new CountDownLatch(1);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -511,7 +502,6 @@
mConversationChannel.setImportance(IMPORTANCE_LOW);
mConversationChannel.setImportantConversation(true);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -540,7 +530,6 @@
mConversationChannel.setImportantConversation(false);
mConversationChannel.setAllowBubbles(true);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -572,7 +561,6 @@
mConversationChannel.setImportantConversation(false);
mConversationChannel.setAllowBubbles(true);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -610,7 +598,6 @@
mConversationChannel.setImportantConversation(false);
mConversationChannel.setAllowBubbles(false);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -639,7 +626,6 @@
mConversationChannel.setImportantConversation(false);
mConversationChannel.setAllowBubbles(false);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -675,7 +661,6 @@
mConversationChannel.setImportantConversation(false);
mConversationChannel.setAllowBubbles(true);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -704,7 +689,6 @@
mConversationChannel.setImportantConversation(false);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -735,7 +719,7 @@
.isEqualTo(GONE);
// no changes until hit done
- assertFalse(mNotificationInfo.shouldBeSaved());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
assertFalse(mConversationChannel.isImportantConversation());
@@ -749,7 +733,6 @@
mConversationChannel.setImportance(IMPORTANCE_LOW);
mConversationChannel.setImportantConversation(false);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -779,7 +762,7 @@
.isEqualTo(GONE);
// no changes until hit done
- assertFalse(mNotificationInfo.shouldBeSaved());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
assertFalse(mConversationChannel.isImportantConversation());
@@ -793,7 +776,6 @@
mConversationChannel.setImportantConversation(false);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -825,7 +807,7 @@
.isEqualTo(VISIBLE);
// no changes until save
- assertFalse(mNotificationInfo.shouldBeSaved());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
assertEquals(IMPORTANCE_DEFAULT, mConversationChannel.getImportance());
@@ -838,7 +820,6 @@
mConversationChannel.setImportantConversation(false);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -868,6 +849,7 @@
assertTrue(captor.getValue().isImportantConversation());
assertTrue(captor.getValue().canBubble());
assertEquals(IMPORTANCE_DEFAULT, captor.getValue().getImportance());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
}
@Test
@@ -876,7 +858,6 @@
mConversationChannel.setImportance(9);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -913,7 +894,6 @@
mConversationChannel.setImportantConversation(true);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -954,7 +934,6 @@
// WHEN we indicate no selected action
mNotificationInfo.bindNotification(
- -1, // no action selected by default
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -984,8 +963,8 @@
mConversationChannel.setImportantConversation(false);
// WHEN we indicate the selected action should be "Favorite"
+ mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE);
mNotificationInfo.bindNotification(
- NotificationConversationInfo.ACTION_FAVORITE, // "Favorite" selected by default
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -1015,7 +994,6 @@
mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH);
mConversationChannel.setImportantConversation(true);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -1044,6 +1022,7 @@
assertFalse(captor.getValue().isImportantConversation());
assertFalse(captor.getValue().canBubble());
assertEquals(IMPORTANCE_HIGH, captor.getValue().getImportance());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
}
@Test
@@ -1052,7 +1031,6 @@
mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH);
mConversationChannel.setImportantConversation(false);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -1089,7 +1067,6 @@
mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -1125,7 +1102,6 @@
mConversationChannel.setAllowBubbles(true);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -1155,12 +1131,46 @@
assertFalse(captor.getValue().isImportantConversation());
assertFalse(captor.getValue().canBubble());
assertEquals(IMPORTANCE_LOW, captor.getValue().getImportance());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
+ }
+
+ @Test
+ public void testSilence_closeGutsThenTryToSave() {
+ mConversationChannel.setImportance(IMPORTANCE_DEFAULT);
+ mConversationChannel.setImportantConversation(true);
+ mConversationChannel.setAllowBubbles(true);
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mMockPackageManager,
+ mPeopleSpaceWidgetManager,
+ mMockINotificationManager,
+ mOnUserInteractionCallback,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ mBubbleMetadata,
+ null,
+ mIconFactory,
+ mContext,
+ true,
+ mTestHandler,
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
+
+ mNotificationInfo.findViewById(R.id.silence).performClick();
+ mNotificationInfo.handleCloseControls(false, false);
+ mNotificationInfo.findViewById(R.id.done).performClick();
+
+ mTestableLooper.processAllMessages();
+
+ assertEquals(IMPORTANCE_DEFAULT, mConversationChannel.getImportance());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
}
@Test
public void testBindNotification_createsNewChannel() throws Exception {
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -1186,7 +1196,6 @@
public void testBindNotification_doesNotCreateNewChannelIfExists() throws Exception {
mNotificationChannel.setConversationId("", CONVERSATION_ID);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -1213,7 +1222,6 @@
//WHEN channel is default importance
mNotificationChannel.setImportantConversation(false);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -1244,7 +1252,6 @@
@Test
public void testSelectDefaultDoesNotRequestPinPeopleTile() {
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -1279,7 +1286,6 @@
mConversationChannel.setImportantConversation(true);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
new file mode 100644
index 0000000..e696c87
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.testing.ViewUtils
+import android.view.LayoutInflater
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationGutsTest : SysuiTestCase() {
+
+ private lateinit var guts: NotificationGuts
+ private lateinit var gutsContentView: View
+
+ @Mock
+ private lateinit var gutsContent: NotificationGuts.GutsContent
+
+ @Mock
+ private lateinit var gutsClosedListener: NotificationGuts.OnGutsClosedListener
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ val layoutInflater = LayoutInflater.from(mContext)
+ guts = layoutInflater.inflate(R.layout.notification_guts, null) as NotificationGuts
+ gutsContentView = View(mContext)
+
+ whenever(gutsContent.contentView).thenReturn(gutsContentView)
+
+ ViewUtils.attachView(guts)
+ }
+
+ @After
+ fun tearDown() {
+ ViewUtils.detachView(guts)
+ }
+
+ @Test
+ fun setGutsContent() {
+ guts.gutsContent = gutsContent
+
+ verify(gutsContent).setGutsParent(guts)
+ }
+
+ @Test
+ fun openControls() {
+ guts.gutsContent = gutsContent
+
+ guts.openControls(true, 0, 0, false, null)
+ }
+
+ @Test
+ fun closeControlsWithSave() {
+ guts.gutsContent = gutsContent
+ guts.setClosedListener(gutsClosedListener)
+
+ guts.closeControls(gutsContentView, true)
+
+ verify(gutsContent).handleCloseControls(true, false)
+ verify(gutsClosedListener).onGutsClosed(guts)
+ }
+
+ @Test
+ fun closeControlsWithoutSave() {
+ guts.gutsContent = gutsContent
+ guts.setClosedListener(gutsClosedListener)
+
+ guts.closeControls(gutsContentView, false)
+
+ verify(gutsContent).handleCloseControls(false, false)
+ verify(gutsClosedListener).onGutsClosed(guts)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index b1f1075..80a81a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -50,6 +50,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.telecom.TelecomManager;
@@ -1090,6 +1091,7 @@
mUiEventLogger.eventId(0));
assertEquals(NotificationControlsEvent.NOTIFICATION_CONTROLS_SAVE_IMPORTANCE.getId(),
mUiEventLogger.eventId(1));
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
}
@Test
@@ -1124,6 +1126,7 @@
assertTrue((updated.getValue().getUserLockedFields()
& USER_LOCKED_IMPORTANCE) != 0);
assertEquals(IMPORTANCE_DEFAULT, updated.getValue().getImportance());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
}
@Test
@@ -1156,6 +1159,7 @@
verify(mMockINotificationManager, times(1)).unlockNotificationChannel(
anyString(), eq(TEST_UID), any());
assertEquals(IMPORTANCE_DEFAULT, mNotificationChannel.getImportance());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
}
@Test
@@ -1191,6 +1195,7 @@
assertTrue((updated.getValue().getUserLockedFields()
& USER_LOCKED_IMPORTANCE) != 0);
assertEquals(IMPORTANCE_LOW, updated.getValue().getImportance());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
}
@Test
@@ -1229,6 +1234,37 @@
anyString(), eq(TEST_UID), updated.capture());
assertTrue((updated.getValue().getUserLockedFields() & USER_LOCKED_IMPORTANCE) != 0);
assertEquals(IMPORTANCE_MIN, updated.getValue().getImportance());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
+ }
+
+ @Test
+ public void testSilence_closeGutsThenTryToSave() throws RemoteException {
+ mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
+ mNotificationInfo.bindNotification(
+ mMockPackageManager,
+ mMockINotificationManager,
+ mOnUserInteractionCallback,
+ mChannelEditorDialogController,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mNotificationChannelSet,
+ mEntry,
+ null,
+ null,
+ mUiEventLogger,
+ true,
+ false,
+ false,
+ mAssistantFeedbackController);
+
+ mNotificationInfo.findViewById(R.id.silence).performClick();
+ mNotificationInfo.handleCloseControls(false, false);
+ mNotificationInfo.handleCloseControls(true, false);
+
+ mTestableLooper.processAllMessages();
+
+ assertEquals(IMPORTANCE_DEFAULT, mNotificationChannel.getImportance());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
}
@Test
@@ -1267,6 +1303,7 @@
anyString(), eq(TEST_UID), updated.capture());
assertTrue((updated.getValue().getUserLockedFields() & USER_LOCKED_IMPORTANCE) != 0);
assertEquals(IMPORTANCE_DEFAULT, updated.getValue().getImportance());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
}
@Test
@@ -1294,6 +1331,7 @@
mNotificationInfo.handleCloseControls(true, false);
verify(mOnUserInteractionCallback).onImportanceChanged(mEntry);
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
}
@Test
@@ -1360,6 +1398,7 @@
assertTrue((updated.getValue().getUserLockedFields()
& USER_LOCKED_IMPORTANCE) != 0);
assertEquals(IMPORTANCE_DEFAULT, updated.getValue().getImportance());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
}
@Test
@@ -1450,7 +1489,7 @@
mNotificationInfo.findViewById(R.id.alert).performClick();
- assertFalse(mNotificationInfo.shouldBeSaved());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
index 43aa8fe..12c8fd5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification.row;
-import static android.app.Notification.EXTRA_IS_GROUP_CONVERSATION;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.print.PrintManager.PRINT_SPOOLER_PACKAGE_NAME;
import static android.view.View.GONE;
@@ -25,7 +24,6 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
@@ -36,8 +34,6 @@
import android.app.INotificationManager;
import android.app.Notification;
import android.app.NotificationChannel;
-import android.app.NotificationChannelGroup;
-import android.app.Person;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
diff --git a/proto/src/OWNERS b/proto/src/OWNERS
index b456ba6..abd08de 100644
--- a/proto/src/OWNERS
+++ b/proto/src/OWNERS
@@ -1,3 +1,4 @@
per-file gnss.proto = file:/services/core/java/com/android/server/location/OWNERS
per-file wifi.proto = file:/wifi/OWNERS
per-file camera.proto = file:/services/core/java/com/android/server/camera/OWNERS
+per-file system_messages.proto = file:/core/res/OWNERS
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 3324c52..b34482f 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -354,24 +354,16 @@
if (supportsFlagForNotImportantViews(info)) {
if ((info.flags & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0) {
- mFetchFlags |=
- AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS;
+ mFetchFlags |= AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
} else {
- mFetchFlags &=
- ~AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS;
+ mFetchFlags &= ~AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
}
}
if ((info.flags & AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS) != 0) {
- mFetchFlags |= AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS;
+ mFetchFlags |= AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
} else {
- mFetchFlags &= ~AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS;
- }
-
- if (mAccessibilityServiceInfo.isAccessibilityTool()) {
- mFetchFlags |= AccessibilityNodeInfo.FLAG_SERVICE_IS_ACCESSIBILITY_TOOL;
- } else {
- mFetchFlags &= ~AccessibilityNodeInfo.FLAG_SERVICE_IS_ACCESSIBILITY_TOOL;
+ mFetchFlags &= ~AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
}
mRequestTouchExplorationMode = (info.flags
@@ -1530,16 +1522,9 @@
return false;
}
- final boolean includeNotImportantViews = (mFetchFlags
- & AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
if ((event.getWindowId() != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID)
&& !event.isImportantForAccessibility()
- && !includeNotImportantViews) {
- return false;
- }
-
- if (event.isAccessibilityDataPrivate()
- && (mFetchFlags & AccessibilityNodeInfo.FLAG_SERVICE_IS_ACCESSIBILITY_TOOL) == 0) {
+ && (mFetchFlags & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) == 0) {
return false;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 6a6d2bb..6eabc98 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3693,7 +3693,6 @@
info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT);
info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
- info.setAccessibilityTool(true);
final AccessibilityUserState userState;
synchronized (mLock) {
userState = getCurrentUserStateLocked();
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index dac23a7..3d8dc14 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -975,11 +975,6 @@
*/
public abstract void setEnableRollbackCode(int token, int enableRollbackCode);
- /**
- * Ask the package manager to compile layouts in the given package.
- */
- public abstract boolean compileLayouts(String packageName);
-
/*
* Inform the package manager that the pending package install identified by
* {@code token} can be completed.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 54a7811..297e6a2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -459,6 +459,7 @@
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiFunction;
public class ActivityManagerService extends IActivityManager.Stub
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback, ActivityManagerGlobalLock {
@@ -4304,7 +4305,8 @@
null /* excludedPackages */, OP_NONE, null /* bOptions */, false /* ordered */,
false /* sticky */, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
Binder.getCallingPid(), userId, false /* allowBackgroundActivityStarts */,
- null /* backgroundActivityStartsToken */, broadcastAllowList);
+ null /* backgroundActivityStartsToken */,
+ broadcastAllowList, null /* filterExtrasForReceiver */);
}
private void cleanupDisabledPackageComponentsLocked(
@@ -13357,7 +13359,8 @@
BroadcastRecord r = new BroadcastRecord(queue, intent, null,
null, null, -1, -1, false, null, null, null, null, OP_NONE, null,
receivers, null, 0, null, null, false, true, true, -1, false, null,
- false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */);
+ false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */,
+ null /* filterExtrasForReceiver */);
queue.enqueueParallelBroadcastLocked(r);
queue.scheduleBroadcastsLocked();
}
@@ -13620,7 +13623,8 @@
excludedPermissions, excludedPackages, appOp, bOptions, ordered, sticky, callingPid,
callingUid, realCallingUid, realCallingPid, userId,
false /* allowBackgroundActivityStarts */,
- null /* tokenNeededForBackgroundActivityStarts */, null /* broadcastAllowList */);
+ null /* tokenNeededForBackgroundActivityStarts */,
+ null /* broadcastAllowList */, null /* filterExtrasForReceiver */);
}
@GuardedBy("this")
@@ -13633,7 +13637,8 @@
int realCallingUid, int realCallingPid, int userId,
boolean allowBackgroundActivityStarts,
@Nullable IBinder backgroundActivityStartsToken,
- @Nullable int[] broadcastAllowList) {
+ @Nullable int[] broadcastAllowList,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
intent = new Intent(intent);
final boolean callerInstantApp = isInstantApp(callerApp, callerPackage, callingUid);
@@ -14233,7 +14238,7 @@
requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
registeredReceivers, resultTo, resultCode, resultData, resultExtras, ordered,
sticky, false, userId, allowBackgroundActivityStarts,
- backgroundActivityStartsToken, timeoutExempt);
+ backgroundActivityStartsToken, timeoutExempt, filterExtrasForReceiver);
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing parallel broadcast " + r);
final boolean replaced = replacePending
&& (queue.replaceParallelBroadcastLocked(r) != null);
@@ -14331,7 +14336,7 @@
requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
receivers, resultTo, resultCode, resultData, resultExtras,
ordered, sticky, false, userId, allowBackgroundActivityStarts,
- backgroundActivityStartsToken, timeoutExempt);
+ backgroundActivityStartsToken, timeoutExempt, filterExtrasForReceiver);
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r);
@@ -14523,7 +14528,8 @@
resultTo, resultCode, resultData, resultExtras, requiredPermissions, null,
null, OP_NONE, bOptions, serialized, sticky, -1, uid, realCallingUid,
realCallingPid, userId, allowBackgroundActivityStarts,
- backgroundActivityStartsToken, broadcastAllowList);
+ backgroundActivityStartsToken, broadcastAllowList,
+ null /* filterExtrasForReceiver */);
} finally {
Binder.restoreCallingIdentity(origId);
}
@@ -16201,6 +16207,13 @@
return mUserController.startUser(userId, /* foreground */ true, unlockListener);
}
+ @Override
+ public boolean startUserInBackgroundOnSecondaryDisplay(int userId, int displayId) {
+ // Permission check done inside UserController.
+ return mUserController.startUserOnSecondaryDisplay(userId, displayId,
+ /* unlockListener= */ null);
+ }
+
/**
* Unlocks the given user.
*
@@ -17058,7 +17071,9 @@
public int broadcastIntent(Intent intent,
IIntentReceiver resultTo,
String[] requiredPermissions,
- boolean serialized, int userId, int[] appIdAllowList, @Nullable Bundle bOptions) {
+ boolean serialized, int userId, int[] appIdAllowList,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+ @Nullable Bundle bOptions) {
synchronized (ActivityManagerService.this) {
intent = verifyBroadcastLocked(intent);
@@ -17074,7 +17089,8 @@
AppOpsManager.OP_NONE, bOptions /*options*/, serialized,
false /*sticky*/, callingPid, callingUid, callingUid, callingPid,
userId, false /*allowBackgroundStarts*/,
- null /*tokenNeededForBackgroundActivityStarts*/, appIdAllowList);
+ null /*tokenNeededForBackgroundActivityStarts*/,
+ appIdAllowList, filterExtrasForReceiver);
} finally {
Binder.restoreCallingIdentity(origId);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 4bbfa3e..5cb25d3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -892,6 +892,7 @@
}
}
+ // TODO(b/239982558): might need to support --displayId as well
private int runProfile(PrintWriter pw) throws RemoteException {
final PrintWriter err = getErrPrintWriter();
String profileFile = null;
@@ -2034,26 +2035,42 @@
int runStartUser(PrintWriter pw) throws RemoteException {
boolean wait = false;
String opt;
+ int displayId = Display.INVALID_DISPLAY;
while ((opt = getNextOption()) != null) {
- if ("-w".equals(opt)) {
- wait = true;
- } else {
- getErrPrintWriter().println("Error: unknown option: " + opt);
- return -1;
+ switch(opt) {
+ case "-w":
+ wait = true;
+ break;
+ case "--display":
+ displayId = getDisplayIdFromNextArg();
+ break;
+ default:
+ getErrPrintWriter().println("Error: unknown option: " + opt);
+ return -1;
}
}
int userId = Integer.parseInt(getNextArgRequired());
final ProgressWaiter waiter = wait ? new ProgressWaiter() : null;
- boolean success = mInterface.startUserInBackgroundWithListener(userId, waiter);
+
+ boolean success;
+ String displaySuffix;
+
+ if (displayId == Display.INVALID_DISPLAY) {
+ success = mInterface.startUserInBackgroundWithListener(userId, waiter);
+ displaySuffix = "";
+ } else {
+ success = mInterface.startUserInBackgroundOnSecondaryDisplay(userId, displayId);
+ displaySuffix = " on display " + displayId;
+ }
if (wait && success) {
success = waiter.waitForFinish(USER_OPERATION_TIMEOUT_MS);
}
if (success) {
- pw.println("Success: user started");
+ pw.println("Success: user started" + displaySuffix);
} else {
- getErrPrintWriter().println("Error: could not start user");
+ getErrPrintWriter().println("Error: could not start user" + displaySuffix);
}
return 0;
}
@@ -2506,6 +2523,14 @@
}
}
+ private int getDisplayIdFromNextArg() {
+ int displayId = Integer.parseInt(getNextArgRequired());
+ if (displayId < 0) {
+ throw new IllegalArgumentException("--display must be a non-negative integer");
+ }
+ return displayId;
+ }
+
int runGetConfig(PrintWriter pw) throws RemoteException {
int days = -1;
int displayId = Display.DEFAULT_DISPLAY;
@@ -2524,10 +2549,7 @@
} else if (opt.equals("--device")) {
inclDevice = true;
} else if (opt.equals("--display")) {
- displayId = Integer.parseInt(getNextArgRequired());
- if (displayId < 0) {
- throw new IllegalArgumentException("--display must be a non-negative integer");
- }
+ displayId = getDisplayIdFromNextArg();
} else {
getErrPrintWriter().println("Error: Unknown option: " + opt);
return -1;
@@ -3714,10 +3736,13 @@
pw.println(" execution of that user if it is currently stopped.");
pw.println(" get-current-user");
pw.println(" Returns id of the current foreground user.");
- pw.println(" start-user [-w] <USER_ID>");
+ pw.println(" start-user [-w] [--display DISPLAY_ID] <USER_ID>");
pw.println(" Start USER_ID in background if it is currently stopped;");
pw.println(" use switch-user if you want to start the user in foreground.");
pw.println(" -w: wait for start-user to complete and the user to be unlocked.");
+ pw.println(" --display <DISPLAY_ID>: allows the user to launch activities in the");
+ pw.println(" given display, when supported (typically on automotive builds");
+ pw.println(" wherethe vehicle has multiple displays)");
pw.println(" unlock-user <USER_ID>");
pw.println(" Unlock the given user. This will only work if the user doesn't");
pw.println(" have an LSKF (PIN/pattern/password).");
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 9028eef..725d205 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -363,8 +363,8 @@
+ ": " + r);
mService.notifyPackageUse(r.intent.getComponent().getPackageName(),
PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
- thread.scheduleReceiver(new Intent(r.intent), r.curReceiver,
- null /* compatInfo (unused but need to keep method signature) */,
+ thread.scheduleReceiver(prepareReceiverIntent(r.intent, r.curFilteredExtras),
+ r.curReceiver, null /* compatInfo (unused but need to keep method signature) */,
r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
app.mState.getReportedProcState());
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
@@ -592,6 +592,7 @@
r.curFilter = null;
r.curReceiver = null;
r.curApp = null;
+ r.curFilteredExtras = null;
mPendingBroadcast = null;
r.resultCode = resultCode;
@@ -943,6 +944,24 @@
skip = true;
}
+ // Filter packages in the intent extras, skipping delivery if none of the packages is
+ // visible to the receiver.
+ Bundle filteredExtras = null;
+ if (!skip && r.filterExtrasForReceiver != null) {
+ final Bundle extras = r.intent.getExtras();
+ if (extras != null) {
+ filteredExtras = r.filterExtrasForReceiver.apply(filter.receiverList.uid, extras);
+ if (filteredExtras == null) {
+ if (DEBUG_BROADCAST) {
+ Slog.v(TAG, "Skipping delivery to "
+ + filter.receiverList.app
+ + " : receiver is filtered by the package visibility");
+ }
+ skip = true;
+ }
+ }
+ }
+
if (skip) {
r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED;
return;
@@ -1000,7 +1019,7 @@
maybeScheduleTempAllowlistLocked(filter.owningUid, r, r.options);
maybeReportBroadcastDispatchedEventLocked(r, filter.owningUid);
performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
- new Intent(r.intent), r.resultCode, r.resultData,
+ prepareReceiverIntent(r.intent, filteredExtras), r.resultCode, r.resultData,
r.resultExtras, r.ordered, r.initialSticky, r.userId,
filter.receiverList.uid, r.callingUid,
r.dispatchTime - r.enqueueTime,
@@ -1167,6 +1186,15 @@
+ ", uid=" + r.callingUid + ") to " + component.flattenToShortString();
}
+ private static Intent prepareReceiverIntent(@NonNull Intent originalIntent,
+ @Nullable Bundle filteredExtras) {
+ final Intent intent = new Intent(originalIntent);
+ if (filteredExtras != null) {
+ intent.replaceExtras(filteredExtras);
+ }
+ return intent;
+ }
+
final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
BroadcastRecord r;
@@ -1837,6 +1865,24 @@
}
}
+ // Filter packages in the intent extras, skipping delivery if none of the packages is
+ // visible to the receiver.
+ Bundle filteredExtras = null;
+ if (!skip && r.filterExtrasForReceiver != null) {
+ final Bundle extras = r.intent.getExtras();
+ if (extras != null) {
+ filteredExtras = r.filterExtrasForReceiver.apply(receiverUid, extras);
+ if (filteredExtras == null) {
+ if (DEBUG_BROADCAST) {
+ Slog.v(TAG, "Skipping delivery to "
+ + info.activityInfo.packageName + " / " + receiverUid
+ + " : receiver is filtered by the package visibility");
+ }
+ skip = true;
+ }
+ }
+ }
+
if (skip) {
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Skipping delivery of ordered [" + mQueueName + "] "
@@ -1855,6 +1901,7 @@
r.state = BroadcastRecord.APP_RECEIVE;
r.curComponent = component;
r.curReceiver = info.activityInfo;
+ r.curFilteredExtras = filteredExtras;
if (DEBUG_MU && r.callingUid > UserHandle.PER_USER_RANGE) {
Slog.v(TAG_MU, "Updated broadcast record activity info for secondary user, "
+ info.activityInfo + ", callingUid = " + r.callingUid + ", uid = "
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index ce4528b..12bab273 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -55,6 +55,7 @@
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiFunction;
/**
* An active intent broadcast.
@@ -114,6 +115,11 @@
@Nullable
final IBinder mBackgroundActivityStartsToken;
+ // Filter the intent extras by using the rules of the package visibility before broadcasting
+ // the intent to the receiver.
+ @Nullable
+ final BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver;
+
static final int IDLE = 0;
static final int APP_RECEIVE = 1;
static final int CALL_IN_RECEIVE = 2;
@@ -134,6 +140,7 @@
ProcessRecord curApp; // hosting application of current receiver.
ComponentName curComponent; // the receiver class that is currently running.
ActivityInfo curReceiver; // info about the receiver that is currently running.
+ Bundle curFilteredExtras; // the bundle that has been filtered by the package visibility rules
boolean mIsReceiverAppRunning; // Was the receiver's app already running.
@@ -227,6 +234,9 @@
pw.println(curReceiver.applicationInfo.sourceDir);
}
}
+ if (curFilteredExtras != null) {
+ pw.print(" filtered extras: "); pw.println(curFilteredExtras);
+ }
if (state != IDLE) {
String stateStr = " (?)";
switch (state) {
@@ -273,7 +283,8 @@
BroadcastOptions _options, List _receivers, IIntentReceiver _resultTo, int _resultCode,
String _resultData, Bundle _resultExtras, boolean _serialized, boolean _sticky,
boolean _initialSticky, int _userId, boolean allowBackgroundActivityStarts,
- @Nullable IBinder backgroundActivityStartsToken, boolean timeoutExempt) {
+ @Nullable IBinder backgroundActivityStartsToken, boolean timeoutExempt,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
if (_intent == null) {
throw new NullPointerException("Can't construct with a null intent");
}
@@ -309,6 +320,7 @@
mBackgroundActivityStartsToken = backgroundActivityStartsToken;
this.timeoutExempt = timeoutExempt;
alarm = options != null && options.isAlarmBroadcast();
+ this.filterExtrasForReceiver = filterExtrasForReceiver;
}
/**
@@ -362,6 +374,7 @@
mBackgroundActivityStartsToken = from.mBackgroundActivityStartsToken;
timeoutExempt = from.timeoutExempt;
alarm = from.alarm;
+ filterExtrasForReceiver = from.filterExtrasForReceiver;
}
/**
@@ -397,7 +410,7 @@
requiredPermissions, excludedPermissions, excludedPackages, appOp, options,
splitReceivers, resultTo, resultCode, resultData, resultExtras, ordered, sticky,
initialSticky, userId, allowBackgroundActivityStarts,
- mBackgroundActivityStartsToken, timeoutExempt);
+ mBackgroundActivityStartsToken, timeoutExempt, filterExtrasForReceiver);
split.enqueueTime = this.enqueueTime;
split.enqueueRealTime = this.enqueueRealTime;
split.enqueueClockTime = this.enqueueClockTime;
@@ -476,7 +489,8 @@
requiredPermissions, excludedPermissions, excludedPackages, appOp, options,
uid2receiverList.valueAt(i), null /* _resultTo */,
resultCode, resultData, resultExtras, ordered, sticky, initialSticky, userId,
- allowBackgroundActivityStarts, mBackgroundActivityStartsToken, timeoutExempt);
+ allowBackgroundActivityStarts, mBackgroundActivityStartsToken, timeoutExempt,
+ filterExtrasForReceiver);
br.enqueueTime = this.enqueueTime;
br.enqueueRealTime = this.enqueueRealTime;
br.enqueueClockTime = this.enqueueClockTime;
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 653b602..b76bad4 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -110,6 +110,8 @@
private static final String ATRACE_COMPACTION_TRACK = "Compaction";
+ private static final int FREEZE_BINDER_TIMEOUT_MS = 100;
+
// Defaults for phenotype flags.
@VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = false;
@VisibleForTesting static final Boolean DEFAULT_USE_FREEZER = true;
@@ -927,11 +929,13 @@
* @param pid the target pid for which binder transactions are to be frozen
* @param freeze specifies whether to flush transactions and then freeze (true) or unfreeze
* binder for the specificed pid.
+ * @param timeoutMs the timeout in milliseconds to wait for the binder interface to freeze
+ * before giving up.
*
* @throws RuntimeException in case a flush/freeze operation could not complete successfully.
* @return 0 if success, or -EAGAIN indicating there's pending transaction.
*/
- private static native int freezeBinder(int pid, boolean freeze);
+ public static native int freezeBinder(int pid, boolean freeze, int timeoutMs);
/**
* Retrieves binder freeze info about a process.
@@ -1298,7 +1302,7 @@
long freezeTime = opt.getFreezeUnfreezeTime();
try {
- freezeBinder(pid, false);
+ freezeBinder(pid, false, FREEZE_BINDER_TIMEOUT_MS);
} catch (RuntimeException e) {
Slog.e(TAG_AM, "Unable to unfreeze binder for " + pid + " " + app.processName
+ ". Killing it");
@@ -1930,7 +1934,7 @@
// Freeze binder interface before the process, to flush any
// transactions that might be pending.
try {
- if (freezeBinder(pid, true) != 0) {
+ if (freezeBinder(pid, true, FREEZE_BINDER_TIMEOUT_MS) != 0) {
rescheduleFreeze(proc, "outstanding txns");
return;
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index ccbca76..ee60565 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -31,6 +31,7 @@
import static android.os.Process.getTotalMemory;
import static android.os.Process.killProcessQuiet;
import static android.os.Process.startWebView;
+import static android.system.OsConstants.*;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK;
@@ -2705,6 +2706,50 @@
}
}
+ private static boolean freezePackageCgroup(int packageUID, boolean freeze) {
+ try {
+ Process.freezeCgroupUid(packageUID, freeze);
+ } catch (RuntimeException e) {
+ final String logtxt = freeze ? "freeze" : "unfreeze";
+ Slog.e(TAG, "Unable to " + logtxt + " cgroup uid: " + packageUID + ": " + e);
+ return false;
+ }
+ return true;
+ }
+
+ private static void freezeBinderAndPackageCgroup(ArrayList<Pair<ProcessRecord, Boolean>> procs,
+ int packageUID) {
+ // Freeze all binder processes under the target UID (whose cgroup is about to be frozen).
+ // Since we're going to kill these, we don't need to unfreze them later.
+ // The procs list may not include all processes under the UID cgroup, but unincluded
+ // processes (forks) should not be Binder users.
+ int N = procs.size();
+ for (int i = 0; i < N; i++) {
+ final int uid = procs.get(i).first.uid;
+ final int pid = procs.get(i).first.getPid();
+ int nRetries = 0;
+ // We only freeze the cgroup of the target package, so we do not need to freeze the
+ // Binder interfaces of dependant processes in other UIDs.
+ if (pid > 0 && uid == packageUID) {
+ try {
+ int rc;
+ do {
+ rc = CachedAppOptimizer.freezeBinder(pid, true, 10 /* timeout_ms */);
+ } while (rc == -EAGAIN && nRetries++ < 1);
+ if (rc != 0) Slog.e(TAG, "Unable to freeze binder for " + pid + ": " + rc);
+ } catch (RuntimeException e) {
+ Slog.e(TAG, "Unable to freeze binder for " + pid + ": " + e);
+ }
+ }
+ }
+
+ // We freeze the entire UID (parent) cgroup so that newly-specialized processes also freeze
+ // despite being added to a new child cgroup. The cgroups of package dependant processes are
+ // not frozen, since it's possible this would freeze processes with no dependency on the
+ // package being killed here.
+ freezePackageCgroup(packageUID, true);
+ }
+
@GuardedBy({"mService", "mProcLock"})
boolean killPackageProcessesLSP(String packageName, int appId,
int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart,
@@ -2757,7 +2802,7 @@
boolean shouldAllowRestart = false;
// If no package is specified, we call all processes under the
- // give user id.
+ // given user id.
if (packageName == null) {
if (userId != UserHandle.USER_ALL && app.userId != userId) {
continue;
@@ -2800,14 +2845,18 @@
}
}
+ final int packageUID = UserHandle.getUid(userId, appId);
+ freezeBinderAndPackageCgroup(procs, packageUID);
+
int N = procs.size();
for (int i=0; i<N; i++) {
final Pair<ProcessRecord, Boolean> proc = procs.get(i);
removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second,
- reasonCode, subReason, reason);
+ reasonCode, subReason, reason, false /* async */);
}
killAppZygotesLocked(packageName, appId, userId, false /* force */);
mService.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
+ freezePackageCgroup(packageUID, false);
return N > 0;
}
@@ -2815,12 +2864,19 @@
boolean removeProcessLocked(ProcessRecord app,
boolean callerWillRestart, boolean allowRestart, int reasonCode, String reason) {
return removeProcessLocked(app, callerWillRestart, allowRestart, reasonCode,
- ApplicationExitInfo.SUBREASON_UNKNOWN, reason);
+ ApplicationExitInfo.SUBREASON_UNKNOWN, reason, true);
}
@GuardedBy("mService")
boolean removeProcessLocked(ProcessRecord app, boolean callerWillRestart,
boolean allowRestart, int reasonCode, int subReason, String reason) {
+ return removeProcessLocked(app, callerWillRestart, allowRestart, reasonCode, subReason,
+ reason, true);
+ }
+
+ @GuardedBy("mService")
+ boolean removeProcessLocked(ProcessRecord app, boolean callerWillRestart,
+ boolean allowRestart, int reasonCode, int subReason, String reason, boolean async) {
final String name = app.processName;
final int uid = app.uid;
if (DEBUG_PROCESSES) Slog.d(TAG_PROCESSES,
@@ -2857,7 +2913,7 @@
needRestart = true;
}
}
- app.killLocked(reason, reasonCode, subReason, true);
+ app.killLocked(reason, reasonCode, subReason, true, async);
mService.handleAppDiedLocked(app, pid, willRestart, allowRestart,
false /* fromBinderDied */);
if (willRestart) {
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 07b6fcd..2f1c5f6 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1057,18 +1057,30 @@
@GuardedBy("mService")
void killLocked(String reason, @Reason int reasonCode, boolean noisy) {
- killLocked(reason, reasonCode, ApplicationExitInfo.SUBREASON_UNKNOWN, noisy);
+ killLocked(reason, reasonCode, ApplicationExitInfo.SUBREASON_UNKNOWN, noisy, true);
}
@GuardedBy("mService")
void killLocked(String reason, @Reason int reasonCode, @SubReason int subReason,
boolean noisy) {
- killLocked(reason, reason, reasonCode, subReason, noisy);
+ killLocked(reason, reason, reasonCode, subReason, noisy, true);
}
@GuardedBy("mService")
void killLocked(String reason, String description, @Reason int reasonCode,
@SubReason int subReason, boolean noisy) {
+ killLocked(reason, description, reasonCode, subReason, noisy, true);
+ }
+
+ @GuardedBy("mService")
+ void killLocked(String reason, @Reason int reasonCode, @SubReason int subReason,
+ boolean noisy, boolean asyncKPG) {
+ killLocked(reason, reason, reasonCode, subReason, noisy, asyncKPG);
+ }
+
+ @GuardedBy("mService")
+ void killLocked(String reason, String description, @Reason int reasonCode,
+ @SubReason int subReason, boolean noisy, boolean asyncKPG) {
if (!mKilledByAm) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "kill");
if (reasonCode == ApplicationExitInfo.REASON_ANR
@@ -1085,7 +1097,8 @@
EventLog.writeEvent(EventLogTags.AM_KILL,
userId, mPid, processName, mState.getSetAdj(), reason);
Process.killProcessQuiet(mPid);
- ProcessList.killProcessGroup(uid, mPid);
+ if (asyncKPG) ProcessList.killProcessGroup(uid, mPid);
+ else Process.killProcessGroup(uid, mPid);
} else {
mPendingStart = false;
}
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index 262436d..eb1fd3a 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -497,6 +497,7 @@
@GuardedBy({"mService", "mProcLock"})
void setCurAdj(int curAdj) {
mCurAdj = curAdj;
+ mApp.getWindowProcessController().setCurrentAdj(curAdj);
}
@GuardedBy(anyOf = {"mService", "mProcLock"})
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 0c0ae7d..1edfabe 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -16,9 +16,11 @@
package com.android.server.am;
+import static android.Manifest.permission.CREATE_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.MANAGE_USERS;
import static android.app.ActivityManager.STOP_USER_ON_SWITCH_DEFAULT;
import static android.app.ActivityManager.STOP_USER_ON_SWITCH_TRUE;
import static android.app.ActivityManager.StopUserOnSwitch;
@@ -100,6 +102,7 @@
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
+import android.view.Display;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -107,6 +110,7 @@
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.FactoryResetter;
import com.android.server.FgThread;
@@ -1071,6 +1075,12 @@
Binder.getCallingPid(), UserHandle.USER_ALL);
});
}
+
+ // TODO(b/239982558): for now we're just updating the user's visibility, but most likely
+ // we'll need to remove this call and handle that as part of the user state workflow
+ // instead.
+ // TODO(b/240613396) also check if multi-display is supported
+ mInjector.getUserManagerInternal().assignUserToDisplay(userId, Display.INVALID_DISPLAY);
}
private void finishUserStopping(final int userId, final UserState uss,
@@ -1363,6 +1373,7 @@
}
final int profilesToStartSize = profilesToStart.size();
int i = 0;
+ // TODO(b/239982558): pass displayId
for (; i < profilesToStartSize && i < (getMaxRunningUsers() - 1); ++i) {
startUser(profilesToStart.get(i).id, /* foreground= */ false);
}
@@ -1371,6 +1382,7 @@
}
}
+ // TODO(b/239982558): might need to infer the display id based on parent user
/**
* Starts a user only if it's a profile, with a more relaxed permission requirement:
* {@link android.Manifest.permission#MANAGE_USERS} or
@@ -1399,7 +1411,8 @@
return false;
}
- return startUserNoChecks(userId, /* foreground= */ false, /* unlockListener= */ null);
+ return startUserNoChecks(userId, Display.DEFAULT_DISPLAY, /* foreground= */ false,
+ /* unlockListener= */ null);
}
@VisibleForTesting
@@ -1445,26 +1458,60 @@
@Nullable IProgressListener unlockListener) {
checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "startUser");
- return startUserNoChecks(userId, foreground, unlockListener);
+ return startUserNoChecks(userId, Display.DEFAULT_DISPLAY, foreground, unlockListener);
}
- private boolean startUserNoChecks(final @UserIdInt int userId, final boolean foreground,
+ // TODO(b/239982558): add javadoc (need to wait until the intents / SystemService callbacks are
+ // defined
+ boolean startUserOnSecondaryDisplay(@UserIdInt int userId, int displayId,
+ @Nullable IProgressListener unlockListener) {
+ checkCallingHasOneOfThosePermissions("startUserOnSecondaryDisplay",
+ MANAGE_USERS, CREATE_USERS);
+
+ return startUserNoChecks(userId, displayId, /* foreground= */ false, unlockListener);
+ }
+
+ private boolean startUserNoChecks(@UserIdInt int userId, int displayId, boolean foreground,
@Nullable IProgressListener unlockListener) {
TimingsTraceAndSlog t = new TimingsTraceAndSlog();
- t.traceBegin("UserController.startUser-" + userId + "-" + (foreground ? "fg" : "bg"));
+ t.traceBegin("UserController.startUser-" + userId
+ + (displayId == Display.DEFAULT_DISPLAY ? "" : "-display-" + displayId)
+ + "-" + (foreground ? "fg" : "bg"));
try {
- return startUserInternal(userId, foreground, unlockListener, t);
+ return startUserInternal(userId, displayId, foreground, unlockListener, t);
} finally {
t.traceEnd();
}
}
- private boolean startUserInternal(@UserIdInt int userId, boolean foreground,
+ private boolean startUserInternal(@UserIdInt int userId, int displayId, boolean foreground,
@Nullable IProgressListener unlockListener, @NonNull TimingsTraceAndSlog t) {
if (DEBUG_MU) {
- Slogf.i(TAG, "Starting user %d%s", userId, foreground ? " in foreground" : "");
+ Slogf.i(TAG, "Starting user %d on display %d %s", userId, displayId,
+ foreground ? " in foreground" : "");
}
+
+ if (displayId != Display.DEFAULT_DISPLAY) {
+ if (!UserManager.isUsersOnSecondaryDisplaysEnabled()) {
+ // TODO(b/239824814): add CTS test and/or unit test
+ throw new UnsupportedOperationException("Not supported by device");
+ }
+
+ // TODO(b/239982558): add unit test for the exceptional cases below
+ Preconditions.checkArgument(displayId > 0, "Invalid display id (%d)", displayId);
+ Preconditions.checkArgument(userId != UserHandle.USER_SYSTEM, "Cannot start system user"
+ + " on secondary display (%d)", displayId);
+ Preconditions.checkArgument(!foreground, "Cannot start user %d in foreground AND "
+ + "on secondary display (%d)", userId, displayId);
+
+ // TODO(b/239982558): for now we're just updating the user's visibility, but most likely
+ // we'll need to remove this call and handle that as part of the user state workflow
+ // instead.
+ // TODO(b/239982558): check if display is valid
+ mInjector.getUserManagerInternal().assignUserToDisplay(userId, displayId);
+ }
+
EventLog.writeEvent(EventLogTags.UC_START_USER_INTERNAL, userId);
final int callingUid = Binder.getCallingUid();
@@ -1519,6 +1566,7 @@
return false;
}
+ // TODO(b/239982558): might need something similar for bg users on secondary display
if (foreground && isUserSwitchUiEnabled()) {
t.traceBegin("startFreezingScreen");
mInjector.getWindowManager().startFreezingScreen(
@@ -2568,15 +2616,24 @@
}
private void checkCallingPermission(String permission, String methodName) {
- if (mInjector.checkCallingPermission(permission)
- != PackageManager.PERMISSION_GRANTED) {
- String msg = "Permission denial: " + methodName
- + "() from pid=" + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid()
- + " requires " + permission;
- Slogf.w(TAG, msg);
- throw new SecurityException(msg);
+ checkCallingHasOneOfThosePermissions(methodName, permission);
+ }
+
+ private void checkCallingHasOneOfThosePermissions(String methodName, String...permissions) {
+ for (String permission : permissions) {
+ if (mInjector.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
}
+ String msg = "Permission denial: " + methodName
+ + "() from pid=" + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " requires "
+ + (permissions.length == 1
+ ? permissions[0]
+ : "one of " + Arrays.toString(permissions));
+ Slogf.w(TAG, msg);
+ throw new SecurityException(msg);
}
private void enforceShellRestriction(String restriction, @UserIdInt int userId) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index b3aff65..eba9c7a 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1915,7 +1915,7 @@
return null;
}
- UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
+ @Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
synchronized (mDeviceStateLock) {
return mDeviceInventory.getDeviceSensorUuid(device);
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 1312d08..c1f4969 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -16,6 +16,7 @@
package com.android.server.audio;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
@@ -375,7 +376,8 @@
makeLeAudioDeviceUnavailable(address, btInfo.mAudioSystemDevice);
} else if (switchToAvailable) {
makeLeAudioDeviceAvailable(address, BtHelper.getName(btInfo.mDevice),
- streamType, btInfo.mAudioSystemDevice, "onSetBtActiveDevice");
+ streamType, btInfo.mVolume, btInfo.mAudioSystemDevice,
+ "onSetBtActiveDevice");
}
break;
default: throw new IllegalArgumentException("Invalid profile "
@@ -1175,8 +1177,8 @@
}
@GuardedBy("mDevicesLock")
- private void makeLeAudioDeviceAvailable(String address, String name, int streamType, int device,
- String eventSource) {
+ private void makeLeAudioDeviceAvailable(String address, String name, int streamType,
+ int volumeIndex, int device, String eventSource) {
if (device != AudioSystem.DEVICE_NONE) {
/* Audio Policy sees Le Audio similar to A2DP. Let's make sure
* AUDIO_POLICY_FORCE_NO_BT_A2DP is not set
@@ -1197,7 +1199,9 @@
return;
}
- final int leAudioVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType, device);
+ final int leAudioVolIndex = (volumeIndex == -1)
+ ? mDeviceBroker.getVssVolumeForDevice(streamType, device)
+ : volumeIndex;
final int maxIndex = mDeviceBroker.getMaxVssVolumeForStream(streamType);
mDeviceBroker.postSetLeAudioVolumeIndex(leAudioVolIndex, maxIndex, streamType);
mDeviceBroker.postApplyVolumeOnDevice(streamType, device, "makeLeAudioDeviceAvailable");
@@ -1511,7 +1515,7 @@
mDevRoleCapturePresetDispatchers.finishBroadcast();
}
- UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
+ @Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(),
device.getAddress());
synchronized (mDevicesLock) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 5a20db3..785040e 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -63,6 +63,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.SensorPrivacyManager;
import android.hardware.SensorPrivacyManagerInternal;
@@ -412,10 +413,10 @@
protected static int[] MAX_STREAM_VOLUME = new int[] {
5, // STREAM_VOICE_CALL
7, // STREAM_SYSTEM
- 7, // STREAM_RING
+ 7, // STREAM_RING // configured by config_audio_ring_vol_steps
15, // STREAM_MUSIC
7, // STREAM_ALARM
- 7, // STREAM_NOTIFICATION
+ 7, // STREAM_NOTIFICATION // configured by config_audio_notif_vol_steps
15, // STREAM_BLUETOOTH_SCO
7, // STREAM_SYSTEM_ENFORCED
15, // STREAM_DTMF
@@ -1116,6 +1117,48 @@
MAX_STREAM_VOLUME[AudioSystem.STREAM_SYSTEM];
}
+ // Read following properties to configure max volume (number of steps) and default volume
+ // for STREAM_NOTIFICATION and STREAM_RING:
+ // config_audio_notif_vol_default
+ // config_audio_notif_vol_steps
+ // config_audio_ring_vol_default
+ // config_audio_ring_vol_steps
+ int[] streams = { AudioSystem.STREAM_NOTIFICATION, AudioSystem.STREAM_RING };
+ int[] stepsResId = { com.android.internal.R.integer.config_audio_notif_vol_steps,
+ com.android.internal.R.integer.config_audio_ring_vol_steps };
+ int[] defaultResId = { com.android.internal.R.integer.config_audio_notif_vol_default,
+ com.android.internal.R.integer.config_audio_ring_vol_default };
+ for (int s = 0; s < streams.length; s++) {
+ try {
+ final int maxVol = mContext.getResources().getInteger(stepsResId[s]);
+ if (maxVol <= 0) {
+ throw new IllegalArgumentException("Invalid negative max volume for stream "
+ + streams[s]);
+ }
+ Log.i(TAG, "Stream " + streams[s] + ": using max vol of " + maxVol);
+ MAX_STREAM_VOLUME[streams[s]] = maxVol;
+ } catch (Resources.NotFoundException e) {
+ Log.e(TAG, "Error querying max vol for stream type " + streams[s], e);
+ }
+ try {
+ final int defaultVol = mContext.getResources().getInteger(defaultResId[s]);
+ if (defaultVol > MAX_STREAM_VOLUME[streams[s]]) {
+ throw new IllegalArgumentException("Invalid default volume (" + defaultVol
+ + ") for stream " + streams[s] + ", greater than max volume of "
+ + MAX_STREAM_VOLUME[streams[s]]);
+ }
+ if (defaultVol < MIN_STREAM_VOLUME[streams[s]]) {
+ throw new IllegalArgumentException("Invalid default volume (" + defaultVol
+ + ") for stream " + streams[s] + ", lower than min volume of "
+ + MIN_STREAM_VOLUME[streams[s]]);
+ }
+ Log.i(TAG, "Stream " + streams[s] + ": using default vol of " + defaultVol);
+ AudioSystem.DEFAULT_STREAM_VOLUME[streams[s]] = defaultVol;
+ } catch (Resources.NotFoundException e) {
+ Log.e(TAG, "Error querying default vol for stream type " + streams[s], e);
+ }
+ }
+
if (looper == null) {
createAudioSystemThread();
} else {
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index e27fb11..8356134 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -353,6 +353,14 @@
mASA.getDevicesForAttributes(
DEFAULT_ATTRIBUTES, false /* forVolume */).toArray(ROUTING_DEVICES);
+ // check validity of routing information
+ if (ROUTING_DEVICES[0] == null) {
+ logloge("onRoutingUpdated: device is null, no Spatial Audio");
+ setDispatchAvailableState(false);
+ // not changing the spatializer level as this is likely a transient state
+ return;
+ }
+
// is media routed to a new device?
if (isWireless(ROUTING_DEVICES[0].getType())) {
addWirelessDeviceIfNew(ROUTING_DEVICES[0]);
@@ -1098,7 +1106,7 @@
logDeviceState(deviceState, "setHeadTrackerEnabled");
// check current routing to see if it affects the headtracking mode
- if (ROUTING_DEVICES[0].getType() == ada.getType()
+ if (ROUTING_DEVICES[0] != null && ROUTING_DEVICES[0].getType() == ada.getType()
&& ROUTING_DEVICES[0].getAddress().equals(ada.getAddress())) {
setDesiredHeadTrackingMode(enabled ? mDesiredHeadTrackingModeWhenEnabled
: Spatializer.HEAD_TRACKING_MODE_DISABLED);
@@ -1633,7 +1641,11 @@
private int getHeadSensorHandleUpdateTracker() {
int headHandle = -1;
- UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(ROUTING_DEVICES[0]);
+ final AudioDeviceAttributes currentDevice = ROUTING_DEVICES[0];
+ if (currentDevice == null) {
+ return headHandle;
+ }
+ UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(currentDevice);
// We limit only to Sensor.TYPE_HEAD_TRACKER here to avoid confusion
// with gaming sensors. (Note that Sensor.TYPE_ROTATION_VECTOR
// and Sensor.TYPE_GAME_ROTATION_VECTOR are supported internally by
@@ -1644,7 +1656,7 @@
final UUID uuid = sensor.getUuid();
if (uuid.equals(routingDeviceUuid)) {
headHandle = sensor.getHandle();
- if (!setHasHeadTracker(ROUTING_DEVICES[0])) {
+ if (!setHasHeadTracker(currentDevice)) {
headHandle = -1;
}
break;
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java
deleted file mode 100644
index 0f1fe68..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2022 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;
-
-import android.annotation.NonNull;
-import android.hardware.biometrics.SensorPropertiesInternal;
-import android.util.proto.ProtoOutputStream;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.List;
-
-/**
- * Common attributes for all biometric service providers.
- *
- * @param <T> Internal settings type.
- */
-public interface BiometricServiceProvider<T extends SensorPropertiesInternal> {
-
- /** Checks if the specified sensor is owned by this provider. */
- boolean containsSensor(int sensorId);
-
- /** All sensor properties. */
- @NonNull
- List<T> getSensorProperties();
-
- /** Properties for the given sensor id. */
- @NonNull
- T getSensorProperties(int sensorId);
-
- boolean isHardwareDetected(int sensorId);
-
- /** If the user has any enrollments for the given sensor. */
- boolean hasEnrollments(int sensorId, int userId);
-
- long getAuthenticatorId(int sensorId, int userId);
-
- @LockoutTracker.LockoutMode
- int getLockoutModeForUser(int sensorId, int userId);
-
- void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
- boolean clearSchedulerBuffer);
-
- void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd);
-
- void dumpInternal(int sensorId, @NonNull PrintWriter pw);
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceRegistry.java b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceRegistry.java
deleted file mode 100644
index 7574523..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceRegistry.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * Copyright (C) 2022 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;
-
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.hardware.biometrics.IBiometricAuthenticator;
-import android.hardware.biometrics.IBiometricService;
-import android.hardware.biometrics.SensorPropertiesInternal;
-import android.os.Handler;
-import android.os.IInterface;
-import android.os.Process;
-import android.os.RemoteCallbackList;
-import android.os.RemoteException;
-import android.util.Pair;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.ServiceThread;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.function.Supplier;
-
-/**
- * Container for all BiometricServiceProvider implementations.
- *
- * @param <T> The service provider type.
- * @param <P> The internal properties type.
- * @param <C> The registration callback for {@link #invokeRegisteredCallback(IInterface, List)}.
- */
-public abstract class BiometricServiceRegistry<T extends BiometricServiceProvider<P>,
- P extends SensorPropertiesInternal,
- C extends IInterface> {
-
- private static final String TAG = "BiometricServiceRegistry";
-
- // Volatile so they can be read without a lock once all services are registered.
- // But, ideally remove this and provide immutable copies via the callback instead.
- @Nullable
- private volatile List<T> mServiceProviders;
- @Nullable
- private volatile List<P> mAllProps;
-
- @NonNull
- private final Supplier<IBiometricService> mBiometricServiceSupplier;
- @NonNull
- private final RemoteCallbackList<C> mRegisteredCallbacks = new RemoteCallbackList<>();
-
- public BiometricServiceRegistry(@NonNull Supplier<IBiometricService> biometricSupplier) {
- mBiometricServiceSupplier = biometricSupplier;
- }
-
- /**
- * Register an implementation by creating a new authenticator and initializing it via
- * {@link IBiometricService#registerAuthenticator(int, int, int, IBiometricAuthenticator)}
- * using the given properties.
- *
- * @param service service to register with
- * @param props internal properties to initialize the authenticator
- */
- protected abstract void registerService(@NonNull IBiometricService service, @NonNull P props);
-
- /**
- * Invoke the callback to notify clients that all authenticators have been registered.
- *
- * @param callback callback to invoke
- * @param allProps properties of all authenticators
- */
- protected abstract void invokeRegisteredCallback(@NonNull C callback,
- @NonNull List<P> allProps) throws RemoteException;
-
- /**
- * Register all authenticators in a background thread.
- *
- * @param serviceProvider Supplier function that will be invoked on the background thread.
- */
- public void registerAll(Supplier<List<T>> serviceProvider) {
- // Some HAL might not be started before the system service and will cause the code below
- // to wait, and some of the operations below might take a significant amount of time to
- // complete (calls to the HALs). To avoid blocking the rest of system server we put
- // this on a background thread.
- final ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
- true /* allowIo */);
- thread.start();
- final Handler handler = new Handler(thread.getLooper());
- handler.post(() -> registerAllInBackground(serviceProvider));
- thread.quitSafely();
- }
-
- /** Register authenticators now, only called by {@link #registerAll(Supplier).} */
- @VisibleForTesting
- public void registerAllInBackground(Supplier<List<T>> serviceProvider) {
- List<T> providers = serviceProvider.get();
- if (providers == null) {
- providers = new ArrayList<>();
- }
-
- final IBiometricService biometricService = mBiometricServiceSupplier.get();
- if (biometricService == null) {
- throw new IllegalStateException("biometric service cannot be null");
- }
-
- // Register each sensor individually with BiometricService
- final List<P> allProps = new ArrayList<>();
- for (T provider : providers) {
- final List<P> props = provider.getSensorProperties();
- for (P prop : props) {
- registerService(biometricService, prop);
- }
- allProps.addAll(props);
- }
-
- finishRegistration(providers, allProps);
- }
-
- private synchronized void finishRegistration(
- @NonNull List<T> providers, @NonNull List<P> allProps) {
- mServiceProviders = Collections.unmodifiableList(providers);
- mAllProps = Collections.unmodifiableList(allProps);
- broadcastAllAuthenticatorsRegistered();
- }
-
- /**
- * Add a callback that will be invoked once the work from {@link #registerAll(Supplier)}
- * has finished registering all providers (executes immediately if already done).
- *
- * @param callback registration callback
- */
- public synchronized void addAllRegisteredCallback(@Nullable C callback) {
- if (callback == null) {
- Slog.e(TAG, "addAllRegisteredCallback, callback is null");
- return;
- }
-
- final boolean registered = mRegisteredCallbacks.register(callback);
- final boolean allRegistered = mServiceProviders != null;
- if (registered && allRegistered) {
- broadcastAllAuthenticatorsRegistered();
- } else if (!registered) {
- Slog.e(TAG, "addAllRegisteredCallback failed to register callback");
- }
- }
-
- private synchronized void broadcastAllAuthenticatorsRegistered() {
- final int n = mRegisteredCallbacks.beginBroadcast();
- for (int i = 0; i < n; ++i) {
- final C cb = mRegisteredCallbacks.getBroadcastItem(i);
- try {
- invokeRegisteredCallback(cb, mAllProps);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception in broadcastAllAuthenticatorsRegistered", e);
- } finally {
- mRegisteredCallbacks.unregister(cb);
- }
- }
- mRegisteredCallbacks.finishBroadcast();
- }
-
- /**
- * Get a list of registered providers.
- *
- * Undefined until {@link #registerAll(Supplier)} has fired the completion callback.
- */
- @NonNull
- public List<T> getProviders() {
- return mServiceProviders != null ? mServiceProviders : Collections.emptyList();
- }
-
- /**
- * Gets the provider for given sensor id or null if not registered.
- *
- * Undefined until {@link #registerAll(Supplier)} has fired the completion callback.
- */
- @Nullable
- public T getProviderForSensor(int sensorId) {
- if (mServiceProviders != null) {
- for (T provider : mServiceProviders) {
- if (provider.containsSensor(sensorId)) {
- return provider;
- }
- }
- }
- return null;
- }
-
- /**
- * For devices with only a single provider, returns that provider.
- * If no providers, or multiple providers exist, returns null.
- *
- * Undefined until {@link #registerAll(Supplier)} has fired the completion callback.
- */
- @Nullable
- public Pair<Integer, T> getSingleProvider() {
- if (mAllProps == null || mAllProps.size() != 1) {
- Slog.e(TAG, "Multiple sensors found: " + mAllProps.size());
- return null;
- }
-
- final int sensorId = mAllProps.get(0).sensorId;
- final T provider = getProviderForSensor(sensorId);
- if (provider != null) {
- return new Pair<>(sensorId, provider);
- }
-
- Slog.e(TAG, "Single sensor, but provider not found");
- return null;
- }
-
- /**
- * Get the properties for all providers.
- *
- * Undefined until {@link #registerAll(Supplier)} has fired the completion callback.
- */
- @NonNull
- public List<P> getAllProperties() {
- return mAllProps != null ? mAllProps : Collections.emptyList();
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
index f854316..0d789f7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
@@ -23,64 +23,32 @@
import static android.hardware.biometrics.BiometricStateListener.STATE_KEYGUARD_AUTH;
import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.IBiometricStateListener;
-import android.hardware.biometrics.SensorPropertiesInternal;
import android.os.RemoteException;
-import android.os.UserManager;
import android.util.Slog;
import com.android.server.biometrics.Utils;
-import java.util.Collections;
-import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* A callback for receiving notifications about biometric sensor state changes.
- *
- * @param <T> service provider type
- * @param <P> internal property type
*/
-public class BiometricStateCallback<T extends BiometricServiceProvider<P>,
- P extends SensorPropertiesInternal> implements ClientMonitorCallback {
+public class BiometricStateCallback implements ClientMonitorCallback {
private static final String TAG = "BiometricStateCallback";
@NonNull
- private final CopyOnWriteArrayList<IBiometricStateListener> mBiometricStateListeners =
- new CopyOnWriteArrayList<>();
- @NonNull
- private final UserManager mUserManager;
- @BiometricStateListener.State
- private int mBiometricState;
- @NonNull
- private List<T> mProviders = List.of();
+ private final CopyOnWriteArrayList<IBiometricStateListener>
+ mBiometricStateListeners = new CopyOnWriteArrayList<>();
- /**
- * Create a new callback that must be {@link #start(List)}ed.
- *
- * @param userManager user manager
- */
- public BiometricStateCallback(@NonNull UserManager userManager) {
+ private @BiometricStateListener.State int mBiometricState;
+
+ public BiometricStateCallback() {
mBiometricState = STATE_IDLE;
- mUserManager = userManager;
}
- /**
- * This should be called when the service has been initialized and all providers are ready.
- *
- * @param allProviders all registered biometric service providers
- */
- public synchronized void start(@NonNull List<T> allProviders) {
- mProviders = Collections.unmodifiableList(allProviders);
- broadcastCurrentEnrollmentState(null /* listener */);
- }
-
- /** Get the current state. */
- @BiometricStateListener.State
public int getBiometricState() {
return mBiometricState;
}
@@ -152,43 +120,23 @@
}
/**
- * Enables clients to register a BiometricStateListener. For example, this is used to forward
- * fingerprint sensor state changes to SideFpsEventHandler.
- *
- * @param listener listener to register
+ * This should be invoked when:
+ * 1) Enrolled --> None-enrolled
+ * 2) None-enrolled --> enrolled
+ * 3) HAL becomes ready
+ * 4) Listener is registered
*/
- public synchronized void registerBiometricStateListener(
- @NonNull IBiometricStateListener listener) {
- mBiometricStateListeners.add(listener);
- broadcastCurrentEnrollmentState(listener);
- }
-
- private synchronized void broadcastCurrentEnrollmentState(
- @Nullable IBiometricStateListener listener) {
- for (T provider : mProviders) {
- for (SensorPropertiesInternal prop : provider.getSensorProperties()) {
- for (UserInfo userInfo : mUserManager.getAliveUsers()) {
- final boolean enrolled = provider.hasEnrollments(prop.sensorId, userInfo.id);
- if (listener != null) {
- notifyEnrollmentStateChanged(
- listener, userInfo.id, prop.sensorId, enrolled);
- } else {
- notifyAllEnrollmentStateChanged(
- userInfo.id, prop.sensorId, enrolled);
- }
- }
- }
- }
- }
-
- private void notifyAllEnrollmentStateChanged(int userId, int sensorId,
+ public void notifyAllEnrollmentStateChanged(int userId, int sensorId,
boolean hasEnrollments) {
for (IBiometricStateListener listener : mBiometricStateListeners) {
notifyEnrollmentStateChanged(listener, userId, sensorId, hasEnrollments);
}
}
- private void notifyEnrollmentStateChanged(@NonNull IBiometricStateListener listener,
+ /**
+ * Notifies the listener of enrollment state changes.
+ */
+ public void notifyEnrollmentStateChanged(@NonNull IBiometricStateListener listener,
int userId, int sensorId, boolean hasEnrollments) {
try {
listener.onEnrollmentsChanged(userId, sensorId, hasEnrollments);
@@ -196,4 +144,14 @@
Slog.e(TAG, "Remote exception", e);
}
}
+
+ /**
+ * Enables clients to register a BiometricStateListener. For example, this is used to forward
+ * fingerprint sensor state changes to SideFpsEventHandler.
+ *
+ * @param listener
+ */
+ public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
+ mBiometricStateListeners.add(listener);
+ }
}
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 271bce9..79e65cc 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
@@ -17,18 +17,20 @@
package com.android.server.biometrics.sensors.face;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.MANAGE_FACE;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
+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.IBiometricStateListener;
import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
@@ -37,18 +39,18 @@
import android.hardware.face.Face;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.FaceServiceReceiver;
-import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
import android.hardware.face.IFaceService;
import android.hardware.face.IFaceServiceReceiver;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
import android.os.NativeHandle;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.UserHandle;
-import android.os.UserManager;
import android.util.Pair;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -56,10 +58,10 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
+import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
@@ -86,10 +88,51 @@
private final LockoutResetDispatcher mLockoutResetDispatcher;
private final LockPatternUtils mLockPatternUtils;
@NonNull
- private final FaceServiceRegistry mRegistry;
+ private final List<ServiceProvider> mServiceProviders;
+
+ @Nullable
+ private ServiceProvider getProviderForSensor(int sensorId) {
+ for (ServiceProvider provider : mServiceProviders) {
+ if (provider.containsSensor(sensorId)) {
+ return provider;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * For devices with only a single provider, returns that provider. If no providers, or multiple
+ * providers exist, returns null.
+ */
+ @Nullable
+ private Pair<Integer, ServiceProvider> getSingleProvider() {
+ final List<FaceSensorPropertiesInternal> properties = getSensorProperties();
+ if (properties.size() != 1) {
+ Slog.e(TAG, "Multiple sensors found: " + properties.size());
+ return null;
+ }
+
+ // Theoretically we can just return the first provider, but maybe this is easier to
+ // understand.
+ final int sensorId = properties.get(0).sensorId;
+ for (ServiceProvider provider : mServiceProviders) {
+ if (provider.containsSensor(sensorId)) {
+ return new Pair<>(sensorId, provider);
+ }
+ }
+
+ Slog.e(TAG, "Single sensor, but provider not found");
+ return null;
+ }
+
@NonNull
- private final BiometricStateCallback<ServiceProvider, FaceSensorPropertiesInternal>
- mBiometricStateCallback;
+ private List<FaceSensorPropertiesInternal> getSensorProperties() {
+ final List<FaceSensorPropertiesInternal> properties = new ArrayList<>();
+ for (ServiceProvider provider : mServiceProviders) {
+ properties.addAll(provider.getSensorProperties());
+ }
+ return properties;
+ }
/**
* Receives the incoming binder calls from FaceManager.
@@ -99,7 +142,8 @@
@Override
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@NonNull String opPackageName) {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for createTestSession, sensorId: " + sensorId);
@@ -112,8 +156,9 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer) {
+
final ProtoOutputStream proto = new ProtoOutputStream();
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider != null) {
provider.dumpProtoState(sensorId, proto, clearSchedulerBuffer);
}
@@ -125,14 +170,16 @@
@Override // Binder call
public List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(
String opPackageName) {
- return mRegistry.getAllProperties();
+
+ return FaceService.this.getSensorProperties();
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public FaceSensorPropertiesInternal getSensorProperties(int sensorId,
@NonNull String opPackageName) {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching sensor for getSensorProperties, sensorId: " + sensorId
+ ", caller: " + opPackageName);
@@ -146,7 +193,8 @@
@Override // Binder call
public void generateChallenge(IBinder token, int sensorId, int userId,
IFaceServiceReceiver receiver, String opPackageName) {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId);
return;
@@ -159,7 +207,8 @@
@Override // Binder call
public void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName,
long challenge) {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching sensor for revokeChallenge, sensorId: " + sensorId);
return;
@@ -173,7 +222,8 @@
public long enroll(int userId, final IBinder token, final byte[] hardwareAuthToken,
final IFaceServiceReceiver receiver, final String opPackageName,
final int[] disabledFeatures, Surface previewSurface, boolean debugConsent) {
- final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
+
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for enroll");
return -1;
@@ -195,7 +245,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC)
@Override // Binder call
public void cancelEnrollment(final IBinder token, long requestId) {
- final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
+
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for cancelEnrollment");
return;
@@ -209,6 +260,7 @@
public long authenticate(final IBinder token, final long operationId, int userId,
final IFaceServiceReceiver receiver, final String opPackageName,
boolean isKeyguardBypassEnabled) {
+
// TODO(b/152413782): If the sensor supports face detect and the device is encrypted or
// lockdown, something wrong happened. See similar path in FingerprintService.
@@ -221,7 +273,7 @@
// permission.
final boolean isKeyguard = Utils.isKeyguard(getContext(), opPackageName);
- final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for authenticate");
return -1;
@@ -249,7 +301,7 @@
return -1;
}
- final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for detectFace");
return -1;
@@ -266,7 +318,8 @@
IBinder token, long operationId, int userId,
IBiometricSensorReceiver sensorReceiver, String opPackageName, long requestId,
int cookie, boolean allowBackgroundAuthentication) {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for prepareForAuthentication");
return;
@@ -283,7 +336,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public void startPreparedClient(int sensorId, int cookie) {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for startPreparedClient");
return;
@@ -296,7 +350,8 @@
@Override // Binder call
public void cancelAuthentication(final IBinder token, final String opPackageName,
final long requestId) {
- final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
+
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for cancelAuthentication");
return;
@@ -315,7 +370,7 @@
return;
}
- final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for cancelFaceDetect");
return;
@@ -328,7 +383,8 @@
@Override // Binder call
public void cancelAuthenticationFromService(int sensorId, final IBinder token,
final String opPackageName, final long requestId) {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for cancelAuthenticationFromService");
return;
@@ -341,7 +397,8 @@
@Override // Binder call
public void remove(final IBinder token, final int faceId, final int userId,
final IFaceServiceReceiver receiver, final String opPackageName) {
- final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
+
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for remove");
return;
@@ -355,6 +412,7 @@
@Override // Binder call
public void removeAll(final IBinder token, final int userId,
final IFaceServiceReceiver receiver, final String opPackageName) {
+
final FaceServiceReceiver internalReceiver = new FaceServiceReceiver() {
int sensorsFinishedRemoving = 0;
final int numSensors = getSensorPropertiesInternal(
@@ -374,7 +432,7 @@
// This effectively iterates through all sensors, but has to do so by finding all
// sensors under each provider.
- for (ServiceProvider provider : mRegistry.getProviders()) {
+ for (ServiceProvider provider : mServiceProviders) {
List<FaceSensorPropertiesInternal> props = provider.getSensorProperties();
for (FaceSensorPropertiesInternal prop : props) {
provider.scheduleRemoveAll(prop.sensorId, token, userId, internalReceiver,
@@ -409,27 +467,27 @@
try {
if (args.length > 1 && "--proto".equals(args[0]) && "--state".equals(args[1])) {
final ProtoOutputStream proto = new ProtoOutputStream(fd);
- for (ServiceProvider provider : mRegistry.getProviders()) {
+ for (ServiceProvider provider : mServiceProviders) {
for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) {
provider.dumpProtoState(props.sensorId, proto, false);
}
}
proto.flush();
} else if (args.length > 0 && "--proto".equals(args[0])) {
- for (ServiceProvider provider : mRegistry.getProviders()) {
+ for (ServiceProvider provider : mServiceProviders) {
for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) {
provider.dumpProtoMetrics(props.sensorId, fd);
}
}
} else if (args.length > 1 && "--hal".equals(args[0])) {
- for (ServiceProvider provider : mRegistry.getProviders()) {
+ for (ServiceProvider provider : mServiceProviders) {
for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) {
provider.dumpHal(props.sensorId, fd,
Arrays.copyOfRange(args, 1, args.length, args.getClass()));
}
}
} else {
- for (ServiceProvider provider : mRegistry.getProviders()) {
+ for (ServiceProvider provider : mServiceProviders) {
for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) {
pw.println("Dumping for sensorId: " + props.sensorId
+ ", provider: " + provider.getClass().getSimpleName());
@@ -446,9 +504,10 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public boolean isHardwareDetected(int sensorId, String opPackageName) {
+
final long token = Binder.clearCallingIdentity();
try {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for isHardwareDetected, caller: " + opPackageName);
return false;
@@ -462,11 +521,12 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public List<Face> getEnrolledFaces(int sensorId, int userId, String opPackageName) {
+
if (userId != UserHandle.getCallingUserId()) {
Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS);
}
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for getEnrolledFaces, caller: " + opPackageName);
return Collections.emptyList();
@@ -478,11 +538,12 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public boolean hasEnrolledFaces(int sensorId, int userId, String opPackageName) {
+
if (userId != UserHandle.getCallingUserId()) {
Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS);
}
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for hasEnrolledFaces, caller: " + opPackageName);
return false;
@@ -494,7 +555,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public @LockoutTracker.LockoutMode int getLockoutModeForUser(int sensorId, int userId) {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for getLockoutModeForUser");
return LockoutTracker.LOCKOUT_NONE;
@@ -507,7 +569,8 @@
@Override
public void invalidateAuthenticatorId(int sensorId, int userId,
IInvalidationCallback callback) {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for invalidateAuthenticatorId");
return;
@@ -519,7 +582,7 @@
@Override // Binder call
public long getAuthenticatorId(int sensorId, int userId) {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for getAuthenticatorId");
return 0;
@@ -532,7 +595,8 @@
@Override // Binder call
public void resetLockout(IBinder token, int sensorId, int userId, byte[] hardwareAuthToken,
String opPackageName) {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for resetLockout, caller: " + opPackageName);
return;
@@ -546,7 +610,8 @@
public void setFeature(final IBinder token, int userId, int feature, boolean enabled,
final byte[] hardwareAuthToken, IFaceServiceReceiver receiver,
final String opPackageName) {
- final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
+
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for setFeature");
return;
@@ -560,7 +625,8 @@
@Override
public void getFeature(final IBinder token, int userId, int feature,
IFaceServiceReceiver receiver, final String opPackageName) {
- final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
+
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for getFeature");
return;
@@ -570,14 +636,18 @@
new ClientMonitorCallbackConverter(receiver), opPackageName);
}
- private List<ServiceProvider> getAidlProviders() {
- final List<ServiceProvider> providers = new ArrayList<>();
+ private void addHidlProviders(@NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
+ for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
+ mServiceProviders.add(
+ Face10.newInstance(getContext(), hidlSensor, mLockoutResetDispatcher));
+ }
+ }
+ private void addAidlProviders() {
final String[] instances = ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
if (instances == null || instances.length == 0) {
- return providers;
+ return;
}
-
for (String instance : instances) {
final String fqName = IFace.DESCRIPTOR + "/" + instance;
final IFace face = IFace.Stub.asInterface(
@@ -590,41 +660,53 @@
final SensorProps[] props = face.getSensorProps();
final FaceProvider provider = new FaceProvider(getContext(), props, instance,
mLockoutResetDispatcher, BiometricContext.getInstance(getContext()));
- providers.add(provider);
+ mServiceProviders.add(provider);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
}
}
-
- return providers;
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public void registerAuthenticators(
@NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
- mRegistry.registerAll(() -> {
- final List<ServiceProvider> providers = new ArrayList<>();
- for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
- providers.add(
- Face10.newInstance(getContext(), hidlSensor, mLockoutResetDispatcher));
+
+ // Some HAL might not be started before the system service and will cause the code below
+ // to wait, and some of the operations below might take a significant amount of time to
+ // complete (calls to the HALs). To avoid blocking the rest of system server we put
+ // this on a background thread.
+ final ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
+ true /* allowIo */);
+ thread.start();
+ final Handler handler = new Handler(thread.getLooper());
+
+ handler.post(() -> {
+ addHidlProviders(hidlSensors);
+ addAidlProviders();
+
+ final IBiometricService biometricService = IBiometricService.Stub.asInterface(
+ ServiceManager.getService(Context.BIOMETRIC_SERVICE));
+
+ // Register each sensor individually with BiometricService
+ for (ServiceProvider provider : mServiceProviders) {
+ final List<FaceSensorPropertiesInternal> props = provider.getSensorProperties();
+ for (FaceSensorPropertiesInternal prop : props) {
+ final int sensorId = prop.sensorId;
+ final @BiometricManager.Authenticators.Types int strength =
+ Utils.propertyStrengthToAuthenticatorStrength(prop.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);
+ }
+ }
}
- providers.addAll(getAidlProviders());
- return providers;
});
}
-
- @Override
- public void addAuthenticatorsRegisteredCallback(
- IFaceAuthenticatorsRegisteredCallback callback) {
- Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
- mRegistry.addAllRegisteredCallback(callback);
- }
-
- @Override
- public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
- mBiometricStateCallback.registerBiometricStateListener(listener);
- }
}
public FaceService(Context context) {
@@ -632,16 +714,7 @@
mServiceWrapper = new FaceServiceWrapper();
mLockoutResetDispatcher = new LockoutResetDispatcher(context);
mLockPatternUtils = new LockPatternUtils(context);
- mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context));
- mRegistry = new FaceServiceRegistry(mServiceWrapper,
- () -> IBiometricService.Stub.asInterface(
- ServiceManager.getService(Context.BIOMETRIC_SERVICE)));
- mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
- @Override
- public void onAllAuthenticatorsRegistered(List<FaceSensorPropertiesInternal> sensors) {
- mBiometricStateCallback.start(mRegistry.getProviders());
- }
- });
+ mServiceProviders = new ArrayList<>();
}
@Override
@@ -679,7 +752,7 @@
if (Utils.isVirtualEnabled(getContext())) {
Slog.i(TAG, "Sync virtual enrollments");
final int userId = ActivityManager.getCurrentUser();
- for (ServiceProvider provider : mRegistry.getProviders()) {
+ for (ServiceProvider provider : mServiceProviders) {
for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) {
provider.scheduleInternalCleanup(props.sensorId, userId, null /* callback */,
true /* favorHalEnrollments */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java
deleted file mode 100644
index 0f0a81d..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2022 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.face;
-
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.hardware.biometrics.BiometricManager;
-import android.hardware.biometrics.IBiometricService;
-import android.hardware.face.FaceSensorPropertiesInternal;
-import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
-import android.hardware.face.IFaceService;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.Utils;
-import com.android.server.biometrics.sensors.BiometricServiceRegistry;
-
-import java.util.List;
-import java.util.function.Supplier;
-
-/** Registry for {@link IFaceService} providers. */
-public class FaceServiceRegistry extends BiometricServiceRegistry<ServiceProvider,
- FaceSensorPropertiesInternal, IFaceAuthenticatorsRegisteredCallback> {
-
- private static final String TAG = "FaceServiceRegistry";
-
- @NonNull
- private final IFaceService mService;
-
- /** Creates a new registry tied to the given service. */
- public FaceServiceRegistry(@NonNull IFaceService service,
- @Nullable Supplier<IBiometricService> biometricSupplier) {
- super(biometricSupplier);
- mService = service;
- }
-
- @Override
- protected void registerService(@NonNull IBiometricService service,
- @NonNull FaceSensorPropertiesInternal props) {
- @BiometricManager.Authenticators.Types final int strength =
- Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength);
- try {
- service.registerAuthenticator(props.sensorId, TYPE_FACE, strength,
- new FaceAuthenticator(mService, props.sensorId));
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when registering sensorId: " + props.sensorId);
- }
- }
-
- @Override
- protected void invokeRegisteredCallback(@NonNull IFaceAuthenticatorsRegisteredCallback callback,
- @NonNull List<FaceSensorPropertiesInternal> allProps) throws RemoteException {
- callback.onAllAuthenticatorsRegistered(allProps);
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
index 4efaedb..6f98365 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
@@ -26,13 +26,15 @@
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceServiceReceiver;
import android.os.IBinder;
+import android.util.proto.ProtoOutputStream;
import android.view.Surface;
-import com.android.server.biometrics.sensors.BiometricServiceProvider;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.LockoutTracker;
import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.List;
/**
@@ -54,11 +56,24 @@
* to check (e.g. via {@link FaceManager#getSensorPropertiesInternal()}) that the code path isn't
* taken. ServiceProviders will provide a no-op for unsupported operations to fail safely.
*/
-public interface ServiceProvider extends BiometricServiceProvider<FaceSensorPropertiesInternal> {
+public interface ServiceProvider {
+ /**
+ * Checks if the specified sensor is owned by this provider.
+ */
+ boolean containsSensor(int sensorId);
+
+ @NonNull
+ List<FaceSensorPropertiesInternal> getSensorProperties();
+
+ @NonNull
+ FaceSensorPropertiesInternal getSensorProperties(int sensorId);
@NonNull
List<Face> getEnrolledFaces(int sensorId, int userId);
+ @LockoutTracker.LockoutMode
+ int getLockoutModeForUser(int sensorId, int userId);
+
/**
* Requests for the authenticatorId (whose source of truth is in the TEE or equivalent) to be
* invalidated. See {@link com.android.server.biometrics.sensors.InvalidationRequesterClient}
@@ -69,6 +84,10 @@
+ " this method");
}
+ long getAuthenticatorId(int sensorId, int userId);
+
+ boolean isHardwareDetected(int sensorId);
+
void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
@NonNull IFaceServiceReceiver receiver, String opPackageName);
@@ -123,6 +142,13 @@
void scheduleInternalCleanup(int sensorId, int userId,
@Nullable ClientMonitorCallback callback, boolean favorHalEnrollments);
+ void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
+ boolean clearSchedulerBuffer);
+
+ void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd);
+
+ void dumpInternal(int sensorId, @NonNull PrintWriter pw);
+
@NonNull
ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@NonNull String opPackageName);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 6bff179..19d54c8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -285,11 +285,6 @@
}
@Override
- public boolean hasEnrollments(int sensorId, int userId) {
- return !getEnrolledFaces(sensorId, userId).isEmpty();
- }
-
- @Override
public void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
@NonNull IInvalidationCallback callback) {
mHandler.post(() -> {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index c0a119f..6528912 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -484,11 +484,6 @@
}
@Override
- public boolean hasEnrollments(int sensorId, int userId) {
- return !getEnrolledFaces(sensorId, userId).isEmpty();
- }
-
- @Override
@LockoutTracker.LockoutMode
public int getLockoutModeForUser(int sensorId, int userId) {
return mLockoutTracker.getLockoutModeForUser(userId);
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 7e2742e..2ba449a 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
@@ -17,11 +17,14 @@
package com.android.server.biometrics.sensors.fingerprint;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.MANAGE_FINGERPRINT;
+import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
import static android.Manifest.permission.TEST_BIOMETRIC;
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 static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR;
@@ -33,6 +36,8 @@
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IBiometricSensorReceiver;
@@ -60,6 +65,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Process;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
@@ -73,9 +79,11 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
+import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
@@ -107,32 +115,74 @@
protected static final String TAG = "FingerprintService";
+ private final Object mLock = new Object();
private final AppOpsManager mAppOps;
private final LockoutResetDispatcher mLockoutResetDispatcher;
private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
private final LockPatternUtils mLockPatternUtils;
- @NonNull
- private final BiometricContext mBiometricContext;
- @NonNull
- private final Supplier<String[]> mAidlInstanceNameSupplier;
- @NonNull
- private final Function<String, IFingerprint> mIFingerprintProvider;
- @NonNull
- private final BiometricStateCallback<ServiceProvider, FingerprintSensorPropertiesInternal>
- mBiometricStateCallback;
- @NonNull
- private final Handler mHandler;
- @NonNull
- private final FingerprintServiceRegistry mRegistry;
+ @NonNull private final List<ServiceProvider> mServiceProviders;
+ @NonNull private final BiometricStateCallback mBiometricStateCallback;
+ @NonNull private final Handler mHandler;
+ @NonNull private final BiometricContext mBiometricContext;
+ @NonNull private final Supplier<IBiometricService> mBiometricServiceSupplier;
+ @NonNull private final Function<String, IFingerprint> mIFingerprintProvider;
- /** Receives the incoming binder calls from FingerprintManager. */
- @VisibleForTesting
- final IFingerprintService.Stub mServiceWrapper = new IFingerprintService.Stub() {
+ @GuardedBy("mLock")
+ @NonNull private final RemoteCallbackList<IFingerprintAuthenticatorsRegisteredCallback>
+ mAuthenticatorsRegisteredCallbacks;
+
+ @GuardedBy("mLock")
+ @NonNull private final List<FingerprintSensorPropertiesInternal> mSensorProps;
+
+ /**
+ * Registers BiometricStateListener in list stored by FingerprintService
+ * @param listener new BiometricStateListener being added
+ */
+ public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
+ mBiometricStateCallback.registerBiometricStateListener(listener);
+ broadcastCurrentEnrollmentState(listener);
+ }
+
+ /**
+ * @param listener if non-null, notifies only this listener. if null, notifies all listeners
+ * in {@link BiometricStateCallback}. This is slightly ugly, but reduces
+ * redundant code.
+ */
+ private void broadcastCurrentEnrollmentState(@Nullable IBiometricStateListener listener) {
+ final UserManager um = UserManager.get(getContext());
+ synchronized (mLock) {
+ // Update the new listener with current state of all sensors
+ for (FingerprintSensorPropertiesInternal prop : mSensorProps) {
+ final ServiceProvider provider = getProviderForSensor(prop.sensorId);
+ for (UserInfo userInfo : um.getAliveUsers()) {
+ final boolean enrolled = !provider
+ .getEnrolledFingerprints(prop.sensorId, userInfo.id).isEmpty();
+
+ // Defer this work and allow the loop to release the lock sooner
+ mHandler.post(() -> {
+ if (listener != null) {
+ mBiometricStateCallback.notifyEnrollmentStateChanged(
+ listener, userInfo.id, prop.sensorId, enrolled);
+ } else {
+ mBiometricStateCallback.notifyAllEnrollmentStateChanged(
+ userInfo.id, prop.sensorId, enrolled);
+ }
+ });
+ }
+ }
+ }
+ }
+
+ /**
+ * Receives the incoming binder calls from FingerprintManager.
+ */
+ private final IFingerprintService.Stub mServiceWrapper = new IFingerprintService.Stub() {
@android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
@Override
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@NonNull String opPackageName) {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for createTestSession, sensorId: " + sensorId);
@@ -145,8 +195,9 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer) {
+
final ProtoOutputStream proto = new ProtoOutputStream();
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider != null) {
provider.dumpProtoState(sensorId, proto, clearSchedulerBuffer);
}
@@ -161,14 +212,16 @@
!= PackageManager.PERMISSION_GRANTED) {
Utils.checkPermission(getContext(), TEST_BIOMETRIC);
}
- return mRegistry.getAllProperties();
+
+ return FingerprintService.this.getSensorProperties();
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public FingerprintSensorPropertiesInternal getSensorProperties(int sensorId,
@NonNull String opPackageName) {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching sensor for getSensorProperties, sensorId: " + sensorId
+ ", caller: " + opPackageName);
@@ -181,7 +234,8 @@
@Override // Binder call
public void generateChallenge(IBinder token, int sensorId, int userId,
IFingerprintServiceReceiver receiver, String opPackageName) {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId);
return;
@@ -194,7 +248,8 @@
@Override // Binder call
public void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName,
long challenge) {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching sensor for revokeChallenge, sensorId: " + sensorId);
return;
@@ -209,7 +264,8 @@
public long enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken,
final int userId, final IFingerprintServiceReceiver receiver,
final String opPackageName, @FingerprintManager.EnrollReason int enrollReason) {
- final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
+
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for enroll");
return -1;
@@ -222,7 +278,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT)
@Override // Binder call
public void cancelEnrollment(final IBinder token, long requestId) {
- final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
+
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for cancelEnrollment");
return;
@@ -282,10 +339,10 @@
final Pair<Integer, ServiceProvider> provider;
if (sensorId == FingerprintManager.SENSOR_ID_ANY) {
- provider = mRegistry.getSingleProvider();
+ provider = getSingleProvider();
} else {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
- provider = new Pair<>(sensorId, mRegistry.getProviderForSensor(sensorId));
+ provider = new Pair<>(sensorId, getProviderForSensor(sensorId));
}
if (provider == null) {
Slog.w(TAG, "Null provider for authenticate");
@@ -317,6 +374,7 @@
final IFingerprintServiceReceiver receiver,
final String opPackageName,
boolean ignoreEnrollmentState) throws PackageManager.NameNotFoundException {
+
final Context context = getUiContext();
final Context promptContext = context.createPackageContextAsUser(
opPackageName, 0 /* flags */, UserHandle.getUserHandleForUid(uId));
@@ -410,7 +468,7 @@
return -1;
}
- final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for detectFingerprint");
return -1;
@@ -426,7 +484,8 @@
public void prepareForAuthentication(int sensorId, IBinder token, long operationId,
int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
long requestId, int cookie, boolean allowBackgroundAuthentication) {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for prepareForAuthentication");
return;
@@ -442,7 +501,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC)
@Override // Binder call
public void startPreparedClient(int sensorId, int cookie) {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for startPreparedClient");
return;
@@ -472,7 +532,7 @@
return;
}
- final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for cancelAuthentication");
return;
@@ -493,7 +553,7 @@
// For IBiometricsFingerprint2.1, cancelling fingerprint detect is the same as
// cancelling authentication.
- final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for cancelFingerprintDetect");
return;
@@ -506,9 +566,11 @@
@Override // Binder call
public void cancelAuthenticationFromService(final int sensorId, final IBinder token,
final String opPackageName, final long requestId) {
+
+
Slog.d(TAG, "cancelAuthenticationFromService, sensorId: " + sensorId);
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for cancelAuthenticationFromService");
return;
@@ -521,7 +583,8 @@
@Override // Binder call
public void remove(final IBinder token, final int fingerId, final int userId,
final IFingerprintServiceReceiver receiver, final String opPackageName) {
- final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
+
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for remove");
return;
@@ -554,7 +617,7 @@
// This effectively iterates through all sensors, but has to do so by finding all
// sensors under each provider.
- for (ServiceProvider provider : mRegistry.getProviders()) {
+ for (ServiceProvider provider : mServiceProviders) {
List<FingerprintSensorPropertiesInternal> props = provider.getSensorProperties();
for (FingerprintSensorPropertiesInternal prop : props) {
provider.scheduleRemoveAll(prop.sensorId, token, internalReceiver, userId,
@@ -589,7 +652,7 @@
try {
if (args.length > 1 && "--proto".equals(args[0]) && "--state".equals(args[1])) {
final ProtoOutputStream proto = new ProtoOutputStream(fd);
- for (ServiceProvider provider : mRegistry.getProviders()) {
+ for (ServiceProvider provider : mServiceProviders) {
for (FingerprintSensorPropertiesInternal props
: provider.getSensorProperties()) {
provider.dumpProtoState(props.sensorId, proto, false);
@@ -597,14 +660,14 @@
}
proto.flush();
} else if (args.length > 0 && "--proto".equals(args[0])) {
- for (ServiceProvider provider : mRegistry.getProviders()) {
+ for (ServiceProvider provider : mServiceProviders) {
for (FingerprintSensorPropertiesInternal props
: provider.getSensorProperties()) {
provider.dumpProtoMetrics(props.sensorId, fd);
}
}
} else {
- for (ServiceProvider provider : mRegistry.getProviders()) {
+ for (ServiceProvider provider : mServiceProviders) {
for (FingerprintSensorPropertiesInternal props
: provider.getSensorProperties()) {
pw.println("Dumping for sensorId: " + props.sensorId
@@ -635,7 +698,7 @@
final long token = Binder.clearCallingIdentity();
try {
- final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for isHardwareDetectedDeprecated, caller: "
+ opPackageName);
@@ -650,7 +713,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public boolean isHardwareDetected(int sensorId, String opPackageName) {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for isHardwareDetected, caller: " + opPackageName);
return false;
@@ -666,7 +730,7 @@
return;
}
- final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for rename");
return;
@@ -717,7 +781,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
public boolean hasEnrolledFingerprints(int sensorId, int userId, String opPackageName) {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for hasEnrolledFingerprints, caller: " + opPackageName);
return false;
@@ -729,7 +794,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public @LockoutTracker.LockoutMode int getLockoutModeForUser(int sensorId, int userId) {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for getLockoutModeForUser");
return LockoutTracker.LOCKOUT_NONE;
@@ -741,7 +807,8 @@
@Override
public void invalidateAuthenticatorId(int sensorId, int userId,
IInvalidationCallback callback) {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for invalidateAuthenticatorId");
return;
@@ -752,7 +819,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public long getAuthenticatorId(int sensorId, int userId) {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for getAuthenticatorId");
return 0;
@@ -764,7 +832,8 @@
@Override // Binder call
public void resetLockout(IBinder token, int sensorId, int userId,
@Nullable byte[] hardwareAuthToken, String opPackageName) {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for resetLockout, caller: " + opPackageName);
return;
@@ -795,38 +864,55 @@
@Override // Binder call
public void registerAuthenticators(
@NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
- mRegistry.registerAll(() -> {
- final List<ServiceProvider> providers = new ArrayList<>();
- providers.addAll(getHidlProviders(hidlSensors));
+
+ // Some HAL might not be started before the system service and will cause the code below
+ // to wait, and some of the operations below might take a significant amount of time to
+ // complete (calls to the HALs). To avoid blocking the rest of system server we put
+ // this on a background thread.
+ final ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
+ true /* allowIo */);
+ thread.start();
+ final Handler handler = new Handler(thread.getLooper());
+ handler.post(() -> {
List<String> aidlSensors = new ArrayList<>();
- final String[] instances = mAidlInstanceNameSupplier.get();
+ final String[] instances =
+ ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR);
if (instances != null) {
aidlSensors.addAll(Lists.newArrayList(instances));
}
- providers.addAll(getAidlProviders(
- Utils.filterAvailableHalInstances(getContext(), aidlSensors)));
- return providers;
+ registerAuthenticatorsForService(aidlSensors, hidlSensors);
});
+ thread.quitSafely();
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void addAuthenticatorsRegisteredCallback(
IFingerprintAuthenticatorsRegisteredCallback callback) {
- mRegistry.addAllRegisteredCallback(callback);
- }
+ if (callback == null) {
+ Slog.e(TAG, "addAuthenticatorsRegisteredCallback, callback is null");
+ return;
+ }
- @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
- @Override
- public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
- mBiometricStateCallback.registerBiometricStateListener(listener);
+ final boolean registered;
+ final boolean hasSensorProps;
+ synchronized (mLock) {
+ registered = mAuthenticatorsRegisteredCallbacks.register(callback);
+ hasSensorProps = !mSensorProps.isEmpty();
+ }
+ if (registered && hasSensorProps) {
+ broadcastAllAuthenticatorsRegistered();
+ } else if (!registered) {
+ Slog.e(TAG, "addAuthenticatorsRegisteredCallback failed to register callback");
+ }
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void onPointerDown(long requestId, int sensorId, int x, int y,
float minor, float major) {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching provider for onFingerDown, sensorId: " + sensorId);
return;
@@ -837,7 +923,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void onPointerUp(long requestId, int sensorId) {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching provider for onFingerUp, sensorId: " + sensorId);
return;
@@ -848,7 +935,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void onUiReady(long requestId, int sensorId) {
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching provider for onUiReady, sensorId: " + sensorId);
return;
@@ -859,7 +947,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) {
- for (ServiceProvider provider : mRegistry.getProviders()) {
+
+ for (ServiceProvider provider : mServiceProviders) {
provider.setUdfpsOverlayController(controller);
}
}
@@ -867,15 +956,22 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void setSidefpsController(@NonNull ISidefpsController controller) {
- for (ServiceProvider provider : mRegistry.getProviders()) {
+
+ for (ServiceProvider provider : mServiceProviders) {
provider.setSidefpsController(controller);
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+ @Override
+ public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ FingerprintService.this.registerBiometricStateListener(listener);
+ }
+
@Override
public void onPowerPressed() {
- for (ServiceProvider provider : mRegistry.getProviders()) {
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ for (ServiceProvider provider : mServiceProviders) {
provider.onPowerPressed();
}
}
@@ -885,7 +981,6 @@
this(context, BiometricContext.getInstance(context),
() -> IBiometricService.Stub.asInterface(
ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
- () -> ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR),
(fqName) -> IFingerprint.Stub.asInterface(
Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName))));
}
@@ -893,34 +988,61 @@
@VisibleForTesting
FingerprintService(Context context,
BiometricContext biometricContext,
- Supplier<IBiometricService> biometricServiceSupplier,
- Supplier<String[]> aidlInstanceNameSupplier,
+ Supplier<IBiometricService> biometricServiceProvider,
Function<String, IFingerprint> fingerprintProvider) {
super(context);
mBiometricContext = biometricContext;
- mAidlInstanceNameSupplier = aidlInstanceNameSupplier;
+ mBiometricServiceSupplier = biometricServiceProvider;
mIFingerprintProvider = fingerprintProvider;
mAppOps = context.getSystemService(AppOpsManager.class);
mGestureAvailabilityDispatcher = new GestureAvailabilityDispatcher();
mLockoutResetDispatcher = new LockoutResetDispatcher(context);
mLockPatternUtils = new LockPatternUtils(context);
- mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context));
+ mServiceProviders = new ArrayList<>();
+ mBiometricStateCallback = new BiometricStateCallback();
+ mAuthenticatorsRegisteredCallbacks = new RemoteCallbackList<>();
+ mSensorProps = new ArrayList<>();
mHandler = new Handler(Looper.getMainLooper());
- mRegistry = new FingerprintServiceRegistry(mServiceWrapper, biometricServiceSupplier);
- mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
- @Override
- public void onAllAuthenticatorsRegistered(
- List<FingerprintSensorPropertiesInternal> sensors) {
- mBiometricStateCallback.start(mRegistry.getProviders());
- }
- });
}
- @NonNull
- private List<ServiceProvider> getHidlProviders(
+ @VisibleForTesting
+ void registerAuthenticatorsForService(@NonNull List<String> aidlInstanceNames,
@NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
- final List<ServiceProvider> providers = new ArrayList<>();
+ addHidlProviders(hidlSensors);
+ addAidlProviders(Utils.filterAvailableHalInstances(getContext(), aidlInstanceNames));
+ final IBiometricService biometricService = mBiometricServiceSupplier.get();
+
+ // Register each sensor individually with BiometricService
+ for (ServiceProvider provider : mServiceProviders) {
+ final List<FingerprintSensorPropertiesInternal> props =
+ provider.getSensorProperties();
+ for (FingerprintSensorPropertiesInternal prop : props) {
+ final int sensorId = prop.sensorId;
+ @BiometricManager.Authenticators.Types final int strength =
+ Utils.propertyStrengthToAuthenticatorStrength(prop.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);
+ }
+ }
+ }
+
+ synchronized (mLock) {
+ for (ServiceProvider provider : mServiceProviders) {
+ mSensorProps.addAll(provider.getSensorProperties());
+ }
+ }
+
+ broadcastCurrentEnrollmentState(null); // broadcasts to all listeners
+ broadcastAllAuthenticatorsRegistered();
+ }
+
+ private void addHidlProviders(List<FingerprintSensorPropertiesInternal> hidlSensors) {
for (FingerprintSensorPropertiesInternal hidlSensor : hidlSensors) {
final Fingerprint21 fingerprint21;
if ((Build.IS_USERDEBUG || Build.IS_ENG)
@@ -937,16 +1059,11 @@
mBiometricStateCallback, hidlSensor, mHandler,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
}
- providers.add(fingerprint21);
+ mServiceProviders.add(fingerprint21);
}
-
- return providers;
}
- @NonNull
- private List<ServiceProvider> getAidlProviders(@NonNull List<String> instances) {
- final List<ServiceProvider> providers = new ArrayList<>();
-
+ private void addAidlProviders(List<String> instances) {
for (String instance : instances) {
final String fqName = IFingerprint.DESCRIPTOR + "/" + instance;
final IFingerprint fp = mIFingerprintProvider.apply(fqName);
@@ -958,7 +1075,7 @@
mLockoutResetDispatcher, mGestureAvailabilityDispatcher,
mBiometricContext);
Slog.i(TAG, "Adding AIDL provider: " + fqName);
- providers.add(provider);
+ mServiceProviders.add(provider);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
}
@@ -966,8 +1083,38 @@
Slog.e(TAG, "Unable to get declared service: " + fqName);
}
}
+ }
- return providers;
+ // Notifies the callbacks that all of the authenticators have been registered and removes the
+ // invoked callbacks from the callback list.
+ private void broadcastAllAuthenticatorsRegistered() {
+ // Make a local copy of the data so it can be used outside of the synchronized block when
+ // making Binder calls.
+ final List<IFingerprintAuthenticatorsRegisteredCallback> callbacks = new ArrayList<>();
+ final List<FingerprintSensorPropertiesInternal> props;
+ synchronized (mLock) {
+ if (!mSensorProps.isEmpty()) {
+ props = new ArrayList<>(mSensorProps);
+ } else {
+ Slog.e(TAG, "mSensorProps is empty");
+ return;
+ }
+ final int n = mAuthenticatorsRegisteredCallbacks.beginBroadcast();
+ for (int i = 0; i < n; ++i) {
+ final IFingerprintAuthenticatorsRegisteredCallback cb =
+ mAuthenticatorsRegisteredCallbacks.getBroadcastItem(i);
+ callbacks.add(cb);
+ mAuthenticatorsRegisteredCallbacks.unregister(cb);
+ }
+ mAuthenticatorsRegisteredCallbacks.finishBroadcast();
+ }
+ for (IFingerprintAuthenticatorsRegisteredCallback cb : callbacks) {
+ try {
+ cb.onAllAuthenticatorsRegistered(props);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception in onAllAuthenticatorsRegistered", e);
+ }
+ }
}
@Override
@@ -975,9 +1122,51 @@
publishBinderService(Context.FINGERPRINT_SERVICE, mServiceWrapper);
}
+ @Nullable
+ private ServiceProvider getProviderForSensor(int sensorId) {
+ for (ServiceProvider provider : mServiceProviders) {
+ if (provider.containsSensor(sensorId)) {
+ return provider;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * For devices with only a single provider, returns that provider. If multiple providers,
+ * returns the first one. If no providers, returns null.
+ */
+ @Nullable
+ private Pair<Integer, ServiceProvider> getSingleProvider() {
+ final List<FingerprintSensorPropertiesInternal> properties = getSensorProperties();
+ if (properties.isEmpty()) {
+ Slog.e(TAG, "No providers found");
+ return null;
+ }
+
+ // Theoretically we can just return the first provider, but maybe this is easier to
+ // understand.
+ final int sensorId = properties.get(0).sensorId;
+ for (ServiceProvider provider : mServiceProviders) {
+ if (provider.containsSensor(sensorId)) {
+ return new Pair<>(sensorId, provider);
+ }
+ }
+
+ Slog.e(TAG, "Provider not found");
+ return null;
+ }
+
+ @NonNull
+ private List<FingerprintSensorPropertiesInternal> getSensorProperties() {
+ synchronized (mLock) {
+ return mSensorProps;
+ }
+ }
+
@NonNull
private List<Fingerprint> getEnrolledFingerprintsDeprecated(int userId, String opPackageName) {
- final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for getEnrolledFingerprintsDeprecated, caller: "
+ opPackageName);
@@ -1040,7 +1229,7 @@
if (Utils.isVirtualEnabled(getContext())) {
Slog.i(TAG, "Sync virtual enrollments");
final int userId = ActivityManager.getCurrentUser();
- for (ServiceProvider provider : mRegistry.getProviders()) {
+ for (ServiceProvider provider : mServiceProviders) {
for (FingerprintSensorPropertiesInternal props : provider.getSensorProperties()) {
provider.scheduleInternalCleanup(props.sensorId, userId, null /* callback */,
true /* favorHalEnrollments */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java
deleted file mode 100644
index 33810b7..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2022 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.fingerprint;
-
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.hardware.biometrics.BiometricManager;
-import android.hardware.biometrics.IBiometricService;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
-import android.hardware.fingerprint.IFingerprintService;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.Utils;
-import com.android.server.biometrics.sensors.BiometricServiceRegistry;
-
-import java.util.List;
-import java.util.function.Supplier;
-
-/** Registry for {@link IFingerprintService} providers. */
-public class FingerprintServiceRegistry extends BiometricServiceRegistry<ServiceProvider,
- FingerprintSensorPropertiesInternal, IFingerprintAuthenticatorsRegisteredCallback> {
-
- private static final String TAG = "FingerprintServiceRegistry";
-
- @NonNull
- private final IFingerprintService mService;
-
- /** Creates a new registry tied to the given service. */
- public FingerprintServiceRegistry(@NonNull IFingerprintService service,
- @Nullable Supplier<IBiometricService> biometricSupplier) {
- super(biometricSupplier);
- mService = service;
- }
-
- @Override
- protected void registerService(@NonNull IBiometricService service,
- @NonNull FingerprintSensorPropertiesInternal props) {
- @BiometricManager.Authenticators.Types final int strength =
- Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength);
- try {
- service.registerAuthenticator(props.sensorId, TYPE_FINGERPRINT, strength,
- new FingerprintAuthenticator(mService, props.sensorId));
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when registering sensorId: " + props.sensorId);
- }
- }
-
- @Override
- protected void invokeRegisteredCallback(
- @NonNull IFingerprintAuthenticatorsRegisteredCallback callback,
- @NonNull List<FingerprintSensorPropertiesInternal> allProps) throws RemoteException {
- callback.onAllAuthenticatorsRegistered(allProps);
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 9075e7e..275d7e4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -28,11 +28,14 @@
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
+import android.util.proto.ProtoOutputStream;
-import com.android.server.biometrics.sensors.BiometricServiceProvider;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.List;
/**
@@ -56,8 +59,23 @@
* fail safely.
*/
@SuppressWarnings("deprecation")
-public interface ServiceProvider extends
- BiometricServiceProvider<FingerprintSensorPropertiesInternal> {
+public interface ServiceProvider {
+ /**
+ * Checks if the specified sensor is owned by this provider.
+ */
+ boolean containsSensor(int sensorId);
+
+ @NonNull
+ List<FingerprintSensorPropertiesInternal> getSensorProperties();
+
+ /**
+ * Returns the internal properties of the specified sensor, if owned by this provider.
+ *
+ * @param sensorId The ID of a fingerprint sensor, or -1 for any sensor.
+ * @return An object representing the internal properties of the specified sensor.
+ */
+ @Nullable
+ FingerprintSensorPropertiesInternal getSensorProperties(int sensorId);
void scheduleResetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken);
@@ -108,11 +126,16 @@
void scheduleInternalCleanup(int sensorId, int userId,
@Nullable ClientMonitorCallback callback, boolean favorHalEnrollments);
+ boolean isHardwareDetected(int sensorId);
+
void rename(int sensorId, int fingerId, int userId, @NonNull String name);
@NonNull
List<Fingerprint> getEnrolledFingerprints(int sensorId, int userId);
+ @LockoutTracker.LockoutMode
+ int getLockoutModeForUser(int sensorId, int userId);
+
/**
* Requests for the authenticatorId (whose source of truth is in the TEE or equivalent) to
* be invalidated. See {@link com.android.server.biometrics.sensors.InvalidationRequesterClient}
@@ -120,6 +143,7 @@
void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
@NonNull IInvalidationCallback callback);
+ long getAuthenticatorId(int sensorId, int userId);
void onPointerDown(long requestId, int sensorId, int x, int y, float minor, float major);
@@ -137,6 +161,13 @@
*/
void setSidefpsController(@NonNull ISidefpsController controller);
+ void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
+ boolean clearSchedulerBuffer);
+
+ void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd);
+
+ void dumpInternal(int sensorId, @NonNull PrintWriter pw);
+
@NonNull
ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@NonNull String opPackageName);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 3fe6332..2dc00520 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -565,11 +565,6 @@
}
@Override
- public boolean hasEnrollments(int sensorId, int userId) {
- return !getEnrolledFingerprints(sensorId, userId).isEmpty();
- }
-
- @Override
public void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
@NonNull IInvalidationCallback callback) {
mHandler.post(() -> {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 0e6df8e..ed482f0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -789,11 +789,6 @@
}
@Override
- public boolean hasEnrollments(int sensorId, int userId) {
- return !getEnrolledFingerprints(sensorId, userId).isEmpty();
- }
-
- @Override
@LockoutTracker.LockoutMode public int getLockoutModeForUser(int sensorId, int userId) {
return mLockoutTracker.getLockoutModeForUser(userId);
}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 05d32d1..6145a91c 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -1008,8 +1008,9 @@
final String packageName = info.topActivity.getPackageName();
// If the app didn't change, there's nothing to do. Otherwise, we have to
// update the category and re-apply the brightness correction.
- if (mForegroundAppPackageName != null
- && mForegroundAppPackageName.equals(packageName)) {
+ String currentForegroundAppPackageName = mForegroundAppPackageName;
+ if (currentForegroundAppPackageName != null
+ && currentForegroundAppPackageName.equals(packageName)) {
return;
}
mPendingForegroundAppPackageName = packageName;
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 7b60345..4e0489a 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -218,6 +218,7 @@
}, pw, "", 200);
}
+ /** Whether a real dream is occurring. */
private boolean isDreamingInternal() {
synchronized (mLock) {
return mCurrentDreamToken != null && !mCurrentDreamIsPreview
@@ -225,6 +226,13 @@
}
}
+ /** Whether a real dream, or a dream preview is occurring. */
+ private boolean isDreamingOrInPreviewInternal() {
+ synchronized (mLock) {
+ return mCurrentDreamToken != null && !mCurrentDreamIsWaking;
+ }
+ }
+
protected void requestStartDreamFromShell() {
requestDreamInternal();
}
@@ -695,6 +703,19 @@
}
@Override // Binder call
+ public boolean isDreamingOrInPreview() {
+ checkPermission(android.Manifest.permission.READ_DREAM_STATE);
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return isDreamingOrInPreviewInternal();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+
+ @Override // Binder call
public void dream() {
checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 2070c2b..1e86d02 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1445,6 +1445,11 @@
}
if (flags != data.getFlags()) {
+ int changedFlags = data.getFlags() ^ flags;
+ if ((changedFlags & FLAG_SUPPRESS_NOTIFICATION) != 0) {
+ // Suppress notification flag changed, clear any effects
+ clearEffectsLocked(key);
+ }
data.setFlags(flags);
// Shouldn't alert again just because of a flag change.
r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
@@ -1596,6 +1601,20 @@
updateLightsLocked();
}
+ @GuardedBy("mNotificationLock")
+ private void clearEffectsLocked(String key) {
+ if (key.equals(mSoundNotificationKey)) {
+ clearSoundLocked();
+ }
+ if (key.equals(mVibrateNotificationKey)) {
+ clearVibrateLocked();
+ }
+ boolean removed = mLights.remove(key);
+ if (removed) {
+ updateLightsLocked();
+ }
+ }
+
protected final BroadcastReceiver mLocaleChangeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -5170,7 +5189,8 @@
extras,
mRankingHelper.findExtractor(ValidateNotificationPeople.class),
MATCHES_CALL_FILTER_CONTACTS_TIMEOUT_MS,
- MATCHES_CALL_FILTER_TIMEOUT_AFFINITY);
+ MATCHES_CALL_FILTER_TIMEOUT_AFFINITY,
+ Binder.getCallingUid());
}
@Override
diff --git a/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java b/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java
index fde45f71..acb36a0 100644
--- a/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java
+++ b/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java
@@ -42,10 +42,6 @@
*/
public static void scheduleJob(Context context, long rescheduleTimeMillis) {
JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
- // if the job already exists for some reason, cancel & reschedule
- if (jobScheduler.getPendingJob(JOB_ID) != null) {
- jobScheduler.cancel(JOB_ID);
- }
ComponentName component = new ComponentName(
context, ReviewNotificationPermissionsJobService.class);
JobInfo newJob = new JobInfo.Builder(JOB_ID, component)
diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index 7d7f3a9..c0bc474 100644
--- a/services/core/java/com/android/server/notification/ZenLog.java
+++ b/services/core/java/com/android/server/notification/ZenLog.java
@@ -66,6 +66,8 @@
private static final int TYPE_SET_NOTIFICATION_POLICY = 16;
private static final int TYPE_SET_CONSOLIDATED_ZEN_POLICY = 17;
private static final int TYPE_MATCHES_CALL_FILTER = 18;
+ private static final int TYPE_RECORD_CALLER = 19;
+ private static final int TYPE_CHECK_REPEAT_CALLER = 20;
private static int sNext;
private static int sSize;
@@ -167,11 +169,28 @@
+ hintsToString(newHints) + ",listeners=" + listenerCount);
}
- /*
+ /**
* Trace calls to matchesCallFilter with the result of the call and the reason for the result.
*/
- public static void traceMatchesCallFilter(boolean result, String reason) {
- append(TYPE_MATCHES_CALL_FILTER, "result=" + result + ", reason=" + reason);
+ public static void traceMatchesCallFilter(boolean result, String reason, int callingUid) {
+ append(TYPE_MATCHES_CALL_FILTER, "result=" + result + ", reason=" + reason
+ + ", calling uid=" + callingUid);
+ }
+
+ /**
+ * Trace what information is available about an incoming call when it's recorded
+ */
+ public static void traceRecordCaller(boolean hasPhone, boolean hasUri) {
+ append(TYPE_RECORD_CALLER, "has phone number=" + hasPhone + ", has uri=" + hasUri);
+ }
+
+ /**
+ * Trace what information was provided about a caller when checking whether it is from a repeat
+ * caller
+ */
+ public static void traceCheckRepeatCaller(boolean found, boolean hasPhone, boolean hasUri) {
+ append(TYPE_CHECK_REPEAT_CALLER, "res=" + found + ", given phone number=" + hasPhone
+ + ", given uri=" + hasUri);
}
private static String subscribeResult(IConditionProvider provider, RemoteException e) {
@@ -198,6 +217,8 @@
case TYPE_SET_NOTIFICATION_POLICY: return "set_notification_policy";
case TYPE_SET_CONSOLIDATED_ZEN_POLICY: return "set_consolidated_policy";
case TYPE_MATCHES_CALL_FILTER: return "matches_call_filter";
+ case TYPE_RECORD_CALLER: return "record_caller";
+ case TYPE_CHECK_REPEAT_CALLER: return "check_repeat_caller";
default: return "unknown";
}
}
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
index b0d40ef..7e36aed 100644
--- a/services/core/java/com/android/server/notification/ZenModeFiltering.java
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -101,23 +101,24 @@
*/
public static boolean matchesCallFilter(Context context, int zen, NotificationManager.Policy
consolidatedPolicy, UserHandle userHandle, Bundle extras,
- ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
+ ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity,
+ int callingUid) {
if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
- ZenLog.traceMatchesCallFilter(false, "no interruptions");
+ ZenLog.traceMatchesCallFilter(false, "no interruptions", callingUid);
return false; // nothing gets through
}
if (zen == Global.ZEN_MODE_ALARMS) {
- ZenLog.traceMatchesCallFilter(false, "alarms only");
+ ZenLog.traceMatchesCallFilter(false, "alarms only", callingUid);
return false; // not an alarm
}
if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
if (consolidatedPolicy.allowRepeatCallers()
&& REPEAT_CALLERS.isRepeat(context, extras, null)) {
- ZenLog.traceMatchesCallFilter(true, "repeat caller");
+ ZenLog.traceMatchesCallFilter(true, "repeat caller", callingUid);
return true;
}
if (!consolidatedPolicy.allowCalls()) {
- ZenLog.traceMatchesCallFilter(false, "calls not allowed");
+ ZenLog.traceMatchesCallFilter(false, "calls not allowed", callingUid);
return false; // no other calls get through
}
if (validator != null) {
@@ -125,11 +126,12 @@
contactsTimeoutMs, timeoutAffinity);
boolean match =
audienceMatches(consolidatedPolicy.allowCallsFrom(), contactAffinity);
- ZenLog.traceMatchesCallFilter(match, "contact affinity " + contactAffinity);
+ ZenLog.traceMatchesCallFilter(match, "contact affinity " + contactAffinity,
+ callingUid);
return match;
}
}
- ZenLog.traceMatchesCallFilter(true, "no restrictions");
+ ZenLog.traceMatchesCallFilter(true, "no restrictions", callingUid);
return true;
}
@@ -419,6 +421,7 @@
private synchronized void recordCallers(String[] people, ArraySet<String> phoneNumbers,
long now) {
+ boolean recorded = false, hasTel = false, hasOther = false;
for (int i = 0; i < people.length; i++) {
String person = people[i];
if (person == null) continue;
@@ -427,10 +430,16 @@
// while ideally we should not need to decode this, sometimes we have seen tel
// numbers given in an encoded format
String tel = Uri.decode(uri.getSchemeSpecificPart());
- if (tel != null) mTelCalls.put(tel, now);
+ if (tel != null) {
+ mTelCalls.put(tel, now);
+ recorded = true;
+ hasTel = true;
+ }
} else {
// for non-tel calls, store the entire string, uri-component and all
mOtherCalls.put(person, now);
+ recorded = true;
+ hasOther = true;
}
}
@@ -438,9 +447,16 @@
// provided; these are in the format of just a phone number string
if (phoneNumbers != null) {
for (String num : phoneNumbers) {
- if (num != null) mTelCalls.put(num, now);
+ if (num != null) {
+ mTelCalls.put(num, now);
+ recorded = true;
+ hasTel = true;
+ }
}
}
+ if (recorded) {
+ ZenLog.traceRecordCaller(hasTel, hasOther);
+ }
}
// helper function to check mTelCalls array for a number, and also check its decoded
@@ -468,6 +484,8 @@
// previously recorded phone call.
private synchronized boolean checkCallers(Context context, String[] people,
ArraySet<String> phoneNumbers) {
+ boolean found = false, checkedTel = false, checkedOther = false;
+
// get the default country code for checking telephone numbers
final String defaultCountryCode =
context.getSystemService(TelephonyManager.class).getNetworkCountryIso();
@@ -477,12 +495,14 @@
final Uri uri = Uri.parse(person);
if ("tel".equals(uri.getScheme())) {
String number = uri.getSchemeSpecificPart();
+ checkedTel = true;
if (checkForNumber(number, defaultCountryCode)) {
- return true;
+ found = true;
}
} else {
+ checkedOther = true;
if (mOtherCalls.containsKey(person)) {
- return true;
+ found = true;
}
}
}
@@ -490,14 +510,16 @@
// also check any passed-in phone numbers
if (phoneNumbers != null) {
for (String num : phoneNumbers) {
+ checkedTel = true;
if (checkForNumber(num, defaultCountryCode)) {
- return true;
+ found = true;
}
}
}
// no matches
- return false;
+ ZenLog.traceCheckRepeatCaller(found, checkedTel, checkedOther);
+ return found;
}
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 6135fe8..d426679 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -177,10 +177,12 @@
}
public boolean matchesCallFilter(UserHandle userHandle, Bundle extras,
- ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
+ ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity,
+ int callingUid) {
synchronized (mConfig) {
return ZenModeFiltering.matchesCallFilter(mContext, mZenMode, mConsolidatedPolicy,
- userHandle, extras, validator, contactsTimeoutMs, timeoutAffinity);
+ userHandle, extras, validator, contactsTimeoutMs, timeoutAffinity,
+ callingUid);
}
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 7159673..51b36dd 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -39,6 +39,7 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.IActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -1416,16 +1417,18 @@
private static void broadcastActionOverlayChanged(@NonNull final Set<String> targetPackages,
final int userId) {
+ final PackageManagerInternal pmInternal =
+ LocalServices.getService(PackageManagerInternal.class);
+ final ActivityManagerInternal amInternal =
+ LocalServices.getService(ActivityManagerInternal.class);
CollectionUtils.forEach(targetPackages, target -> {
final Intent intent = new Intent(ACTION_OVERLAY_CHANGED,
Uri.fromParts("package", target, null));
intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- try {
- ActivityManager.getService().broadcastIntent(null, intent, null, null, 0, null,
- null, null, android.app.AppOpsManager.OP_NONE, null, false, false, userId);
- } catch (RemoteException e) {
- Slog.e(TAG, "broadcastActionOverlayChanged remote exception", e);
- }
+ final int[] allowList = pmInternal.getVisibilityAllowList(target, userId);
+ amInternal.broadcastIntent(intent, null /* resultTo */, null /* requiredPermissions */,
+ false /* serialized */, userId, allowList, null /* filterExtrasForReceiver */,
+ null /* bOptions */);
});
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index b05e44f..da76738 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -306,7 +306,7 @@
} else {
final String resourceName = getNextArgRequired();
final String typeStr = getNextArgRequired();
- final String strData = getNextArgRequired();
+ final String strData = String.join(" ", peekRemainingArgs());
addOverlayValue(overlayBuilder, resourceName, typeStr, strData);
}
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 2aec187..4e9c472 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -28,7 +28,6 @@
import android.annotation.AppIdInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.BroadcastOptions;
@@ -41,19 +40,20 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.PowerExemptionManager;
-import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.text.TextUtils;
+import android.util.IntArray;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.Supplier;
/**
* Helper class to send broadcasts for various situations.
@@ -80,6 +80,7 @@
final int flags, final String targetPkg, final IIntentReceiver finishedReceiver,
final int[] userIds, int[] instantUserIds,
@Nullable SparseArray<int[]> broadcastAllowList,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
@Nullable Bundle bOptions) {
try {
final IActivityManager am = ActivityManager.getService();
@@ -93,11 +94,13 @@
if (ArrayUtils.isEmpty(instantUserIds)) {
doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
- resolvedUserIds, false /* isInstantApp */, broadcastAllowList, bOptions);
+ resolvedUserIds, false /* isInstantApp */, broadcastAllowList,
+ filterExtrasForReceiver, bOptions);
} else {
// send restricted broadcasts for instant apps
doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
- instantUserIds, true /* isInstantApp */, null, bOptions);
+ instantUserIds, true /* isInstantApp */, null,
+ null /* filterExtrasForReceiver */, bOptions);
}
} catch (RemoteException ex) {
}
@@ -113,6 +116,7 @@
public void doSendBroadcast(String action, String pkg, Bundle extras,
int flags, String targetPkg, IIntentReceiver finishedReceiver,
int[] userIds, boolean isInstantApp, @Nullable SparseArray<int[]> broadcastAllowList,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
@Nullable Bundle bOptions) {
for (int userId : userIds) {
final Intent intent = new Intent(action,
@@ -148,45 +152,30 @@
intent, finishedReceiver, requiredPermissions,
finishedReceiver != null, userId,
broadcastAllowList == null ? null : broadcastAllowList.get(userId),
- bOptions);
+ filterExtrasForReceiver, bOptions);
}
}
- public void sendResourcesChangedBroadcast(@NonNull Computer snapshot, boolean mediaStatus,
- boolean replacing, @NonNull String[] pkgNames, @NonNull int[] uids) {
+ public void sendResourcesChangedBroadcast(@NonNull Supplier<Computer> snapshotComputer,
+ boolean mediaStatus, boolean replacing, @NonNull String[] pkgNames,
+ @NonNull int[] uids) {
if (ArrayUtils.isEmpty(pkgNames) || ArrayUtils.isEmpty(uids)) {
return;
}
-
- try {
- final IActivityManager am = ActivityManager.getService();
- if (am == null) {
- return;
- }
-
- final int[] resolvedUserIds = am.getRunningUserIds();
- for (int userId : resolvedUserIds) {
- final var lists = getBroadcastParams(snapshot, pkgNames, uids, userId);
- for (int i = 0; i < lists.size(); i++) {
- // Send broadcasts here
- final Bundle extras = new Bundle(3);
- final BroadcastParams list = lists.get(i);
- extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST,
- list.getPackageNames());
- extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, list.getUids());
- extras.putBoolean(Intent.EXTRA_REPLACING, replacing);
- final SparseArray<int[]> allowList = list.getAllowList().size() == 0
- ? null : list.getAllowList();
- final String action =
- mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
- : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
- sendPackageBroadcast(action, null /* pkg */, extras, 0 /* flags */,
- null /* targetPkg */, null /* finishedReceiver */, new int[]{userId},
- null /* instantUserIds */, allowList, null /* bOptions */);
- }
- }
- } catch (RemoteException ex) {
+ Bundle extras = new Bundle();
+ extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgNames);
+ extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uids);
+ if (replacing) {
+ extras.putBoolean(Intent.EXTRA_REPLACING, replacing);
}
+ String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
+ : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
+ sendPackageBroadcast(action, null /* pkg */, extras, 0 /* flags */,
+ null /* targetPkg */, null /* finishedReceiver */, null /* userIds */,
+ null /* instantUserIds */, null /* broadcastAllowList */,
+ (callingUid, intentExtras) -> filterExtrasChangedPackageList(
+ snapshotComputer.get(), callingUid, intentExtras),
+ null /* bOptions */);
}
/**
@@ -266,7 +255,8 @@
final int flags = !componentNames.contains(packageName)
? Intent.FLAG_RECEIVER_REGISTERED_ONLY : 0;
sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, flags, null, null,
- userIds, instantUserIds, broadcastAllowList, null);
+ userIds, instantUserIds, broadcastAllowList, null /* filterExtrasForReceiver */,
+ null /* bOptions */);
}
public static void sendDeviceCustomizationReadyBroadcast() {
@@ -334,53 +324,72 @@
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
packageName, extras, 0, null, null, userIds, instantUserIds,
- broadcastAllowlist, null);
+ broadcastAllowlist, null /* filterExtrasForReceiver */, null);
}
public void sendFirstLaunchBroadcast(String pkgName, String installerPkg,
int[] userIds, int[] instantUserIds) {
sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, pkgName, null, 0,
- installerPkg, null, userIds, instantUserIds, null /* broadcastAllowList */, null);
+ installerPkg, null, userIds, instantUserIds, null /* broadcastAllowList */,
+ null /* filterExtrasForReceiver */, null);
}
/**
- * Get broadcast params list based on the given package and uid list. The broadcast params are
- * used to send broadcast separately if the given packages have different visibility allow list.
+ * Filter package names for the intent extras {@link Intent#EXTRA_CHANGED_PACKAGE_LIST} and
+ * {@link Intent#EXTRA_CHANGED_UID_LIST} by using the rules of the package visibility.
*
- * @param pkgList The names of packages which have changes.
- * @param uidList The uids of packages which have changes.
- * @param userId The user where packages reside.
- * @return The list of {@link BroadcastParams} object.
+ * @param callingUid The uid that is going to access the intent extras.
+ * @param extras The intent extras to filter
+ * @return An extras that have been filtered, or {@code null} if the given uid is unable to
+ * access all the packages in the extras.
*/
- public List<BroadcastParams> getBroadcastParams(@NonNull Computer snapshot,
- @NonNull String[] pkgList, @NonNull int[] uidList, @UserIdInt int userId) {
- final List<BroadcastParams> lists = new ArrayList<>(pkgList.length);
- // Get allow lists for the pkg in the pkgList. Merge into the existed pkgs and uids if
- // allow lists are the same.
- for (int i = 0; i < pkgList.length; i++) {
- final String pkgName = pkgList[i];
- final int uid = uidList[i];
- if (TextUtils.isEmpty(pkgName) || Process.INVALID_UID == uid) {
+ @Nullable
+ public static Bundle filterExtrasChangedPackageList(@NonNull Computer snapshot, int callingUid,
+ @NonNull Bundle extras) {
+ if (UserHandle.isCore(callingUid)) {
+ // see all
+ return extras;
+ }
+ final String[] pkgs = extras.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ if (ArrayUtils.isEmpty(pkgs)) {
+ return extras;
+ }
+ final int userId = extras.getInt(
+ Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(callingUid));
+ final int[] uids = extras.getIntArray(Intent.EXTRA_CHANGED_UID_LIST);
+ final Pair<String[], int[]> filteredPkgs =
+ filterPackages(snapshot, pkgs, uids, callingUid, userId);
+ if (ArrayUtils.isEmpty(filteredPkgs.first)) {
+ // caller is unable to access this intent
+ return null;
+ }
+ final Bundle filteredExtras = new Bundle(extras);
+ filteredExtras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, filteredPkgs.first);
+ filteredExtras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, filteredPkgs.second);
+ return filteredExtras;
+ }
+
+ @NonNull
+ private static Pair<String[], int[]> filterPackages(@NonNull Computer snapshot,
+ @NonNull String[] pkgs, @Nullable int[] uids, int callingUid, int userId) {
+ final int pkgSize = pkgs.length;
+ final int uidSize = !ArrayUtils.isEmpty(uids) ? uids.length : 0;
+
+ final ArrayList<String> pkgList = new ArrayList<>(pkgSize);
+ final IntArray uidList = uidSize > 0 ? new IntArray(uidSize) : null;
+ for (int i = 0; i < pkgSize; i++) {
+ final String packageName = pkgs[i];
+ if (snapshot.shouldFilterApplication(
+ snapshot.getPackageStateInternal(packageName), callingUid, userId)) {
continue;
}
- int[] allowList = snapshot.getVisibilityAllowList(pkgName, userId);
- if (allowList == null) {
- allowList = new int[0];
- }
- boolean merged = false;
- for (int j = 0; j < lists.size(); j++) {
- final BroadcastParams list = lists.get(j);
- if (Arrays.equals(list.getAllowList().get(userId), allowList)) {
- list.addPackage(pkgName, uid);
- merged = true;
- break;
- }
- }
- if (!merged) {
- lists.add(new BroadcastParams(pkgName, uid, allowList, userId));
+ pkgList.add(packageName);
+ if (uidList != null && i < uidSize) {
+ uidList.add(uids[i]);
}
}
-
- return lists;
+ return new Pair<>(
+ pkgList.size() > 0 ? pkgList.toArray(new String[pkgList.size()]) : null,
+ uidList != null && uidList.size() > 0 ? uidList.toArray() : null);
}
}
diff --git a/services/core/java/com/android/server/pm/BroadcastParams.java b/services/core/java/com/android/server/pm/BroadcastParams.java
deleted file mode 100644
index 279aab0..0000000
--- a/services/core/java/com/android/server/pm/BroadcastParams.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm;
-
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.annotation.UserIdInt;
-import android.util.IntArray;
-import android.util.SparseArray;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * A helper class that contains information about package names and uids that share the same allow
- * list for sending broadcasts. Used by various package helpers.
- */
-final class BroadcastParams {
- private final @NonNull List<String> mPackageNames;
- private final @NonNull IntArray mUids;
- private final @NonNull SparseArray<int[]> mAllowList;
-
- BroadcastParams(@NonNull String packageName, @IntRange(from = 0) int uid,
- @NonNull int[] allowList, @UserIdInt int userId) {
- mPackageNames = new ArrayList<>(Arrays.asList(packageName));
- mUids = IntArray.wrap(new int[]{uid});
- mAllowList = new SparseArray<>(1);
- mAllowList.put(userId, allowList);
- }
-
- public void addPackage(@NonNull String packageName, @IntRange(from = 0) int uid) {
- mPackageNames.add(packageName);
- mUids.add(uid);
- }
-
- public @NonNull String[] getPackageNames() {
- return mPackageNames.toArray(new String[0]);
- }
-
- public @NonNull int[] getUids() {
- return mUids.toArray();
- }
-
- public @NonNull SparseArray<int[]> getAllowList() {
- return mAllowList;
- }
-}
diff --git a/services/core/java/com/android/server/pm/DistractingPackageHelper.java b/services/core/java/com/android/server/pm/DistractingPackageHelper.java
index 53e9754..dbf0d29 100644
--- a/services/core/java/com/android/server/pm/DistractingPackageHelper.java
+++ b/services/core/java/com/android/server/pm/DistractingPackageHelper.java
@@ -27,7 +27,6 @@
import android.util.ArraySet;
import android.util.IntArray;
import android.util.Slog;
-import android.util.SparseArray;
import com.android.internal.util.ArrayUtils;
import com.android.server.pm.pkg.PackageStateInternal;
@@ -127,7 +126,7 @@
if (!changedPackagesList.isEmpty()) {
final String[] changedPackages = changedPackagesList.toArray(
new String[changedPackagesList.size()]);
- sendDistractingPackagesChanged(snapshot, changedPackages, changedUids.toArray(), userId,
+ sendDistractingPackagesChanged(changedPackages, changedUids.toArray(), userId,
restrictionFlags);
mPm.scheduleWritePackageRestrictions(userId);
}
@@ -169,7 +168,7 @@
if (!changedPackages.isEmpty()) {
final String[] packageArray = changedPackages.toArray(
new String[changedPackages.size()]);
- sendDistractingPackagesChanged(snapshot, packageArray, changedUids.toArray(), userId,
+ sendDistractingPackagesChanged(packageArray, changedUids.toArray(), userId,
RESTRICTION_NONE);
mPm.scheduleWritePackageRestrictions(userId);
}
@@ -182,24 +181,21 @@
* @param uidList The uids of packages which have suspension changes.
* @param userId The user where packages reside.
*/
- void sendDistractingPackagesChanged(@NonNull Computer snapshot, @NonNull String[] pkgList,
- int[] uidList, int userId, int distractionFlags) {
- final List<BroadcastParams> lists = mBroadcastHelper.getBroadcastParams(
- snapshot, pkgList, uidList, userId);
+ void sendDistractingPackagesChanged(@NonNull String[] pkgList, int[] uidList, int userId,
+ int distractionFlags) {
+ final Bundle extras = new Bundle();
+ extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
+ extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
+ extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, distractionFlags);
+
final Handler handler = mInjector.getHandler();
- for (int i = 0; i < lists.size(); i++) {
- final Bundle extras = new Bundle(3);
- final BroadcastParams list = lists.get(i);
- extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, list.getPackageNames());
- extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, list.getUids());
- extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, distractionFlags);
- final SparseArray<int[]> allowList = list.getAllowList().size() == 0
- ? null : list.getAllowList();
- handler.post(() -> mBroadcastHelper.sendPackageBroadcast(
- Intent.ACTION_DISTRACTING_PACKAGES_CHANGED, null /* pkg */,
- extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */,
- null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */,
- allowList, null /* bOptions */));
- }
+ handler.post(() -> mBroadcastHelper.sendPackageBroadcast(
+ Intent.ACTION_DISTRACTING_PACKAGES_CHANGED, null /* pkg */,
+ extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */,
+ null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */,
+ null /* broadcastAllowList */,
+ (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
+ mPm.snapshotComputer(), callingUid, intentExtras),
+ null /* bOptions */));
}
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 728d2d1..9db9837 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2626,7 +2626,7 @@
}
final String[] pkgNames = new String[]{res.mRemovedInfo.mRemovedPackage};
final int[] uids = new int[]{res.mRemovedInfo.mUid};
- mBroadcastHelper.sendResourcesChangedBroadcast(mPm.snapshotComputer(),
+ mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer,
false /* mediaStatus */, true /* replacing */, pkgNames, uids);
}
res.mRemovedInfo.sendPackageRemovedBroadcasts(killApp, false /*removedBySystem*/);
@@ -2804,7 +2804,7 @@
}
final String[] pkgNames = new String[]{packageName};
final int[] uids = new int[]{res.mPkg.getUid()};
- mBroadcastHelper.sendResourcesChangedBroadcast(mPm.snapshotComputer(),
+ mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer,
true /* mediaStatus */, true /* replacing */, pkgNames, uids);
}
} else if (!ArrayUtils.isEmpty(res.mLibraryConsumers)) { // if static shared lib
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 72f3850..5ad39f2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2947,7 +2947,7 @@
@Nullable Bundle bOptions) {
mHandler.post(() -> mBroadcastHelper.sendPackageBroadcast(action, pkg, extras, flags,
targetPkg, finishedReceiver, userIds, instantUserIds, broadcastAllowList,
- bOptions));
+ null /* filterExtrasForReceiver */, bOptions));
}
@Override
@@ -6309,21 +6309,6 @@
}
}
- /**
- * Ask the package manager to compile layouts in the given package.
- */
- @Override
- public boolean compileLayouts(String packageName) {
- AndroidPackage pkg;
- synchronized (mLock) {
- pkg = mPackages.get(packageName);
- if (pkg == null) {
- return false;
- }
- }
- return mArtManagerService.compileLayouts(pkg);
- }
-
@Nullable
@Override
public String removeLegacyDefaultBrowserPackageName(int userId) {
@@ -7077,7 +7062,8 @@
mResolveActivity.processName = pkg.getProcessName();
mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS
- | ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS;
+ | ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS
+ | ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
mResolveActivity.theme = 0;
mResolveActivity.exported = true;
mResolveActivity.enabled = true;
@@ -7110,7 +7096,8 @@
mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
mResolveActivity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NEVER;
mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS
- | ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
+ | ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY
+ | ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
mResolveActivity.theme = R.style.Theme_Material_Dialog_Alert;
mResolveActivity.exported = true;
mResolveActivity.enabled = true;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index dba7dcd..da89b9e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -1792,7 +1792,6 @@
String checkProfilesRaw = null;
boolean secondaryDex = false;
String split = null;
- boolean compileLayouts = false;
String opt;
while ((opt = getNextOption()) != null) {
@@ -1812,9 +1811,6 @@
case "-r":
compilationReason = getNextArgRequired();
break;
- case "--compile-layouts":
- compileLayouts = true;
- break;
case "--check-prof":
checkProfilesRaw = getNextArgRequired();
break;
@@ -1848,14 +1844,15 @@
final boolean compilerFilterGiven = compilerFilter != null;
final boolean compilationReasonGiven = compilationReason != null;
- // Make sure exactly one of -m, -r, or --compile-layouts is given.
- if ((!compilerFilterGiven && !compilationReasonGiven && !compileLayouts)
- || (!compilerFilterGiven && compilationReasonGiven && compileLayouts)
- || (compilerFilterGiven && !compilationReasonGiven && compileLayouts)
- || (compilerFilterGiven && compilationReasonGiven && !compileLayouts)
- || (compilerFilterGiven && compilationReasonGiven && compileLayouts)) {
- pw.println("Must specify exactly one of compilation filter (\"-m\"), compilation " +
- "reason (\"-r\"), or compile layouts (\"--compile-layouts\")");
+ // Make sure exactly one of -m, or -r is given.
+ if (compilerFilterGiven && compilationReasonGiven) {
+ pw.println("Cannot use compilation filter (\"-m\") and compilation reason (\"-r\") "
+ + "at the same time");
+ return 1;
+ }
+ if (!compilerFilterGiven && !compilationReasonGiven) {
+ pw.println("Cannot run without any of compilation filter (\"-m\") and compilation "
+ + "reason (\"-r\")");
return 1;
}
@@ -1920,19 +1917,12 @@
pw.flush();
}
- boolean result = true;
- if (compileLayouts) {
- PackageManagerInternal internal = LocalServices.getService(
- PackageManagerInternal.class);
- result = internal.compileLayouts(packageName);
- } else {
- result = secondaryDex
+ final boolean result = secondaryDex
? mInterface.performDexOptSecondary(packageName,
targetCompilerFilter, forceCompilation)
: mInterface.performDexOptMode(packageName,
checkProfiles, targetCompilerFilter, forceCompilation,
true /* bootComplete */, split);
- }
if (!result) {
failedPackages.add(packageName);
}
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index f621d8b..41c6c0f 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -298,7 +298,7 @@
packageNames[i] = pkg.getPackageName();
packageUids[i] = pkg.getUid();
}
- mBroadcastHelper.sendResourcesChangedBroadcast(mPm.snapshotComputer(), mediaStatus,
+ mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer, mediaStatus,
replacing, packageNames, packageUids);
}
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index 01aecd9..6847b70 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -38,7 +38,6 @@
import android.util.ArraySet;
import android.util.IntArray;
import android.util.Slog;
-import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
@@ -184,11 +183,9 @@
}
});
- final Computer newSnapshot = mPm.snapshotComputer();
-
if (!changedPackagesList.isEmpty()) {
final String[] changedPackages = changedPackagesList.toArray(new String[0]);
- sendPackagesSuspendedForUser(newSnapshot,
+ sendPackagesSuspendedForUser(
suspended ? Intent.ACTION_PACKAGES_SUSPENDED
: Intent.ACTION_PACKAGES_UNSUSPENDED,
changedPackages, changedUids.toArray(), userId);
@@ -197,7 +194,7 @@
}
// Send the suspension changed broadcast to ensure suspension state is not stale.
if (!modifiedPackages.isEmpty()) {
- sendPackagesSuspendedForUser(newSnapshot, Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
+ sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
modifiedPackages.toArray(new String[0]), modifiedUids.toArray(), userId);
}
return unmodifiablePackages.toArray(new String[0]);
@@ -326,14 +323,12 @@
}
});
- final Computer newSnapshot = mPm.snapshotComputer();
-
mPm.scheduleWritePackageRestrictions(userId);
if (!unsuspendedPackages.isEmpty()) {
final String[] packageArray = unsuspendedPackages.toArray(
new String[unsuspendedPackages.size()]);
sendMyPackageSuspendedOrUnsuspended(packageArray, false, userId);
- sendPackagesSuspendedForUser(newSnapshot, Intent.ACTION_PACKAGES_UNSUSPENDED,
+ sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_UNSUSPENDED,
packageArray, unsuspendedUids.toArray(), userId);
}
}
@@ -585,23 +580,19 @@
* @param userId The user where packages reside.
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
- void sendPackagesSuspendedForUser(@NonNull Computer snapshot, @NonNull String intent,
- @NonNull String[] pkgList, @NonNull int[] uidList, int userId) {
- final List<BroadcastParams> lists = mBroadcastHelper.getBroadcastParams(
- snapshot, pkgList, uidList, userId);
+ void sendPackagesSuspendedForUser(@NonNull String intent, @NonNull String[] pkgList,
+ @NonNull int[] uidList, int userId) {
final Handler handler = mInjector.getHandler();
- for (int i = 0; i < lists.size(); i++) {
- final Bundle extras = new Bundle(3);
- final BroadcastParams list = lists.get(i);
- extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, list.getPackageNames());
- extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, list.getUids());
- final SparseArray<int[]> allowList = list.getAllowList().size() == 0
- ? null : list.getAllowList();
- handler.post(() -> mBroadcastHelper.sendPackageBroadcast(intent, null /* pkg */,
- extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */,
- null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */,
- allowList, null /* bOptions */));
- }
+ final Bundle extras = new Bundle(3);
+ extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
+ extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
+ handler.post(() -> mBroadcastHelper.sendPackageBroadcast(intent, null /* pkg */,
+ extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */,
+ null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */,
+ null /* broadcastAllowList */,
+ (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
+ mPm.snapshotComputer(), callingUid, intentExtras),
+ null /* bOptions */));
}
private String getKnownPackageName(@NonNull Computer snapshot,
@@ -652,7 +643,7 @@
}
mBroadcastHelper.doSendBroadcast(action, null, intentExtras,
Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, packageName, null,
- targetUserIds, false, null, null);
+ targetUserIds, false, null, null, null);
}
});
}
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 8dc9428..d358e43 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -305,4 +305,7 @@
* for users that already existed on-disk from an older version of Android.
*/
public abstract boolean shouldIgnorePrepareStorageErrors(int userId);
+
+ /** TODO(b/239982558): add javadoc / mention invalid_id is used to unassing */
+ public abstract void assignUserToDisplay(@UserIdInt int userId, int displayId);
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a0335e8..f08bea3 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -107,6 +107,7 @@
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.Xml;
+import android.view.Display;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -620,6 +621,16 @@
@GuardedBy("mUserStates")
private final WatchedUserStates mUserStates = new WatchedUserStates();
+ /**
+ * Used on devices that support background users (key) running on secondary displays (value).
+ *
+ * <p>Is {@code null} by default and instantiated on demand when the users are started on
+ * secondary displays.
+ */
+ @Nullable
+ @GuardedBy("mUsersLock")
+ private SparseIntArray mUsersOnSecondaryDisplays;
+
private static UserManagerService sInstance;
public static UserManagerService getInstance() {
@@ -1663,15 +1674,33 @@
+ ") is visible");
}
- // First check current foreground user (on main display)
+ return isUserVisibleUnchecked(userId);
+ }
+
+ private boolean isUserVisibleUnchecked(@UserIdInt int userId) {
+ // First check current foreground user and their profiles (on main display)
+ if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
+ return true;
+ }
+
+ // TODO(b/239824814): STOPSHIP - add CTS tests (requires change on testing infra)
+ synchronized (mUsersLock) {
+ if (mUsersOnSecondaryDisplays != null) {
+ // TODO(b/239824814): make sure it handles profile as well
+ return (mUsersOnSecondaryDisplays.indexOfKey(userId) >= 0);
+ }
+ }
+
+ return false;
+ }
+
+ private boolean isCurrentUserOrRunningProfileOfCurrentUser(@UserIdInt int userId) {
int currentUserId = Binder.withCleanCallingIdentity(() -> ActivityManager.getCurrentUser());
if (currentUserId == userId) {
return true;
}
- // Then profiles of current user
- // TODO(b/239824814): consider using AMInternal.isCurrentProfile() instead
if (isProfile(userId)) {
int parentId = Binder.withCleanCallingIdentity(() -> getProfileParentId(userId));
if (parentId == currentUserId) {
@@ -1679,10 +1708,24 @@
}
}
- // TODO(b/239824814): support background users on secondary display (and their profiles)
return false;
}
+ // TODO(b/239982558): currently used just by shell command, might need to move to
+ // UserManagerInternal if needed by other components (like WindowManagerService)
+ // TODO(b/239982558): add unit test
+ // TODO(b/239982558): try to merge with isUserVisibleUnchecked()
+ private boolean isUserVisibleOnDisplay(@UserIdInt int userId, int displayId) {
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ return isCurrentUserOrRunningProfileOfCurrentUser(userId);
+ }
+ synchronized (mUsersLock) {
+ // TODO(b/239824814): make sure it handles profile as well
+ return mUsersOnSecondaryDisplays != null && mUsersOnSecondaryDisplays.get(userId,
+ Display.INVALID_DISPLAY) == displayId;
+ }
+ }
+
@Override
public @NonNull String getUserName() {
final int callingUid = Binder.getCallingUid();
@@ -4209,9 +4252,9 @@
UserManager.USER_OPERATION_ERROR_MAX_USERS);
}
// Keep logic in sync with getRemainingCreatableUserCount()
- if (!isGuest && !isManagedProfile && !isDemo && isUserLimitReached()) {
+ if (!isGuest && !isProfile && !isDemo && isUserLimitReached()) {
// If the user limit has been reached, we cannot add a user (except guest/demo).
- // Note that managed profiles can bypass it in certain circumstances (taken
+ // Note that profiles can bypass it in certain circumstances (taken
// into account in the profile check below).
throwCheckedUserOperationException(
"Cannot add user. Maximum user limit is reached.",
@@ -5776,6 +5819,11 @@
pw.println(" --reboot (which does a full reboot) or");
pw.println(" --no-restart (which requires a manual restart)");
pw.println();
+ pw.println(" is-user-visible [--display DISPLAY_ID] <USER_ID>");
+ pw.println(" Checks if the given user is visible in the given display.");
+ pw.println(" If the display option is not set, it uses the user's context to check");
+ pw.println(" (so it emulates what apps would get from UserManager.isUserVisible())");
+ pw.println();
}
@Override
@@ -5792,6 +5840,8 @@
return runReportPackageAllowlistProblems();
case "set-system-user-mode-emulation":
return runSetSystemUserModeEmulation();
+ case "is-user-visible":
+ return runIsUserVisible();
default:
return handleDefaultCommands(cmd);
}
@@ -5841,9 +5891,6 @@
for (int i = 0; i < size; i++) {
final UserInfo user = users.get(i);
final boolean running = am.isUserRunning(user.id, 0);
- final boolean current = user.id == currentUser;
- final boolean hasParent = user.profileGroupId != user.id
- && user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID;
if (verbose) {
final DevicePolicyManagerInternal dpm = getDevicePolicyManagerInternal();
String deviceOwner = "";
@@ -5862,7 +5909,11 @@
Binder.restoreCallingIdentity(ident);
}
}
- pw.printf("%d: id=%d, name=%s, type=%s, flags=%s%s%s%s%s%s%s%s%s\n",
+ final boolean current = user.id == currentUser;
+ final boolean hasParent = user.profileGroupId != user.id
+ && user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID;
+ final boolean visible = isUserVisibleUnchecked(user.id);
+ pw.printf("%d: id=%d, name=%s, type=%s, flags=%s%s%s%s%s%s%s%s%s%s\n",
i,
user.id,
user.name,
@@ -5874,7 +5925,9 @@
user.preCreated ? " (pre-created)" : "",
user.convertedFromPreCreated ? " (converted)" : "",
deviceOwner, profileOwner,
- current ? " (current)" : "");
+ current ? " (current)" : "",
+ visible ? " (visible)" : ""
+ );
} else {
// NOTE: the standard "list users" command is used by integration tests and
// hence should not be changed. If you need to add more info, use the
@@ -6031,6 +6084,56 @@
return 0;
}
+ private int runIsUserVisible() {
+ PrintWriter pw = getOutPrintWriter();
+ int displayId = Display.INVALID_DISPLAY;
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ boolean invalidOption = false;
+ switch (opt) {
+ case "--display":
+ displayId = Integer.parseInt(getNextArgRequired());
+ invalidOption = displayId == Display.INVALID_DISPLAY;
+ break;
+ default:
+ invalidOption = true;
+ }
+ if (invalidOption) {
+ pw.println("Invalid option: " + opt);
+ return -1;
+ }
+ }
+ int userId = UserHandle.parseUserArg(getNextArgRequired());
+ switch (userId) {
+ case UserHandle.USER_ALL:
+ case UserHandle.USER_CURRENT_OR_SELF:
+ case UserHandle.USER_NULL:
+ pw.printf("invalid value (%d) for --user option\n", userId);
+ return -1;
+ case UserHandle.USER_CURRENT:
+ userId = ActivityManager.getCurrentUser();
+ break;
+ }
+
+ boolean isVisible;
+ if (displayId != Display.INVALID_DISPLAY) {
+ isVisible = isUserVisibleOnDisplay(userId, displayId);
+ } else {
+ isVisible = getUserManagerForUser(userId).isUserVisible();
+ }
+ pw.println(isVisible);
+ return 0;
+ }
+
+ /**
+ * Gets the {@link UserManager} associated with the context of the given user.
+ */
+ private UserManager getUserManagerForUser(int userId) {
+ UserHandle user = UserHandle.of(userId);
+ Context context = mContext.createContextAsUser(user, /* flags= */ 0);
+ return context.getSystemService(UserManager.class);
+ }
+
/**
* Confirms if the build is debuggable
*
@@ -6130,12 +6233,17 @@
pw.print(" Cached user IDs (including pre-created): ");
pw.println(Arrays.toString(mUserIdsIncludingPreCreated));
}
-
} // synchronized (mPackagesLock)
// Multiple Users on Multiple Display info
pw.println(" Supports users on secondary displays: "
+ UserManager.isUsersOnSecondaryDisplaysEnabled());
+ synchronized (mUsersLock) {
+ if (mUsersOnSecondaryDisplays != null) {
+ pw.print(" Users on secondary displays: ");
+ pw.println(mUsersOnSecondaryDisplays);
+ }
+ }
// Dump some capabilities
pw.println();
@@ -6686,6 +6794,56 @@
return userData != null && userData.getIgnorePrepareStorageErrors();
}
}
+
+ @Override
+ public void assignUserToDisplay(int userId, int displayId) {
+ if (DBG) {
+ Slogf.d(LOG_TAG, "assignUserToDisplay(%d, %d): mUsersOnSecondaryDisplays=%s",
+ userId, displayId, mUsersOnSecondaryDisplays);
+ }
+ // TODO(b/240613396) throw exception if feature not supported
+
+ if (displayId == Display.INVALID_DISPLAY) {
+ synchronized (mUsersLock) {
+ if (mUsersOnSecondaryDisplays == null) {
+ if (false) {
+ // TODO(b/240613396): remove this if once we check for support above
+ Slogf.wtf(LOG_TAG, "assignUserToDisplay(%d, %d): no "
+ + "mUsersOnSecondaryDisplays", userId, displayId);
+ }
+ return;
+ }
+ if (DBG) {
+ Slogf.d(LOG_TAG, "Removing %d from mUsersOnSecondaryDisplays", userId);
+ }
+ mUsersOnSecondaryDisplays.delete(userId);
+ if (mUsersOnSecondaryDisplays.size() == 0) {
+ if (DBG) {
+ Slogf.d(LOG_TAG, "Removing mUsersOnSecondaryDisplays");
+ }
+ mUsersOnSecondaryDisplays = null;
+ }
+ }
+ return;
+ }
+
+ // TODO(b/239982558) check for invalid cases like:
+ // - userId already assigned to another display
+ // - displayId already assigned to another user
+ synchronized (mUsersLock) {
+ if (mUsersOnSecondaryDisplays == null) {
+ if (DBG) {
+ Slogf.d(LOG_TAG, "Creating mUsersOnSecondaryDisplays");
+ }
+ mUsersOnSecondaryDisplays = new SparseIntArray();
+ }
+ if (DBG) {
+ Slogf.d(LOG_TAG, "Adding %d->%d to mUsersOnSecondaryDisplays",
+ userId, displayId);
+ }
+ mUsersOnSecondaryDisplays.put(userId, displayId);
+ }
+ }
}
/**
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
index 4b0a8e2..466c4c9 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
@@ -103,7 +103,6 @@
pw.println(" <DOMAINS>: space separated list of domains to change, or \"all\" to");
pw.println(" change every domain.");
pw.println(" set-app-links-allowed --user <USER_ID> [--package <PACKAGE>] <ALLOWED>");
- pw.println(" <ENABLED> <DOMAINS>...");
pw.println(" Toggle the auto verified link handling setting for a package.");
pw.println(" --user <USER_ID>: the user to change selections for");
pw.println(" --package <PACKAGE>: the package to set, or \"all\" to set all packages");
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 96823c8a..f378588 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -72,6 +72,12 @@
public class ThermalManagerService extends SystemService {
private static final String TAG = ThermalManagerService.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ /** Input range limits for getThermalHeadroom API */
+ public static final int MIN_FORECAST_SEC = 0;
+ public static final int MAX_FORECAST_SEC = 60;
+
/** Lock to protect listen list. */
private final Object mLock = new Object();
@@ -478,6 +484,13 @@
return Float.NaN;
}
+ if (forecastSeconds < MIN_FORECAST_SEC || forecastSeconds > MAX_FORECAST_SEC) {
+ if (DEBUG) {
+ Slog.d(TAG, "Invalid forecastSeconds: " + forecastSeconds);
+ }
+ return Float.NaN;
+ }
+
return mTemperatureWatcher.getForecast(forecastSeconds);
}
diff --git a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
index 12e68b1..eebd046 100644
--- a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
@@ -96,6 +96,7 @@
"Turning off vibrator " + getVibratorId());
}
controller.off();
+ getVibration().stats().reportVibratorOff();
}
protected void changeAmplitude(float amplitude) {
@@ -104,6 +105,7 @@
"Amplitude changed on vibrator " + getVibratorId() + " to " + amplitude);
}
controller.setAmplitude(amplitude);
+ getVibration().stats().reportSetAmplitude();
}
/**
@@ -147,6 +149,8 @@
if (nextSegmentIndex >= effectSize && repeatIndex >= 0) {
// Count the loops that were played.
int loopSize = effectSize - repeatIndex;
+ int loopSegmentsPlayed = nextSegmentIndex - repeatIndex;
+ getVibration().stats().reportRepetition(loopSegmentsPlayed / loopSize);
nextSegmentIndex = repeatIndex + ((nextSegmentIndex - effectSize) % loopSize);
}
Step nextStep = conductor.nextVibrateStep(nextStartTime, controller, effect,
diff --git a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
index 3bc11c8..f8b9926 100644
--- a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
@@ -67,9 +67,10 @@
Slog.d(VibrationThread.TAG, "Compose " + primitives + " primitives on vibrator "
+ controller.getVibratorInfo().getId());
}
- mVibratorOnResult = controller.on(
- primitives.toArray(new PrimitiveSegment[primitives.size()]),
- getVibration().id);
+ PrimitiveSegment[] primitivesArray =
+ primitives.toArray(new PrimitiveSegment[primitives.size()]);
+ mVibratorOnResult = controller.on(primitivesArray, getVibration().id);
+ getVibration().stats().reportComposePrimitives(mVibratorOnResult, primitivesArray);
return nextSteps(/* segmentsPlayed= */ primitives.size());
} finally {
diff --git a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
index 919f1be..81f52c9 100644
--- a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
@@ -68,8 +68,9 @@
Slog.d(VibrationThread.TAG, "Compose " + pwles + " PWLEs on vibrator "
+ controller.getVibratorInfo().getId());
}
- mVibratorOnResult = controller.on(pwles.toArray(new RampSegment[pwles.size()]),
- getVibration().id);
+ RampSegment[] pwlesArray = pwles.toArray(new RampSegment[pwles.size()]);
+ mVibratorOnResult = controller.on(pwlesArray, getVibration().id);
+ getVibration().stats().reportComposePwle(mVibratorOnResult, pwlesArray);
return nextSteps(/* segmentsPlayed= */ pwles.size());
} finally {
diff --git a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
index 601ae97..419021478 100644
--- a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
@@ -62,6 +62,7 @@
VibrationEffect fallback = getVibration().getFallback(prebaked.getEffectId());
mVibratorOnResult = controller.on(prebaked, getVibration().id);
+ getVibration().stats().reportPerformEffect(mVibratorOnResult, prebaked);
if (mVibratorOnResult == 0 && prebaked.shouldFallback()
&& (fallback instanceof VibrationEffect.Composed)) {
diff --git a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
index 1f0d2d7..6fb9111 100644
--- a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
@@ -148,7 +148,9 @@
"Turning on vibrator " + controller.getVibratorInfo().getId() + " for "
+ duration + "ms");
}
- return controller.on(duration, getVibration().id);
+ long vibratorOnResult = controller.on(duration, getVibration().id);
+ getVibration().stats().reportVibratorOn(vibratorOnResult);
+ return vibratorOnResult;
}
/**
diff --git a/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
index 080a36c..2c6fbbc9 100644
--- a/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
+++ b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
@@ -93,10 +93,8 @@
}
mVibratorsOnMaxDuration = startVibrating(effectMapping, nextSteps);
- if (mVibratorsOnMaxDuration > 0) {
- conductor.vibratorManagerHooks.noteVibratorOn(conductor.getVibration().uid,
- mVibratorsOnMaxDuration);
- }
+ conductor.vibratorManagerHooks.noteVibratorOn(conductor.getVibration().uid,
+ mVibratorsOnMaxDuration);
} finally {
if (mVibratorsOnMaxDuration >= 0) {
// It least one vibrator was started then add a finish step to wait for all
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index d79837b..a375d0a 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -16,10 +16,10 @@
package com.android.server.vibrator;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.CombinedVibration;
import android.os.IBinder;
-import android.os.SystemClock;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.vibrator.PrebakedSegment;
@@ -30,48 +30,60 @@
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.util.FrameworkStatsLog;
+
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.function.Function;
/** Represents a vibration request to the vibrator service. */
final class Vibration {
- private static final String TAG = "Vibration";
private static final SimpleDateFormat DEBUG_DATE_FORMAT =
new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
+ /** Vibration status with reference to values from vibratormanagerservice.proto for logging. */
enum Status {
- RUNNING,
- FINISHED,
- FINISHED_UNEXPECTED, // Didn't terminate in the usual way.
- FORWARDED_TO_INPUT_DEVICES,
- CANCELLED_BINDER_DIED,
- CANCELLED_BY_SCREEN_OFF,
- CANCELLED_BY_SETTINGS_UPDATE,
- CANCELLED_BY_USER,
- CANCELLED_BY_UNKNOWN_REASON,
- CANCELLED_SUPERSEDED,
- IGNORED_ERROR_APP_OPS,
- IGNORED_ERROR_CANCELLING,
- IGNORED_ERROR_SCHEDULING,
- IGNORED_ERROR_TOKEN,
- IGNORED_APP_OPS,
- IGNORED_BACKGROUND,
- IGNORED_UNKNOWN_VIBRATION,
- IGNORED_UNSUPPORTED,
- IGNORED_FOR_EXTERNAL,
- IGNORED_FOR_HIGHER_IMPORTANCE,
- IGNORED_FOR_ONGOING,
- IGNORED_FOR_POWER,
- IGNORED_FOR_RINGER_MODE,
- IGNORED_FOR_SETTINGS,
- IGNORED_SUPERSEDED,
+ UNKNOWN(VibrationProto.UNKNOWN),
+ RUNNING(VibrationProto.RUNNING),
+ FINISHED(VibrationProto.FINISHED),
+ FINISHED_UNEXPECTED(VibrationProto.FINISHED_UNEXPECTED),
+ FORWARDED_TO_INPUT_DEVICES(VibrationProto.FORWARDED_TO_INPUT_DEVICES),
+ CANCELLED_BINDER_DIED(VibrationProto.CANCELLED_BINDER_DIED),
+ CANCELLED_BY_SCREEN_OFF(VibrationProto.CANCELLED_BY_SCREEN_OFF),
+ CANCELLED_BY_SETTINGS_UPDATE(VibrationProto.CANCELLED_BY_SETTINGS_UPDATE),
+ CANCELLED_BY_USER(VibrationProto.CANCELLED_BY_USER),
+ CANCELLED_BY_UNKNOWN_REASON(VibrationProto.CANCELLED_BY_UNKNOWN_REASON),
+ CANCELLED_SUPERSEDED(VibrationProto.CANCELLED_SUPERSEDED),
+ IGNORED_ERROR_APP_OPS(VibrationProto.IGNORED_ERROR_APP_OPS),
+ IGNORED_ERROR_CANCELLING(VibrationProto.IGNORED_ERROR_CANCELLING),
+ IGNORED_ERROR_SCHEDULING(VibrationProto.IGNORED_ERROR_SCHEDULING),
+ IGNORED_ERROR_TOKEN(VibrationProto.IGNORED_ERROR_TOKEN),
+ IGNORED_APP_OPS(VibrationProto.IGNORED_APP_OPS),
+ IGNORED_BACKGROUND(VibrationProto.IGNORED_BACKGROUND),
+ IGNORED_UNKNOWN_VIBRATION(VibrationProto.IGNORED_UNKNOWN_VIBRATION),
+ IGNORED_UNSUPPORTED(VibrationProto.IGNORED_UNSUPPORTED),
+ IGNORED_FOR_EXTERNAL(VibrationProto.IGNORED_FOR_EXTERNAL),
+ IGNORED_FOR_HIGHER_IMPORTANCE(VibrationProto.IGNORED_FOR_HIGHER_IMPORTANCE),
+ IGNORED_FOR_ONGOING(VibrationProto.IGNORED_FOR_ONGOING),
+ IGNORED_FOR_POWER(VibrationProto.IGNORED_FOR_POWER),
+ IGNORED_FOR_RINGER_MODE(VibrationProto.IGNORED_FOR_RINGER_MODE),
+ IGNORED_FOR_SETTINGS(VibrationProto.IGNORED_FOR_SETTINGS),
+ IGNORED_SUPERSEDED(VibrationProto.IGNORED_SUPERSEDED);
+
+ private final int mProtoEnumValue;
+
+ Status(int value) {
+ mProtoEnumValue = value;
+ }
+
+ public int getProtoEnumValue() {
+ return mProtoEnumValue;
+ }
}
- /** Start time using {@link SystemClock#uptimeMillis()}, for calculations. */
- public final long startUptimeMillis;
public final VibrationAttributes attrs;
public final long id;
public final int uid;
@@ -91,17 +103,11 @@
@Nullable
private CombinedVibration mOriginalEffect;
- /**
- * Start/end times in unix epoch time. Only to be used for debugging purposes and to correlate
- * with other system events, any duration calculations should be done use
- * {@link #startUptimeMillis} so as not to be affected by discontinuities created by RTC
- * adjustments.
- */
- private final long mStartTimeDebug;
- private long mEndTimeDebug;
- /** End time using {@link SystemClock#uptimeMillis()}, for calculations. */
- private long mEndUptimeMillis;
- private Status mStatus;
+ /** Vibration status. */
+ private Vibration.Status mStatus;
+
+ /** Vibration runtime stats. */
+ private final VibrationStats mStats = new VibrationStats();
/** A {@link CountDownLatch} to enable waiting for completion. */
private final CountDownLatch mCompletionLatch = new CountDownLatch(1);
@@ -111,34 +117,35 @@
this.token = token;
this.mEffect = effect;
this.id = id;
- this.startUptimeMillis = SystemClock.uptimeMillis();
this.attrs = attrs;
this.uid = uid;
this.opPkg = opPkg;
this.reason = reason;
- mStartTimeDebug = System.currentTimeMillis();
- mStatus = Status.RUNNING;
+ mStatus = Vibration.Status.RUNNING;
+ }
+
+ VibrationStats stats() {
+ return mStats;
}
/**
- * Set the {@link Status} of this vibration and the current system time as this
+ * Set the {@link Status} of this vibration and reports the current system time as this
* vibration end time, for debugging purposes.
*
* <p>This method will only accept given value if the current status is {@link
* Status#RUNNING}.
*/
- public void end(Status status) {
+ public void end(EndInfo info) {
if (hasEnded()) {
// Vibration already ended, keep first ending status set and ignore this one.
return;
}
- mStatus = status;
- mEndUptimeMillis = SystemClock.uptimeMillis();
- mEndTimeDebug = System.currentTimeMillis();
+ mStatus = info.status;
+ mStats.reportEnded(info.endedByUid, info.endedByUsage);
mCompletionLatch.countDown();
}
- /** Waits indefinitely until another thread calls {@link #end(Status)} on this vibration. */
+ /** Waits indefinitely until another thread calls {@link #end} on this vibration. */
public void waitForEnd() throws InterruptedException {
mCompletionLatch.await();
}
@@ -228,16 +235,69 @@
/** Return {@link Vibration.DebugInfo} with read-only debug information about this vibration. */
public Vibration.DebugInfo getDebugInfo() {
- long durationMs = hasEnded() ? mEndUptimeMillis - startUptimeMillis : -1;
- return new Vibration.DebugInfo(
- mStartTimeDebug, mEndTimeDebug, durationMs, mEffect, mOriginalEffect,
- /* scale= */ 0, attrs, uid, opPkg, reason, mStatus);
+ return new Vibration.DebugInfo(mStatus, mStats, mEffect, mOriginalEffect, /* scale= */ 0,
+ attrs, uid, opPkg, reason);
+ }
+
+ /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
+ public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
+ int vibrationType = isRepeating()
+ ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED
+ : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
+ return new VibrationStats.StatsInfo(
+ uid, vibrationType, attrs.getUsage(), mStatus, mStats, completionUptimeMillis);
+ }
+
+ /** Immutable info passed as a signal to end a vibration. */
+ static final class EndInfo {
+ /** The {@link Status} to be set to the vibration when it ends with this info. */
+ @NonNull
+ public final Status status;
+ /** The UID that triggered the vibration that ended this, or -1 if undefined. */
+ public final int endedByUid;
+ /** The VibrationAttributes.USAGE_* of the vibration that ended this, or -1 if undefined. */
+ public final int endedByUsage;
+
+ EndInfo(@NonNull Vibration.Status status) {
+ this(status, /* endedByUid= */ -1, /* endedByUsage= */ -1);
+ }
+
+ EndInfo(@NonNull Vibration.Status status, int endedByUid, int endedByUsage) {
+ this.status = status;
+ this.endedByUid = endedByUid;
+ this.endedByUsage = endedByUsage;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof EndInfo)) return false;
+ EndInfo that = (EndInfo) o;
+ return endedByUid == that.endedByUid
+ && endedByUsage == that.endedByUsage
+ && status == that.status;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(status, endedByUid, endedByUsage);
+ }
+
+ @Override
+ public String toString() {
+ return "EndInfo{"
+ + "status=" + status
+ + ", endedByUid=" + endedByUid
+ + ", endedByUsage=" + endedByUsage
+ + '}';
+ }
}
/** Debug information about vibrations. */
static final class DebugInfo {
- private final long mStartTimeDebug;
- private final long mEndTimeDebug;
+ private final long mCreateTime;
+ private final long mStartTime;
+ private final long mEndTime;
private final long mDurationMs;
private final CombinedVibration mEffect;
private final CombinedVibration mOriginalEffect;
@@ -248,12 +308,13 @@
private final String mReason;
private final Status mStatus;
- DebugInfo(long startTimeDebug, long endTimeDebug, long durationMs,
- CombinedVibration effect, CombinedVibration originalEffect, float scale,
- VibrationAttributes attrs, int uid, String opPkg, String reason, Status status) {
- mStartTimeDebug = startTimeDebug;
- mEndTimeDebug = endTimeDebug;
- mDurationMs = durationMs;
+ DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration effect,
+ @Nullable CombinedVibration originalEffect, float scale, VibrationAttributes attrs,
+ int uid, String opPkg, String reason) {
+ mCreateTime = stats.getCreateTimeDebug();
+ mStartTime = stats.getStartTimeDebug();
+ mEndTime = stats.getEndTimeDebug();
+ mDurationMs = stats.getDurationDebug();
mEffect = effect;
mOriginalEffect = originalEffect;
mScale = scale;
@@ -267,11 +328,13 @@
@Override
public String toString() {
return new StringBuilder()
- .append("startTime: ")
- .append(DEBUG_DATE_FORMAT.format(new Date(mStartTimeDebug)))
+ .append("createTime: ")
+ .append(DEBUG_DATE_FORMAT.format(new Date(mCreateTime)))
+ .append(", startTime: ")
+ .append(DEBUG_DATE_FORMAT.format(new Date(mStartTime)))
.append(", endTime: ")
- .append(mEndTimeDebug == 0 ? null
- : DEBUG_DATE_FORMAT.format(new Date(mEndTimeDebug)))
+ .append(mEndTime == 0 ? null
+ : DEBUG_DATE_FORMAT.format(new Date(mEndTime)))
.append(", durationMs: ")
.append(mDurationMs)
.append(", status: ")
@@ -296,8 +359,8 @@
/** Write this info into given {@code fieldId} on {@link ProtoOutputStream}. */
public void dumpProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- proto.write(VibrationProto.START_TIME, mStartTimeDebug);
- proto.write(VibrationProto.END_TIME, mEndTimeDebug);
+ proto.write(VibrationProto.START_TIME, mStartTime);
+ proto.write(VibrationProto.END_TIME, mEndTime);
proto.write(VibrationProto.DURATION_MS, mDurationMs);
proto.write(VibrationProto.STATUS, mStatus.ordinal());
@@ -421,4 +484,5 @@
proto.end(token);
}
}
+
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationStats.java b/services/core/java/com/android/server/vibrator/VibrationStats.java
new file mode 100644
index 0000000..931be1d
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VibrationStats.java
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2022 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.vibrator;
+
+import android.os.SystemClock;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+
+/** Holds basic stats about the vibration playback and interaction with the vibrator HAL. */
+final class VibrationStats {
+ static final String TAG = "VibrationStats";
+
+ // Milestone timestamps, using SystemClock.uptimeMillis(), for calculations.
+ // - Create: time a vibration object was created, which is closer to when the service receives a
+ // vibrate request.
+ // - Start: time a vibration started to play, which is closer to the time that the
+ // VibrationEffect started playing the very first segment.
+ // - End: time a vibration ended, even if it never started to play. This can be as soon as the
+ // vibrator HAL reports it has finished the last command, or before it has even started
+ // when the vibration is ignored or cancelled.
+ // Create and end times set by VibratorManagerService only, guarded by its lock.
+ // Start times set by VibrationThread only (single-threaded).
+ private long mCreateUptimeMillis;
+ private long mStartUptimeMillis;
+ private long mEndUptimeMillis;
+
+ // Milestone timestamps, using unix epoch time, only to be used for debugging purposes and
+ // to correlate with other system events. Any duration calculations should be done with the
+ // {create/start/end}UptimeMillis counterparts so as not to be affected by discontinuities
+ // created by RTC adjustments.
+ // Set together with the *UptimeMillis counterparts.
+ private long mCreateTimeDebug;
+ private long mStartTimeDebug;
+ private long mEndTimeDebug;
+
+ // Vibration interruption tracking.
+ // Set by VibratorManagerService only, guarded by its lock.
+ private int mEndedByUid;
+ private int mEndedByUsage;
+ private int mInterruptedUsage;
+
+ // All following counters are set by VibrationThread only (single-threaded):
+ // Counts how many times the VibrationEffect was repeated.
+ private int mRepeatCount;
+ // Total duration, in milliseconds, the vibrator was active with non-zero amplitude.
+ private int mVibratorOnTotalDurationMillis;
+ // Total number of primitives used in compositions.
+ private int mVibrationCompositionTotalSize;
+ private int mVibrationPwleTotalSize;
+ // Counts how many times each IVibrator method was triggered by this vibration.
+ private int mVibratorOnCount;
+ private int mVibratorOffCount;
+ private int mVibratorSetAmplitudeCount;
+ private int mVibratorSetExternalControlCount;
+ private int mVibratorPerformCount;
+ private int mVibratorComposeCount;
+ private int mVibratorComposePwleCount;
+
+ // Ids of vibration effects and primitives used by this vibration, with support flag.
+ // Set by VibrationThread only (single-threaded).
+ private SparseBooleanArray mVibratorEffectsUsed = new SparseBooleanArray();
+ private SparseBooleanArray mVibratorPrimitivesUsed = new SparseBooleanArray();
+
+ VibrationStats() {
+ mCreateUptimeMillis = SystemClock.uptimeMillis();
+ mCreateTimeDebug = System.currentTimeMillis();
+ // Set invalid UID and VibrationAttributes.USAGE values to indicate fields are unset.
+ mEndedByUid = -1;
+ mEndedByUsage = -1;
+ mInterruptedUsage = -1;
+ }
+
+ long getCreateUptimeMillis() {
+ return mCreateUptimeMillis;
+ }
+
+ long getStartUptimeMillis() {
+ return mStartUptimeMillis;
+ }
+
+ long getEndUptimeMillis() {
+ return mEndUptimeMillis;
+ }
+
+ long getCreateTimeDebug() {
+ return mCreateTimeDebug;
+ }
+
+ long getStartTimeDebug() {
+ return mStartTimeDebug;
+ }
+
+ long getEndTimeDebug() {
+ return mEndTimeDebug;
+ }
+
+ /**
+ * Duration calculated for debugging purposes, between the creation of a vibration and the
+ * end time being reported, or -1 if the vibration has not ended.
+ */
+ long getDurationDebug() {
+ return hasEnded() ? (mEndUptimeMillis - mCreateUptimeMillis) : -1;
+ }
+
+ /** Return true if vibration reported it has ended. */
+ boolean hasEnded() {
+ return mEndUptimeMillis > 0;
+ }
+
+ /** Return true if vibration reported it has started triggering the vibrator. */
+ boolean hasStarted() {
+ return mStartUptimeMillis > 0;
+ }
+
+ /**
+ * Set the current system time as this vibration start time, for debugging purposes.
+ *
+ * <p>This indicates the vibration has started to interact with the vibrator HAL and the
+ * device may start vibrating after this point.
+ *
+ * <p>This method will only accept given value if the start timestamp was never set.
+ */
+ void reportStarted() {
+ if (hasEnded() || (mStartUptimeMillis != 0)) {
+ // Vibration already started or ended, keep first time set and ignore this one.
+ return;
+ }
+ mStartUptimeMillis = SystemClock.uptimeMillis();
+ mStartTimeDebug = System.currentTimeMillis();
+ }
+
+ /**
+ * Set status and end cause for this vibration to end, and the current system time as this
+ * vibration end time, for debugging purposes.
+ *
+ * <p>This might be triggered before {@link #reportStarted()}, which indicates this
+ * vibration was cancelled or ignored before it started triggering the vibrator.
+ *
+ * @return true if the status was accepted. This method will only accept given values if
+ * the end timestamp was never set.
+ */
+ boolean reportEnded(int endedByUid, int endedByUsage) {
+ if (hasEnded()) {
+ // Vibration already ended, keep first ending stats set and ignore this one.
+ return false;
+ }
+ mEndedByUid = endedByUid;
+ mEndedByUsage = endedByUsage;
+ mEndUptimeMillis = SystemClock.uptimeMillis();
+ mEndTimeDebug = System.currentTimeMillis();
+ return true;
+ }
+
+ /**
+ * Report this vibration has interrupted another vibration.
+ *
+ * <p>This method will only accept the first value as the one that was interrupted by this
+ * vibration, and will ignore all successive calls.
+ */
+ void reportInterruptedAnotherVibration(int interruptedUsage) {
+ if (mInterruptedUsage < 0) {
+ mInterruptedUsage = interruptedUsage;
+ }
+ }
+
+ /** Report the vibration has looped a few more times. */
+ void reportRepetition(int loops) {
+ mRepeatCount += loops;
+ }
+
+ /** Report a call to vibrator method to turn on for given duration. */
+ void reportVibratorOn(long halResult) {
+ mVibratorOnCount++;
+
+ if (halResult > 0) {
+ // If HAL result is positive then it represents the actual duration it will be ON.
+ mVibratorOnTotalDurationMillis += (int) halResult;
+ }
+ }
+
+ /** Report a call to vibrator method to turn off. */
+ void reportVibratorOff() {
+ mVibratorOffCount++;
+ }
+
+ /** Report a call to vibrator method to change the vibration amplitude. */
+ void reportSetAmplitude() {
+ mVibratorSetAmplitudeCount++;
+ }
+
+ /** Report a call to vibrator method to trigger a vibration effect. */
+ void reportPerformEffect(long halResult, PrebakedSegment prebaked) {
+ mVibratorPerformCount++;
+
+ if (halResult > 0) {
+ // If HAL result is positive then it represents the actual duration of the vibration.
+ mVibratorEffectsUsed.put(prebaked.getEffectId(), true);
+ mVibratorOnTotalDurationMillis += (int) halResult;
+ } else {
+ // Effect unsupported or request failed.
+ mVibratorEffectsUsed.put(prebaked.getEffectId(), false);
+ }
+ }
+
+ /** Report a call to vibrator method to trigger a vibration as a composition of primitives. */
+ void reportComposePrimitives(long halResult, PrimitiveSegment[] primitives) {
+ mVibratorComposeCount++;
+ mVibrationCompositionTotalSize += primitives.length;
+
+ if (halResult > 0) {
+ // If HAL result is positive then it represents the actual duration of the vibration.
+ // Remove the requested delays to update the total time the vibrator was ON.
+ for (PrimitiveSegment primitive : primitives) {
+ halResult -= primitive.getDelay();
+ mVibratorPrimitivesUsed.put(primitive.getPrimitiveId(), true);
+ }
+ if (halResult > 0) {
+ mVibratorOnTotalDurationMillis += (int) halResult;
+ }
+ } else {
+ // One or more primitives were unsupported, or request failed.
+ for (PrimitiveSegment primitive : primitives) {
+ mVibratorPrimitivesUsed.put(primitive.getPrimitiveId(), false);
+ }
+ }
+ }
+
+ /** Report a call to vibrator method to trigger a vibration as a PWLE. */
+ void reportComposePwle(long halResult, RampSegment[] segments) {
+ mVibratorComposePwleCount++;
+ mVibrationPwleTotalSize += segments.length;
+
+ if (halResult > 0) {
+ // If HAL result is positive then it represents the actual duration of the vibration.
+ // Remove the zero-amplitude segments to update the total time the vibrator was ON.
+ for (RampSegment ramp : segments) {
+ if ((ramp.getStartAmplitude() == 0) && (ramp.getEndAmplitude() == 0)) {
+ halResult -= ramp.getDuration();
+ }
+ }
+ if (halResult > 0) {
+ mVibratorOnTotalDurationMillis += (int) halResult;
+ }
+ }
+ }
+
+ /**
+ * Increment the stats for total number of times the {@code setExternalControl} method was
+ * triggered in the vibrator HAL.
+ */
+ void reportSetExternalControl() {
+ mVibratorSetExternalControlCount++;
+ }
+
+ /**
+ * Immutable metrics about this vibration, to be kept in memory until it can be pushed through
+ * {@link com.android.internal.util.FrameworkStatsLog} as a
+ * {@link com.android.internal.util.FrameworkStatsLog#VIBRATION_REPORTED}.
+ */
+ static final class StatsInfo {
+ public final int uid;
+ public final int vibrationType;
+ public final int usage;
+ public final int status;
+ public final boolean endedBySameUid;
+ public final int endedByUsage;
+ public final int interruptedUsage;
+ public final int repeatCount;
+ public final int totalDurationMillis;
+ public final int vibratorOnMillis;
+ public final int startLatencyMillis;
+ public final int endLatencyMillis;
+ public final int halComposeCount;
+ public final int halComposePwleCount;
+ public final int halOnCount;
+ public final int halOffCount;
+ public final int halPerformCount;
+ public final int halSetAmplitudeCount;
+ public final int halSetExternalControlCount;
+ public final int halCompositionSize;
+ public final int halPwleSize;
+ public final int[] halSupportedCompositionPrimitivesUsed;
+ public final int[] halSupportedEffectsUsed;
+ public final int[] halUnsupportedCompositionPrimitivesUsed;
+ public final int[] halUnsupportedEffectsUsed;
+ private boolean mIsWritten;
+
+ StatsInfo(int uid, int vibrationType, int usage, Vibration.Status status,
+ VibrationStats stats, long completionUptimeMillis) {
+ this.uid = uid;
+ this.vibrationType = vibrationType;
+ this.usage = usage;
+ this.status = status.getProtoEnumValue();
+ endedBySameUid = (uid == stats.mEndedByUid);
+ endedByUsage = stats.mEndedByUsage;
+ interruptedUsage = stats.mInterruptedUsage;
+ repeatCount = stats.mRepeatCount;
+
+ // This duration goes from the time this object was created until the time it was
+ // completed. We can use latencies to detect the times between first and last
+ // interaction with vibrator.
+ totalDurationMillis =
+ (int) Math.max(0, completionUptimeMillis - stats.mCreateUptimeMillis);
+ vibratorOnMillis = stats.mVibratorOnTotalDurationMillis;
+
+ if (stats.hasStarted()) {
+ // We only measure latencies for vibrations that actually triggered the vibrator.
+ startLatencyMillis =
+ (int) Math.max(0, stats.mStartUptimeMillis - stats.mCreateUptimeMillis);
+ endLatencyMillis =
+ (int) Math.max(0, completionUptimeMillis - stats.mEndUptimeMillis);
+ } else {
+ startLatencyMillis = endLatencyMillis = 0;
+ }
+
+ halComposeCount = stats.mVibratorComposeCount;
+ halComposePwleCount = stats.mVibratorComposePwleCount;
+ halOnCount = stats.mVibratorOnCount;
+ halOffCount = stats.mVibratorOffCount;
+ halPerformCount = stats.mVibratorPerformCount;
+ halSetAmplitudeCount = stats.mVibratorSetAmplitudeCount;
+ halSetExternalControlCount = stats.mVibratorSetExternalControlCount;
+ halCompositionSize = stats.mVibrationCompositionTotalSize;
+ halPwleSize = stats.mVibrationPwleTotalSize;
+ halSupportedCompositionPrimitivesUsed =
+ filteredKeys(stats.mVibratorPrimitivesUsed, /* supported= */ true);
+ halSupportedEffectsUsed =
+ filteredKeys(stats.mVibratorEffectsUsed, /* supported= */ true);
+ halUnsupportedCompositionPrimitivesUsed =
+ filteredKeys(stats.mVibratorPrimitivesUsed, /* supported= */ false);
+ halUnsupportedEffectsUsed =
+ filteredKeys(stats.mVibratorEffectsUsed, /* supported= */ false);
+ }
+
+ @VisibleForTesting
+ boolean isWritten() {
+ return mIsWritten;
+ }
+
+ void writeVibrationReported() {
+ if (mIsWritten) {
+ Slog.wtf(TAG, "Writing same vibration stats multiple times for uid=" + uid);
+ }
+ mIsWritten = true;
+ // Mapping from this MetricInfo representation and the atom proto VibrationReported.
+ FrameworkStatsLog.write_non_chained(
+ FrameworkStatsLog.VIBRATION_REPORTED,
+ uid, null, vibrationType, usage, status, endedBySameUid, endedByUsage,
+ interruptedUsage, repeatCount, totalDurationMillis, vibratorOnMillis,
+ startLatencyMillis, endLatencyMillis, halComposeCount, halComposePwleCount,
+ halOnCount, halOffCount, halPerformCount, halSetAmplitudeCount,
+ halSetExternalControlCount, halSupportedCompositionPrimitivesUsed,
+ halSupportedEffectsUsed, halUnsupportedCompositionPrimitivesUsed,
+ halUnsupportedEffectsUsed, halCompositionSize, halPwleSize);
+ }
+
+ private static int[] filteredKeys(SparseBooleanArray supportArray, boolean supported) {
+ int count = 0;
+ for (int i = 0; i < supportArray.size(); i++) {
+ if (supportArray.valueAt(i) == supported) count++;
+ }
+ if (count == 0) {
+ return null;
+ }
+ int pos = 0;
+ int[] res = new int[count];
+ for (int i = 0; i < supportArray.size(); i++) {
+ if (supportArray.valueAt(i) == supported) {
+ res[pos++] = supportArray.keyAt(i);
+ }
+ }
+ return res;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index e3d8067..0799b95 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -81,12 +81,12 @@
private final IntArray mSignalVibratorsComplete;
@Nullable
@GuardedBy("mLock")
- private Vibration.Status mSignalCancelStatus = null;
+ private Vibration.EndInfo mSignalCancel = null;
@GuardedBy("mLock")
private boolean mSignalCancelImmediate = false;
@Nullable
- private Vibration.Status mCancelStatus = null;
+ private Vibration.EndInfo mCancelledVibrationEndInfo = null;
private boolean mCancelledImmediately = false; // hard stop
private int mPendingVibrateSteps;
private int mRemainingStartSequentialEffectSteps;
@@ -153,6 +153,9 @@
// This count is decremented at the completion of the step, so we don't subtract one.
mRemainingStartSequentialEffectSteps = sequentialEffect.getEffects().size();
mNextSteps.offer(new StartSequentialEffectStep(this, sequentialEffect));
+ // Vibration will start playing in the Vibrator, following the effect timings and delays.
+ // Report current time as the vibration start time, for debugging.
+ mVibration.stats().reportStarted();
}
public Vibration getVibration() {
@@ -182,24 +185,25 @@
* Calculate the {@link Vibration.Status} based on the current queue state and the expected
* number of {@link StartSequentialEffectStep} to be played.
*/
- public Vibration.Status calculateVibrationStatus() {
+ @Nullable
+ public Vibration.EndInfo calculateVibrationEndInfo() {
if (Build.IS_DEBUGGABLE) {
expectIsVibrationThread(true);
}
- if (mCancelStatus != null) {
- return mCancelStatus;
+ if (mCancelledVibrationEndInfo != null) {
+ return mCancelledVibrationEndInfo;
}
- if (mPendingVibrateSteps > 0
- || mRemainingStartSequentialEffectSteps > 0) {
- return Vibration.Status.RUNNING;
+ if (mPendingVibrateSteps > 0 || mRemainingStartSequentialEffectSteps > 0) {
+ // Vibration still running.
+ return null;
}
// No pending steps, and something happened.
if (mSuccessfulVibratorOnSteps > 0) {
- return Vibration.Status.FINISHED;
+ return new Vibration.EndInfo(Vibration.Status.FINISHED);
}
// If no step was able to turn the vibrator ON successfully.
- return Vibration.Status.IGNORED_UNSUPPORTED;
+ return new Vibration.EndInfo(Vibration.Status.IGNORED_UNSUPPORTED);
}
/**
@@ -305,45 +309,50 @@
if (DEBUG) {
Slog.d(TAG, "Binder died, cancelling vibration...");
}
- notifyCancelled(Vibration.Status.CANCELLED_BINDER_DIED, /* immediate= */ false);
+ notifyCancelled(new Vibration.EndInfo(Vibration.Status.CANCELLED_BINDER_DIED),
+ /* immediate= */ false);
}
/**
* Notify the execution that cancellation is requested. This will be acted upon
* asynchronously in the VibrationThread.
*
+ * <p>Only the first cancel signal will be used to end a cancelled vibration, but subsequent
+ * calls with {@code immediate} flag set to true can still force the first cancel signal to
+ * take effect urgently.
+ *
* @param immediate indicates whether cancellation should abort urgently and skip cleanup steps.
*/
- public void notifyCancelled(@NonNull Vibration.Status cancelStatus, boolean immediate) {
+ public void notifyCancelled(@NonNull Vibration.EndInfo cancelInfo, boolean immediate) {
if (Build.IS_DEBUGGABLE) {
expectIsVibrationThread(false);
}
if (DEBUG) {
- Slog.d(TAG, "Vibration cancel requested with status=" + cancelStatus
+ Slog.d(TAG, "Vibration cancel requested with signal=" + cancelInfo
+ ", immediate=" + immediate);
}
- if ((cancelStatus == null) || !cancelStatus.name().startsWith("CANCEL")) {
- Slog.w(TAG, "Vibration cancel requested with bad status=" + cancelStatus
+ if ((cancelInfo == null) || !cancelInfo.status.name().startsWith("CANCEL")) {
+ Slog.w(TAG, "Vibration cancel requested with bad signal=" + cancelInfo
+ ", using CANCELLED_UNKNOWN_REASON to ensure cancellation.");
- cancelStatus = Vibration.Status.CANCELLED_BY_UNKNOWN_REASON;
+ cancelInfo = new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_UNKNOWN_REASON);
}
synchronized (mLock) {
- if (immediate && mSignalCancelImmediate || (mSignalCancelStatus != null)) {
+ if ((immediate && mSignalCancelImmediate) || (mSignalCancel != null)) {
if (DEBUG) {
Slog.d(TAG, "Vibration cancel request ignored as the vibration "
- + mVibration.id + "is already being cancelled with status="
- + mSignalCancelStatus + ", immediate=" + mSignalCancelImmediate);
+ + mVibration.id + "is already being cancelled with signal="
+ + mSignalCancel + ", immediate=" + mSignalCancelImmediate);
}
return;
}
mSignalCancelImmediate |= immediate;
- if (mSignalCancelStatus == null) {
- mSignalCancelStatus = cancelStatus;
+ if (mSignalCancel == null) {
+ mSignalCancel = cancelInfo;
} else {
if (DEBUG) {
- Slog.d(TAG, "Vibration cancel request new status=" + cancelStatus
- + " ignored as the vibration was already cancelled with status="
- + mSignalCancelStatus + ", but immediate flag was updated to "
+ Slog.d(TAG, "Vibration cancel request new signal=" + cancelInfo
+ + " ignored as the vibration was already cancelled with signal="
+ + mSignalCancel + ", but immediate flag was updated to "
+ mSignalCancelImmediate);
}
}
@@ -401,9 +410,9 @@
if (Build.IS_DEBUGGABLE) {
expectIsVibrationThread(true); // Reads VibrationThread variables as well as signals.
}
- return (mSignalCancelStatus != mCancelStatus)
- || (mSignalCancelImmediate && !mCancelledImmediately)
- || (mSignalVibratorsComplete.size() > 0);
+ return (mSignalCancel != null && mCancelledVibrationEndInfo == null)
+ || (mSignalCancelImmediate && !mCancelledImmediately)
+ || (mSignalVibratorsComplete.size() > 0);
}
/**
@@ -416,7 +425,7 @@
}
int[] vibratorsToProcess = null;
- Vibration.Status doCancelStatus = null;
+ Vibration.EndInfo doCancelInfo = null;
boolean doCancelImmediate = false;
// Collect signals to process, but don't keep the lock while processing them.
synchronized (mLock) {
@@ -426,10 +435,10 @@
}
// This should only happen once.
doCancelImmediate = true;
- doCancelStatus = mSignalCancelStatus;
+ doCancelInfo = mSignalCancel;
}
- if (mSignalCancelStatus != mCancelStatus) {
- doCancelStatus = mSignalCancelStatus;
+ if ((mSignalCancel != null) && (mCancelledVibrationEndInfo == null)) {
+ doCancelInfo = mSignalCancel;
}
if (!doCancelImmediate && mSignalVibratorsComplete.size() > 0) {
// Swap out the queue of completions to process.
@@ -443,11 +452,11 @@
// completion signals that were collected in this call, but we won't process them
// anyway as all steps are cancelled.
if (doCancelImmediate) {
- processCancelImmediately(doCancelStatus);
+ processCancelImmediately(doCancelInfo);
return;
}
- if (doCancelStatus != null) {
- processCancel(doCancelStatus);
+ if (doCancelInfo != null) {
+ processCancel(doCancelInfo);
}
if (vibratorsToProcess != null) {
processVibratorsComplete(vibratorsToProcess);
@@ -460,12 +469,12 @@
* <p>This will remove all steps and replace them with respective results of
* {@link Step#cancel()}.
*/
- public void processCancel(Vibration.Status cancelStatus) {
+ public void processCancel(Vibration.EndInfo cancelInfo) {
if (Build.IS_DEBUGGABLE) {
expectIsVibrationThread(true);
}
- mCancelStatus = cancelStatus;
+ mCancelledVibrationEndInfo = cancelInfo;
// Vibrator callbacks should wait until all steps from the queue are properly cancelled
// and clean up steps are added back to the queue, so they can handle the callback.
List<Step> cleanUpSteps = new ArrayList<>();
@@ -483,13 +492,13 @@
*
* <p>This will remove and trigger {@link Step#cancelImmediately()} in all steps, in order.
*/
- public void processCancelImmediately(Vibration.Status cancelStatus) {
+ public void processCancelImmediately(Vibration.EndInfo cancelInfo) {
if (Build.IS_DEBUGGABLE) {
expectIsVibrationThread(true);
}
mCancelledImmediately = true;
- mCancelStatus = cancelStatus;
+ mCancelledVibrationEndInfo = cancelInfo;
Step step;
while ((step = pollNext()) != null) {
step.cancelImmediately();
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index cecc5c0..e824db10 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -76,7 +76,7 @@
* cleanup tasks, and should not be given new work until {@link #onVibrationThreadReleased}
* is called.
*/
- void onVibrationCompleted(long vibrationId, Vibration.Status status);
+ void onVibrationCompleted(long vibrationId, @NonNull Vibration.EndInfo vibrationEndInfo);
/**
* Tells the manager that the VibrationThread is finished with the previous vibration and
@@ -237,7 +237,8 @@
try {
runCurrentVibrationWithWakeLockAndDeathLink();
} finally {
- clientVibrationCompleteIfNotAlready(Vibration.Status.FINISHED_UNEXPECTED);
+ clientVibrationCompleteIfNotAlready(
+ new Vibration.EndInfo(Vibration.Status.FINISHED_UNEXPECTED));
}
} finally {
mWakeLock.release();
@@ -255,7 +256,8 @@
vibrationBinderToken.linkToDeath(mExecutingConductor, 0);
} catch (RemoteException e) {
Slog.e(TAG, "Error linking vibration to token death", e);
- clientVibrationCompleteIfNotAlready(Vibration.Status.IGNORED_ERROR_TOKEN);
+ clientVibrationCompleteIfNotAlready(
+ new Vibration.EndInfo(Vibration.Status.IGNORED_ERROR_TOKEN));
return;
}
// Ensure that the unlink always occurs now.
@@ -274,11 +276,11 @@
// Indicate that the vibration is complete. This can be called multiple times only for
// convenience of handling error conditions - an error after the client is complete won't
// affect the status.
- private void clientVibrationCompleteIfNotAlready(Vibration.Status completedStatus) {
+ private void clientVibrationCompleteIfNotAlready(@NonNull Vibration.EndInfo vibrationEndInfo) {
if (!mCalledVibrationCompleteCallback) {
mCalledVibrationCompleteCallback = true;
mVibratorManagerHooks.onVibrationCompleted(
- mExecutingConductor.getVibration().id, completedStatus);
+ mExecutingConductor.getVibration().id, vibrationEndInfo);
}
}
@@ -298,12 +300,15 @@
mExecutingConductor.runNextStep();
}
- Vibration.Status status = mExecutingConductor.calculateVibrationStatus();
- // This block can only run once due to mCalledVibrationCompleteCallback.
- if (status != Vibration.Status.RUNNING && !mCalledVibrationCompleteCallback) {
- // First time vibration stopped running, start clean-up tasks and notify
- // callback immediately.
- clientVibrationCompleteIfNotAlready(status);
+ if (!mCalledVibrationCompleteCallback) {
+ // This block can only run once due to mCalledVibrationCompleteCallback.
+ Vibration.EndInfo vibrationEndInfo =
+ mExecutingConductor.calculateVibrationEndInfo();
+ if (vibrationEndInfo != null) {
+ // First time vibration stopped running, start clean-up tasks and notify
+ // callback immediately.
+ clientVibrationCompleteIfNotAlready(vibrationEndInfo);
+ }
}
}
} finally {
diff --git a/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
new file mode 100644
index 0000000..f600a29
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2022 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.vibrator;
+
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.ArrayDeque;
+import java.util.Queue;
+
+/** Helper class for async write of atoms to {@link FrameworkStatsLog} using a given Handler. */
+public class VibratorFrameworkStatsLogger {
+ private static final String TAG = "VibratorFrameworkStatsLogger";
+
+ // VibrationReported pushed atom needs to be throttled to at most one every 10ms.
+ private static final int VIBRATION_REPORTED_MIN_INTERVAL_MILLIS = 10;
+ // We accumulate events that should take 3s to write and drop excessive metrics.
+ private static final int VIBRATION_REPORTED_MAX_QUEUE_SIZE = 300;
+ // Warning about dropping entries after this amount of atoms were dropped by the throttle.
+ private static final int VIBRATION_REPORTED_WARNING_QUEUE_SIZE = 200;
+
+ private final Object mLock = new Object();
+ private final Handler mHandler;
+ private final long mVibrationReportedLogIntervalMillis;
+ private final long mVibrationReportedQueueMaxSize;
+ private final Runnable mConsumeVibrationStatsQueueRunnable =
+ () -> writeVibrationReportedFromQueue();
+
+ @GuardedBy("mLock")
+ private long mLastVibrationReportedLogUptime;
+ @GuardedBy("mLock")
+ private Queue<VibrationStats.StatsInfo> mVibrationStatsQueue = new ArrayDeque<>();
+
+ VibratorFrameworkStatsLogger(Handler handler) {
+ this(handler, VIBRATION_REPORTED_MIN_INTERVAL_MILLIS, VIBRATION_REPORTED_MAX_QUEUE_SIZE);
+ }
+
+ @VisibleForTesting
+ VibratorFrameworkStatsLogger(Handler handler, int vibrationReportedLogIntervalMillis,
+ int vibrationReportedQueueMaxSize) {
+ mHandler = handler;
+ mVibrationReportedLogIntervalMillis = vibrationReportedLogIntervalMillis;
+ mVibrationReportedQueueMaxSize = vibrationReportedQueueMaxSize;
+ }
+
+ /** Writes {@link FrameworkStatsLog#VIBRATOR_STATE_CHANGED} for state ON. */
+ public void writeVibratorStateOnAsync(int uid, long duration) {
+ mHandler.post(
+ () -> FrameworkStatsLog.write_non_chained(
+ FrameworkStatsLog.VIBRATOR_STATE_CHANGED, uid, null,
+ FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON, duration));
+ }
+
+ /** Writes {@link FrameworkStatsLog#VIBRATOR_STATE_CHANGED} for state OFF. */
+ public void writeVibratorStateOffAsync(int uid) {
+ mHandler.post(
+ () -> FrameworkStatsLog.write_non_chained(
+ FrameworkStatsLog.VIBRATOR_STATE_CHANGED, uid, null,
+ FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF,
+ /* duration= */ 0));
+ }
+
+ /**
+ * Writes {@link FrameworkStatsLog#VIBRATION_REPORTED} for given vibration.
+ *
+ * <p>This atom is throttled to be pushed once every 10ms, so this logger can keep a queue of
+ * {@link VibrationStats.StatsInfo} entries to slowly write to statsd.
+ */
+ public void writeVibrationReportedAsync(VibrationStats.StatsInfo metrics) {
+ boolean needsScheduling;
+ long scheduleDelayMs;
+ int queueSize;
+
+ synchronized (mLock) {
+ queueSize = mVibrationStatsQueue.size();
+ needsScheduling = (queueSize == 0);
+
+ if (queueSize < mVibrationReportedQueueMaxSize) {
+ mVibrationStatsQueue.offer(metrics);
+ }
+
+ long nextLogUptime =
+ mLastVibrationReportedLogUptime + mVibrationReportedLogIntervalMillis;
+ scheduleDelayMs = Math.max(0, nextLogUptime - SystemClock.uptimeMillis());
+ }
+
+ if ((queueSize + 1) == VIBRATION_REPORTED_WARNING_QUEUE_SIZE) {
+ Slog.w(TAG, " Approaching vibration metrics queue limit, events might be dropped.");
+ }
+
+ if (needsScheduling) {
+ mHandler.postDelayed(mConsumeVibrationStatsQueueRunnable, scheduleDelayMs);
+ }
+ }
+
+ /** Writes next {@link FrameworkStatsLog#VIBRATION_REPORTED} from the queue. */
+ private void writeVibrationReportedFromQueue() {
+ boolean needsScheduling;
+ VibrationStats.StatsInfo stats;
+
+ synchronized (mLock) {
+ stats = mVibrationStatsQueue.poll();
+ needsScheduling = !mVibrationStatsQueue.isEmpty();
+
+ if (stats != null) {
+ mLastVibrationReportedLogUptime = SystemClock.uptimeMillis();
+ }
+ }
+
+ if (stats == null) {
+ Slog.w(TAG, "Unexpected vibration metric flush with empty queue. Ignoring.");
+ } else {
+ stats.writeVibrationReported();
+ }
+
+ if (needsScheduling) {
+ mHandler.postDelayed(mConsumeVibrationStatsQueueRunnable,
+ mVibrationReportedLogIntervalMillis);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 5ac2f4f..2f12a82 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -129,6 +129,7 @@
private final Context mContext;
private final PowerManager.WakeLock mWakeLock;
private final IBatteryStats mBatteryStatsService;
+ private final VibratorFrameworkStatsLogger mFrameworkStatsLogger;
private final Handler mHandler;
private final VibrationThread mVibrationThread;
private final AppOpsManager mAppOps;
@@ -163,10 +164,12 @@
// When the system is entering a non-interactive state, we want to cancel
// vibrations in case a misbehaving app has abandoned them.
if (shouldCancelOnScreenOffLocked(mNextVibration)) {
- clearNextVibrationLocked(Vibration.Status.CANCELLED_BY_SCREEN_OFF);
+ clearNextVibrationLocked(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF));
}
if (shouldCancelOnScreenOffLocked(mCurrentVibration)) {
- mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_SCREEN_OFF,
+ mCurrentVibration.notifyCancelled(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
/* immediate= */ false);
}
}
@@ -207,6 +210,7 @@
mVibratorManagerRecords = new VibratorManagerRecords(dumpLimit);
mBatteryStatsService = injector.getBatteryStatsService();
+ mFrameworkStatsLogger = injector.getFrameworkStatsLogger(mHandler);
mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -384,7 +388,8 @@
* The Vibration is only returned if it is ongoing after this method returns.
*/
@Nullable
- private Vibration vibrateInternal(int uid, String opPkg, @NonNull CombinedVibration effect,
+ @VisibleForTesting
+ Vibration vibrateInternal(int uid, String opPkg, @NonNull CombinedVibration effect,
@Nullable VibrationAttributes attrs, String reason, IBinder token) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
try {
@@ -399,6 +404,7 @@
return null;
}
attrs = fixupVibrationAttributes(attrs, effect);
+ // Create Vibration.Stats as close to the received request as possible, for tracking.
Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(), effect, attrs,
uid, opPkg, reason);
fillVibrationFallbacks(vib, effect);
@@ -413,32 +419,56 @@
if (DEBUG) {
Slog.d(TAG, "Starting vibrate for vibration " + vib.id);
}
- Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
- vib.uid, vib.opPkg, vib.attrs);
+ int ignoredByUid = -1;
+ int ignoredByUsage = -1;
+ Vibration.Status status = null;
- if (ignoreStatus == null) {
- ignoreStatus = shouldIgnoreVibrationForOngoingLocked(vib);
+ // Check if user settings or DnD is set to ignore this vibration.
+ status = shouldIgnoreVibrationLocked(vib.uid, vib.opPkg, vib.attrs);
+
+ // Check if something has external control, assume it's more important.
+ if ((status == null) && (mCurrentExternalVibration != null)) {
+ status = Vibration.Status.IGNORED_FOR_EXTERNAL;
+ ignoredByUid = mCurrentExternalVibration.externalVibration.getUid();
+ ignoredByUsage = mCurrentExternalVibration.externalVibration
+ .getVibrationAttributes().getUsage();
}
- if (ignoreStatus != null) {
- endVibrationLocked(vib, ignoreStatus);
- return vib;
- }
-
- final long ident = Binder.clearCallingIdentity();
- try {
- if (mCurrentVibration != null) {
- mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED,
- /* immediate= */ false);
+ // Check if ongoing vibration is more important than this vibration.
+ if (status == null) {
+ status = shouldIgnoreVibrationForOngoingLocked(vib);
+ if (status != null) {
+ ignoredByUid = mCurrentVibration.getVibration().uid;
+ ignoredByUsage = mCurrentVibration.getVibration().attrs.getUsage();
}
- Vibration.Status status = startVibrationLocked(vib);
- if (status != Vibration.Status.RUNNING) {
- endVibrationLocked(vib, status);
- }
- return vib;
- } finally {
- Binder.restoreCallingIdentity(ident);
}
+
+ // If not ignored so far then try to start this vibration.
+ if (status == null) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (mCurrentVibration != null) {
+ vib.stats().reportInterruptedAnotherVibration(
+ mCurrentVibration.getVibration().attrs.getUsage());
+ mCurrentVibration.notifyCancelled(
+ new Vibration.EndInfo(
+ Vibration.Status.CANCELLED_SUPERSEDED, vib.uid,
+ vib.attrs.getUsage()),
+ /* immediate= */ false);
+ }
+ status = startVibrationLocked(vib);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ // Ignored or failed to start the vibration, end it and report metrics right away.
+ if (status != Vibration.Status.RUNNING) {
+ endVibrationLocked(vib,
+ new Vibration.EndInfo(status, ignoredByUid, ignoredByUsage),
+ /* shouldWriteStats= */ true);
+ }
+ return vib;
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
@@ -457,26 +487,28 @@
if (DEBUG) {
Slog.d(TAG, "Canceling vibration");
}
+ Vibration.EndInfo cancelledByUserInfo =
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER);
final long ident = Binder.clearCallingIdentity();
try {
if (mNextVibration != null
&& shouldCancelVibration(mNextVibration.getVibration(),
usageFilter, token)) {
- clearNextVibrationLocked(Vibration.Status.CANCELLED_BY_USER);
+ clearNextVibrationLocked(cancelledByUserInfo);
}
if (mCurrentVibration != null
&& shouldCancelVibration(mCurrentVibration.getVibration(),
usageFilter, token)) {
- mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_USER,
- /* immediate= */false);
+ mCurrentVibration.notifyCancelled(
+ cancelledByUserInfo, /* immediate= */false);
}
if (mCurrentExternalVibration != null
&& shouldCancelVibration(
mCurrentExternalVibration.externalVibration.getVibrationAttributes(),
usageFilter)) {
- mCurrentExternalVibration.externalVibration.mute();
- endExternalVibrateLocked(Vibration.Status.CANCELLED_BY_USER,
- /* continueExternalControl= */ false);
+ mCurrentExternalVibration.mute();
+ endExternalVibrateLocked(
+ cancelledByUserInfo, /* continueExternalControl= */ false);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -604,15 +636,17 @@
Slog.d(TAG, "Canceling vibration because settings changed: "
+ (inputDevicesChanged ? "input devices changed" : ignoreStatus));
}
- mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE,
+ mCurrentVibration.notifyCancelled(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
/* immediate= */ false);
}
}
}
- private void setExternalControl(boolean externalControl) {
+ private void setExternalControl(boolean externalControl, VibrationStats vibrationStats) {
for (int i = 0; i < mVibrators.size(); i++) {
mVibrators.valueAt(i).setExternalControl(externalControl);
+ vibrationStats.reportSetExternalControl();
}
}
@@ -654,7 +688,9 @@
}
// If there's already a vibration queued (waiting for the previous one to finish
// cancelling), end it cleanly and replace it with the new one.
- clearNextVibrationLocked(Vibration.Status.IGNORED_SUPERSEDED);
+ clearNextVibrationLocked(
+ new Vibration.EndInfo(Vibration.Status.IGNORED_SUPERSEDED,
+ vib.uid, vib.attrs.getUsage()));
mNextVibration = conductor;
return Vibration.Status.RUNNING;
} finally {
@@ -671,6 +707,7 @@
switch (mode) {
case AppOpsManager.MODE_ALLOWED:
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
+ // Make sure mCurrentVibration is set while triggering the VibrationThread.
mCurrentVibration = conductor;
if (!mVibrationThread.runVibrationOnVibrationThread(mCurrentVibration)) {
// Shouldn't happen. The method call already logs a wtf.
@@ -690,18 +727,26 @@
}
@GuardedBy("mLock")
- private void endVibrationLocked(Vibration vib, Vibration.Status status) {
- vib.end(status);
- logVibrationStatus(vib.uid, vib.attrs, status);
+ private void endVibrationLocked(Vibration vib, Vibration.EndInfo vibrationEndInfo,
+ boolean shouldWriteStats) {
+ vib.end(vibrationEndInfo);
+ logVibrationStatus(vib.uid, vib.attrs, vibrationEndInfo.status);
mVibratorManagerRecords.record(vib);
+ if (shouldWriteStats) {
+ mFrameworkStatsLogger.writeVibrationReportedAsync(
+ vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
+ }
}
@GuardedBy("mLock")
- private void endVibrationLocked(ExternalVibrationHolder vib, Vibration.Status status) {
- vib.end(status);
+ private void endVibrationAndWriteStatsLocked(ExternalVibrationHolder vib,
+ Vibration.EndInfo vibrationEndInfo) {
+ vib.end(vibrationEndInfo);
logVibrationStatus(vib.externalVibration.getUid(),
- vib.externalVibration.getVibrationAttributes(), status);
+ vib.externalVibration.getVibrationAttributes(), vibrationEndInfo.status);
mVibratorManagerRecords.record(vib);
+ mFrameworkStatsLogger.writeVibrationReportedAsync(
+ vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
}
private void logVibrationStatus(int uid, VibrationAttributes attrs, Vibration.Status status) {
@@ -744,15 +789,17 @@
}
@GuardedBy("mLock")
- private void reportFinishedVibrationLocked(Vibration.Status status) {
+ private void reportFinishedVibrationLocked(Vibration.EndInfo vibrationEndInfo) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
try {
Vibration vib = mCurrentVibration.getVibration();
if (DEBUG) {
- Slog.d(TAG, "Reporting vibration " + vib.id + " finished with status " + status);
+ Slog.d(TAG, "Reporting vibration " + vib.id + " finished with " + vibrationEndInfo);
}
- endVibrationLocked(vib, status);
+ // DO NOT write metrics at this point, wait for the VibrationThread to report the
+ // vibration was released, after all cleanup. The metrics will be reported then.
+ endVibrationLocked(vib, vibrationEndInfo, /* shouldWriteStats= */ false);
finishAppOpModeLocked(vib.uid, vib.opPkg);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
@@ -791,11 +838,6 @@
@GuardedBy("mLock")
@Nullable
private Vibration.Status shouldIgnoreVibrationForOngoingLocked(Vibration vib) {
- if (mCurrentExternalVibration != null) {
- // If something has external control of the vibrator, assume that it's more important.
- return Vibration.Status.IGNORED_FOR_EXTERNAL;
- }
-
if (mCurrentVibration == null || vib.isRepeating()) {
// Incoming repeating vibrations always take precedence over ongoing vibrations.
return null;
@@ -1122,7 +1164,7 @@
}
Vibration vib = conductor.getVibration();
return mVibrationSettings.shouldCancelVibrationOnScreenOff(
- vib.uid, vib.opPkg, vib.attrs.getUsage(), vib.startUptimeMillis);
+ vib.uid, vib.opPkg, vib.attrs.getUsage(), vib.stats().getCreateUptimeMillis());
}
@GuardedBy("mLock")
@@ -1158,6 +1200,10 @@
BatteryStats.SERVICE_NAME));
}
+ VibratorFrameworkStatsLogger getFrameworkStatsLogger(Handler handler) {
+ return new VibratorFrameworkStatsLogger(handler);
+ }
+
VibratorController createVibratorController(int vibratorId,
VibratorController.OnVibrationCompleteListener listener) {
return new VibratorController(vibratorId, listener);
@@ -1197,6 +1243,10 @@
public void noteVibratorOn(int uid, long duration) {
try {
if (duration <= 0) {
+ // Tried to turn vibrator ON and got:
+ // duration == 0: Unsupported effect/method or zero-amplitude segment.
+ // duration < 0: Unexpected error triggering the vibrator.
+ // Skip battery stats and atom metric for VibratorStageChanged to ON.
return;
}
if (duration == Long.MAX_VALUE) {
@@ -1205,10 +1255,9 @@
duration = BATTERY_STATS_REPEATING_VIBRATION_DURATION;
}
mBatteryStatsService.noteVibratorOn(uid, duration);
- FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
- uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON,
- duration);
+ mFrameworkStatsLogger.writeVibratorStateOnAsync(uid, duration);
} catch (RemoteException e) {
+ Slog.e(TAG, "Error logging VibratorStateChanged to ON", e);
}
}
@@ -1216,22 +1265,21 @@
public void noteVibratorOff(int uid) {
try {
mBatteryStatsService.noteVibratorOff(uid);
- FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
- uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF,
- /* duration= */ 0);
+ mFrameworkStatsLogger.writeVibratorStateOffAsync(uid);
} catch (RemoteException e) {
+ Slog.e(TAG, "Error logging VibratorStateChanged to OFF", e);
}
}
@Override
- public void onVibrationCompleted(long vibrationId, Vibration.Status status) {
+ public void onVibrationCompleted(long vibrationId, Vibration.EndInfo vibrationEndInfo) {
if (DEBUG) {
- Slog.d(TAG, "Vibration " + vibrationId + " finished with status " + status);
+ Slog.d(TAG, "Vibration " + vibrationId + " finished with " + vibrationEndInfo);
}
synchronized (mLock) {
if (mCurrentVibration != null
&& mCurrentVibration.getVibration().id == vibrationId) {
- reportFinishedVibrationLocked(status);
+ reportFinishedVibrationLocked(vibrationEndInfo);
}
}
}
@@ -1251,13 +1299,21 @@
"VibrationId mismatch on release. expected=%d, released=%d",
mCurrentVibration.getVibration().id, vibrationId));
}
- mCurrentVibration = null;
+ if (mCurrentVibration != null) {
+ // This is when we consider the current vibration complete, so report metrics.
+ mFrameworkStatsLogger.writeVibrationReportedAsync(
+ mCurrentVibration.getVibration().getStatsInfo(
+ /* completionUptimeMillis= */ SystemClock.uptimeMillis()));
+ mCurrentVibration = null;
+ }
if (mNextVibration != null) {
VibrationStepConductor nextConductor = mNextVibration;
mNextVibration = null;
Vibration.Status status = startVibrationOnThreadLocked(nextConductor);
if (status != Vibration.Status.RUNNING) {
- endVibrationLocked(nextConductor.getVibration(), status);
+ // Failed to start the vibration, end it and report metrics right away.
+ endVibrationLocked(nextConductor.getVibration(),
+ new Vibration.EndInfo(status), /* shouldWriteStats= */ true);
}
}
}
@@ -1325,31 +1381,48 @@
private final class ExternalVibrationHolder implements IBinder.DeathRecipient {
public final ExternalVibration externalVibration;
+ public final VibrationStats stats = new VibrationStats();
public int scale;
- private final long mStartUptimeMillis;
- private final long mStartTimeDebug;
-
- private long mEndUptimeMillis;
- private long mEndTimeDebug;
private Vibration.Status mStatus;
private ExternalVibrationHolder(ExternalVibration externalVibration) {
this.externalVibration = externalVibration;
this.scale = IExternalVibratorService.SCALE_NONE;
- mStartUptimeMillis = SystemClock.uptimeMillis();
- mStartTimeDebug = System.currentTimeMillis();
mStatus = Vibration.Status.RUNNING;
}
- public void end(Vibration.Status status) {
+ public void mute() {
+ externalVibration.mute();
+ }
+
+ public void linkToDeath() {
+ externalVibration.linkToDeath(this);
+ }
+
+ public void unlinkToDeath() {
+ externalVibration.unlinkToDeath(this);
+ }
+
+ public boolean isHoldingSameVibration(ExternalVibration externalVibration) {
+ return this.externalVibration.equals(externalVibration);
+ }
+
+ public void end(Vibration.EndInfo info) {
if (mStatus != Vibration.Status.RUNNING) {
- // Vibration already ended, keep first ending status set and ignore this one.
+ // Already ended, ignore this call
return;
}
- mStatus = status;
- mEndUptimeMillis = SystemClock.uptimeMillis();
- mEndTimeDebug = System.currentTimeMillis();
+ mStatus = info.status;
+ stats.reportEnded(info.endedByUid, info.endedByUsage);
+
+ if (stats.hasStarted()) {
+ // External vibration doesn't have feedback from total time the vibrator was playing
+ // with non-zero amplitude, so we use the duration between start and end times of
+ // the vibration as the time the vibrator was ON, since the haptic channels are
+ // open for this duration and can receive vibration waveform data.
+ stats.reportVibratorOn(stats.getEndUptimeMillis() - stats.getStartUptimeMillis());
+ }
}
public void binderDied() {
@@ -1358,19 +1431,26 @@
if (DEBUG) {
Slog.d(TAG, "External vibration finished because binder died");
}
- endExternalVibrateLocked(Vibration.Status.CANCELLED_BINDER_DIED,
+ endExternalVibrateLocked(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BINDER_DIED),
/* continueExternalControl= */ false);
}
}
}
public Vibration.DebugInfo getDebugInfo() {
- long durationMs = mEndUptimeMillis == 0 ? -1 : mEndUptimeMillis - mStartUptimeMillis;
return new Vibration.DebugInfo(
- mStartTimeDebug, mEndTimeDebug, durationMs,
- /* effect= */ null, /* originalEffect= */ null, scale,
+ mStatus, stats, /* effect= */ null, /* originalEffect= */ null, scale,
externalVibration.getVibrationAttributes(), externalVibration.getUid(),
- externalVibration.getPackage(), /* reason= */ null, mStatus);
+ externalVibration.getPackage(), /* reason= */ null);
+ }
+
+ public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
+ return new VibrationStats.StatsInfo(
+ externalVibration.getUid(),
+ FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL,
+ externalVibration.getVibrationAttributes().getUsage(), mStatus, stats,
+ completionUptimeMillis);
}
}
@@ -1500,9 +1580,11 @@
/** Clears mNextVibration if set, ending it cleanly */
@GuardedBy("mLock")
- private void clearNextVibrationLocked(Vibration.Status endStatus) {
+ private void clearNextVibrationLocked(Vibration.EndInfo vibrationEndInfo) {
if (mNextVibration != null) {
- endVibrationLocked(mNextVibration.getVibration(), endStatus);
+ // Clearing next vibration before playing it, end it and report metrics right away.
+ endVibrationLocked(mNextVibration.getVibration(), vibrationEndInfo,
+ /* shouldWriteStats= */ true);
mNextVibration = null;
}
}
@@ -1510,25 +1592,25 @@
/**
* Ends the external vibration, and clears related service state.
*
- * @param status the status to end the associated Vibration with
+ * @param vibrationEndInfo the status and related info to end the associated Vibration with
* @param continueExternalControl indicates whether external control will continue. If not, the
* HAL will have external control turned off.
*/
@GuardedBy("mLock")
- private void endExternalVibrateLocked(Vibration.Status status,
+ private void endExternalVibrateLocked(Vibration.EndInfo vibrationEndInfo,
boolean continueExternalControl) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "endExternalVibrateLocked");
try {
if (mCurrentExternalVibration == null) {
return;
}
- endVibrationLocked(mCurrentExternalVibration, status);
- mCurrentExternalVibration.externalVibration.unlinkToDeath(
- mCurrentExternalVibration);
- mCurrentExternalVibration = null;
+ mCurrentExternalVibration.unlinkToDeath();
if (!continueExternalControl) {
- setExternalControl(false);
+ setExternalControl(false, mCurrentExternalVibration.stats);
}
+ // The external control was turned off, end it and report metrics right away.
+ endVibrationAndWriteStatsLocked(mCurrentExternalVibration, vibrationEndInfo);
+ mCurrentExternalVibration = null;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
@@ -1552,6 +1634,8 @@
return IExternalVibratorService.SCALE_MUTE;
}
+ // Create Vibration.Stats as close to the received request as possible, for tracking.
+ ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
VibrationAttributes attrs = fixupVibrationAttributes(vib.getVibrationAttributes(),
/* effect= */ null);
if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
@@ -1562,18 +1646,17 @@
boolean alreadyUnderExternalControl = false;
boolean waitForCompletion = false;
- int scale;
synchronized (mLock) {
Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
vib.getUid(), vib.getPackage(), attrs);
if (ignoreStatus != null) {
- ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
vibHolder.scale = IExternalVibratorService.SCALE_MUTE;
- endVibrationLocked(vibHolder, ignoreStatus);
+ // Failed to start the vibration, end it and report metrics right away.
+ endVibrationAndWriteStatsLocked(vibHolder, new Vibration.EndInfo(ignoreStatus));
return vibHolder.scale;
}
if (mCurrentExternalVibration != null
- && mCurrentExternalVibration.externalVibration.equals(vib)) {
+ && mCurrentExternalVibration.isHoldingSameVibration(vib)) {
// We are already playing this external vibration, so we can return the same
// scale calculated in the previous call to this method.
return mCurrentExternalVibration.scale;
@@ -1582,8 +1665,14 @@
// If we're not under external control right now, then cancel any normal
// vibration that may be playing and ready the vibrator for external control.
if (mCurrentVibration != null) {
- clearNextVibrationLocked(Vibration.Status.IGNORED_FOR_EXTERNAL);
- mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED,
+ vibHolder.stats.reportInterruptedAnotherVibration(
+ mCurrentVibration.getVibration().attrs.getUsage());
+ clearNextVibrationLocked(
+ new Vibration.EndInfo(Vibration.Status.IGNORED_FOR_EXTERNAL,
+ vib.getUid(), attrs.getUsage()));
+ mCurrentVibration.notifyCancelled(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
+ vib.getUid(), attrs.getUsage()),
/* immediate= */ true);
waitForCompletion = true;
}
@@ -1597,22 +1686,27 @@
// Note that this doesn't support multiple concurrent external controls, as we
// would need to mute the old one still if it came from a different controller.
alreadyUnderExternalControl = true;
- mCurrentExternalVibration.externalVibration.mute();
- endExternalVibrateLocked(Vibration.Status.CANCELLED_SUPERSEDED,
+ mCurrentExternalVibration.mute();
+ vibHolder.stats.reportInterruptedAnotherVibration(
+ mCurrentExternalVibration.externalVibration
+ .getVibrationAttributes().getUsage());
+ endExternalVibrateLocked(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
+ vib.getUid(), attrs.getUsage()),
/* continueExternalControl= */ true);
}
- mCurrentExternalVibration = new ExternalVibrationHolder(vib);
- vib.linkToDeath(mCurrentExternalVibration);
- mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale(
- attrs.getUsage());
- scale = mCurrentExternalVibration.scale;
+ mCurrentExternalVibration = vibHolder;
+ vibHolder.linkToDeath();
+ vibHolder.scale = mVibrationScaler.getExternalVibrationScale(attrs.getUsage());
}
if (waitForCompletion) {
if (!mVibrationThread.waitForThreadIdle(VIBRATION_CANCEL_WAIT_MILLIS)) {
Slog.e(TAG, "Timed out waiting for vibration to cancel");
synchronized (mLock) {
- endExternalVibrateLocked(Vibration.Status.IGNORED_ERROR_CANCELLING,
+ // Trigger endExternalVibrateLocked to unlink to death recipient.
+ endExternalVibrateLocked(
+ new Vibration.EndInfo(Vibration.Status.IGNORED_ERROR_CANCELLING),
/* continueExternalControl= */ false);
}
return IExternalVibratorService.SCALE_MUTE;
@@ -1622,23 +1716,27 @@
if (DEBUG) {
Slog.d(TAG, "Vibrator going under external control.");
}
- setExternalControl(true);
+ setExternalControl(true, vibHolder.stats);
}
if (DEBUG) {
Slog.e(TAG, "Playing external vibration: " + vib);
}
- return scale;
+ // Vibrator will start receiving data from external channels after this point.
+ // Report current time as the vibration start time, for debugging.
+ vibHolder.stats.reportStarted();
+ return vibHolder.scale;
}
@Override
public void onExternalVibrationStop(ExternalVibration vib) {
synchronized (mLock) {
if (mCurrentExternalVibration != null
- && mCurrentExternalVibration.externalVibration.equals(vib)) {
+ && mCurrentExternalVibration.isHoldingSameVibration(vib)) {
if (DEBUG) {
Slog.e(TAG, "Stopping external vibration" + vib);
}
- endExternalVibrateLocked(Vibration.Status.FINISHED,
+ endExternalVibrateLocked(
+ new Vibration.EndInfo(Vibration.Status.FINISHED),
/* continueExternalControl= */ false);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index d48af21..f8cbd8b3d 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -70,6 +70,7 @@
import static com.android.internal.util.FrameworkStatsLog.CAMERA_COMPAT_CONTROL_EVENT_REPORTED__EVENT__CLICKED_REVERT_TREATMENT;
import static com.android.server.am.MemoryStatUtil.MemoryStat;
import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
+import static com.android.server.am.ProcessList.INVALID_ADJ;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_METRICS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -277,6 +278,8 @@
final boolean mProcessSwitch;
/** The process state of the launching activity prior to the launch */
final int mProcessState;
+ /** The oom adj score of the launching activity prior to the launch */
+ final int mProcessOomAdj;
/** Whether the last launched activity has reported drawn. */
boolean mIsDrawn;
/** The latest activity to have been launched. */
@@ -312,7 +315,7 @@
@Nullable
static TransitionInfo create(@NonNull ActivityRecord r,
@NonNull LaunchingState launchingState, @Nullable ActivityOptions options,
- boolean processRunning, boolean processSwitch, int processState,
+ boolean processRunning, boolean processSwitch, int processState, int processOomAdj,
boolean newActivityCreated, int startResult) {
if (startResult != START_SUCCESS && startResult != START_TASK_TO_FRONT) {
return null;
@@ -328,19 +331,20 @@
transitionType = TYPE_TRANSITION_COLD_LAUNCH;
}
return new TransitionInfo(r, launchingState, options, transitionType, processRunning,
- processSwitch, processState);
+ processSwitch, processState, processOomAdj);
}
/** Use {@link TransitionInfo#create} instead to ensure the transition type is valid. */
private TransitionInfo(ActivityRecord r, LaunchingState launchingState,
ActivityOptions options, int transitionType, boolean processRunning,
- boolean processSwitch, int processState) {
+ boolean processSwitch, int processState, int processOomAdj) {
mLaunchingState = launchingState;
mTransitionStartTimeNs = launchingState.mCurrentTransitionStartTimeNs;
mTransitionType = transitionType;
mProcessRunning = processRunning;
mProcessSwitch = processSwitch;
mProcessState = processState;
+ mProcessOomAdj = processOomAdj;
mTransitionDeviceUptimeMs = launchingState.mCurrentUpTimeMs;
setLatestLaunchedActivity(r);
// The launching state can be reused by consecutive launch. Its original association
@@ -644,9 +648,15 @@
// interesting.
final boolean processSwitch = !processRunning
|| !processRecord.hasStartedActivity(launchedActivity);
- final int processState = processRunning
- ? processRecord.getCurrentProcState()
- : PROCESS_STATE_NONEXISTENT;
+ final int processState;
+ final int processOomAdj;
+ if (processRunning) {
+ processState = processRecord.getCurrentProcState();
+ processOomAdj = processRecord.getCurrentAdj();
+ } else {
+ processState = PROCESS_STATE_NONEXISTENT;
+ processOomAdj = INVALID_ADJ;
+ }
final TransitionInfo info = launchingState.mAssociatedTransitionInfo;
if (DEBUG_METRICS) {
@@ -654,6 +664,7 @@
+ " launchedActivity=" + launchedActivity + " processRunning=" + processRunning
+ " processSwitch=" + processSwitch
+ " processState=" + processState
+ + " processOomAdj=" + processOomAdj
+ " newActivityCreated=" + newActivityCreated + " info=" + info);
}
@@ -689,8 +700,8 @@
}
final TransitionInfo newInfo = TransitionInfo.create(launchedActivity, launchingState,
- options, processRunning, processSwitch, processState, newActivityCreated,
- resultCode);
+ options, processRunning, processSwitch, processState, processOomAdj,
+ newActivityCreated, resultCode);
if (newInfo == null) {
abort(launchingState, "unrecognized launch");
return;
@@ -1006,8 +1017,10 @@
final long uptime = info.mTransitionDeviceUptimeMs;
final int transitionDelay = info.mCurrentTransitionDelayMs;
final int processState = info.mProcessState;
+ final int processOomAdj = info.mProcessOomAdj;
mLoggerHandler.post(() -> logAppTransition(
- timestamp, uptime, transitionDelay, infoSnapshot, isHibernating, processState));
+ timestamp, uptime, transitionDelay, infoSnapshot, isHibernating,
+ processState, processOomAdj));
}
mLoggerHandler.post(() -> logAppDisplayed(infoSnapshot));
if (info.mPendingFullyDrawn != null) {
@@ -1020,7 +1033,7 @@
// This gets called on another thread without holding the activity manager lock.
private void logAppTransition(long transitionStartTimeNs, long transitionDeviceUptimeMs,
int currentTransitionDelayMs, TransitionInfoSnapshot info, boolean isHibernating,
- int processState) {
+ int processState, int processOomAdj) {
final LogMaker builder = new LogMaker(APP_TRANSITION);
builder.setPackageName(info.packageName);
builder.setType(info.type);
@@ -1087,7 +1100,8 @@
isLoading,
info.launchedActivityName.hashCode(),
TimeUnit.NANOSECONDS.toMillis(transitionStartTimeNs),
- processState);
+ processState,
+ processOomAdj);
if (DEBUG_METRICS) {
Slog.i(TAG, String.format("APP_START_OCCURRED(%s, %s, %s, %s, %s)",
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c64e525..d11adc1 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5640,7 +5640,7 @@
ProtoLog.v(WM_DEBUG_ADD_REMOVE, "notifyAppResumed: wasStopped=%b %s",
wasStopped, this);
mAppStopped = false;
- // Allow the window to turn the screen on once the app is resumed again.
+ // Allow the window to turn the screen on once the app is started and resumed.
if (mAtmService.getActivityStartController().isInExecution()) {
setCurrentLaunchCanTurnScreenOn(true);
}
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 77d6097..8f18064 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -94,6 +94,7 @@
boolean mCheckedForSetup = false;
+ /** Whether an {@link ActivityStarter} is currently executing (starting an Activity). */
private boolean mInExecution = false;
/**
@@ -129,7 +130,7 @@
return mFactory.obtain().setIntent(intent).setReason(reason);
}
- void onExecutionStarted(ActivityStarter starter) {
+ void onExecutionStarted() {
mInExecution = true;
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index abedd96..619d693 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1267,7 +1267,7 @@
}
private void onExecutionStarted() {
- mController.onExecutionStarted(this);
+ mController.onExecutionStarted();
}
private boolean isHomeApp(int uid, @Nullable String packageName) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index be995a8..5a1afc4 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2654,12 +2654,12 @@
@Override
public void accept(ActivityRecord r) {
- if (r.finishing) {
- return;
- }
if (r.mLaunchCookie != null) {
mInfo.addLaunchCookie(r.mLaunchCookie);
}
+ if (r.finishing) {
+ return;
+ }
mInfo.numActivities++;
mInfo.baseActivity = r.mActivityComponent;
if (mTopRunning == null) {
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 3d91921..8dd5850 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -399,9 +399,8 @@
return false;
}
- final ScreenRotationAnimation screenRotationAnimation =
- mDisplayContent.getRotationAnimation();
- if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
+ if (mDisplayContent.inTransition()
+ && !mDisplayContent.mTransitionController.useShellTransitionsRotation()) {
// Rotation updates cannot be performed while the previous rotation change animation
// is still in progress. Skip this update. We will try updating again after the
// animation is finished and the display is unfrozen.
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index bbc95a1..584a40e 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -622,9 +622,11 @@
throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId);
}
+ boolean hasParticipatedDisplay = false;
// Commit all going-invisible containers
for (int i = 0; i < mParticipants.size(); ++i) {
- final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
+ final WindowContainer<?> participant = mParticipants.valueAt(i);
+ final ActivityRecord ar = participant.asActivityRecord();
if (ar != null) {
boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(ar);
// We need both the expected visibility AND current requested-visibility to be
@@ -656,8 +658,13 @@
// Legacy dispatch relies on this (for now).
ar.mEnteringAnimation = visibleAtTransitionEnd;
}
+ continue;
}
- final WallpaperWindowToken wt = mParticipants.valueAt(i).asWallpaperToken();
+ if (participant.asDisplayContent() != null) {
+ hasParticipatedDisplay = true;
+ continue;
+ }
+ final WallpaperWindowToken wt = participant.asWallpaperToken();
if (wt != null) {
final boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(wt);
if (!visibleAtTransitionEnd && !wt.isVisibleRequested()) {
@@ -737,6 +744,12 @@
mState = STATE_FINISHED;
mController.mTransitionTracer.logState(this);
+ // Rotation change may be deferred while there is a display change transition, so check
+ // again in case there is a new pending change.
+ if (hasParticipatedDisplay && !mController.useShellTransitionsRotation()) {
+ mController.mAtm.mWindowManager.updateRotation(false /* alwaysSendConfiguration */,
+ false /* forceRelayout */);
+ }
}
void abort() {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 6544f82..3ee4be0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1869,7 +1869,7 @@
displayContent.getInsetsStateController().updateAboveInsetsState(
false /* notifyInsetsChanged */);
- outInsetsState.set(win.getCompatInsetsState(), win.isClientLocal());
+ outInsetsState.set(win.getCompatInsetsState(), true /* copySources */);
getInsetsSourceControls(win, outActiveControls);
if (win.mLayoutAttached) {
@@ -2556,7 +2556,7 @@
}
if (outInsetsState != null) {
- outInsetsState.set(win.getCompatInsetsState(), win.isClientLocal());
+ outInsetsState.set(win.getCompatInsetsState(), true /* copySources */);
}
ProtoLog.v(WM_DEBUG_FOCUS, "Relayout of %s: focusMayChange=%b",
@@ -2623,27 +2623,32 @@
transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
}
- String reason = null;
- if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) {
- reason = "applyAnimation";
- focusMayChange = true;
- win.mAnimatingExit = true;
- } else if (win.mDisplayContent.okToAnimate() && win.isExitAnimationRunningSelfOrParent()) {
- // Currently in a hide animation... turn this into
- // an exit.
- win.mAnimatingExit = true;
- } else if (win.mDisplayContent.okToAnimate()
- && win.mDisplayContent.mWallpaperController.isWallpaperTarget(win)
- && win.mAttrs.type != TYPE_NOTIFICATION_SHADE) {
- reason = "isWallpaperTarget";
- // If the wallpaper is currently behind this app window, we need to change both of them
- // inside of a transaction to avoid artifacts.
- // For NotificationShade, sysui is in charge of running window animation and it updates
- // the client view visibility only after both NotificationShade and the wallpaper are
- // hidden. So we don't need to care about exit animation, but can destroy its surface
- // immediately.
- win.mAnimatingExit = true;
- } else {
+ if (win.isWinVisibleLw() && win.mDisplayContent.okToAnimate()) {
+ String reason = null;
+ if (winAnimator.applyAnimationLocked(transit, false)) {
+ reason = "applyAnimation";
+ focusMayChange = true;
+ win.mAnimatingExit = true;
+ } else if (win.isExitAnimationRunningSelfOrParent()) {
+ reason = "animating";
+ win.mAnimatingExit = true;
+ } else if (win.mDisplayContent.mWallpaperController.isWallpaperTarget(win)
+ && win.mAttrs.type != TYPE_NOTIFICATION_SHADE) {
+ reason = "isWallpaperTarget";
+ // If the wallpaper is currently behind this app window, they should be updated
+ // in a transaction to avoid artifacts.
+ // For NotificationShade, sysui is in charge of running window animation and it
+ // updates the client view visibility only after both NotificationShade and the
+ // wallpaper are hidden. So the exit animation is not needed and can destroy its
+ // surface immediately.
+ win.mAnimatingExit = true;
+ }
+ if (reason != null) {
+ ProtoLog.d(WM_DEBUG_ANIM,
+ "Set animatingExit: reason=startExitingAnimation/%s win=%s", reason, win);
+ }
+ }
+ if (!win.mAnimatingExit) {
boolean stopped = win.mActivityRecord == null || win.mActivityRecord.mAppStopped;
// We set mDestroying=true so ActivityRecord#notifyAppStopped in-to destroy surfaces
// will later actually destroy the surface if we do not do so here. Normally we leave
@@ -2651,10 +2656,6 @@
win.mDestroying = true;
win.destroySurface(false, stopped);
}
- if (reason != null) {
- ProtoLog.d(WM_DEBUG_ANIM, "Set animatingExit: reason=startExitingAnimation/%s win=%s",
- reason, win);
- }
if (mAccessibilityController.hasCallbacks()) {
mAccessibilityController.onWindowTransition(win, transit);
}
@@ -4302,7 +4303,9 @@
// Even if alwaysSend, we are waiting for a transition or remote to provide
// updated configuration, so we can't update configuration yet.
if (!pendingRemoteDisplayChange) {
- if (!rotationChanged || forceRelayout) {
+ // The layout-needed flag will be set if there is a rotation change, so
+ // only set it if the caller requests to force relayout.
+ if (forceRelayout) {
displayContent.setLayoutNeeded();
layoutNeeded = true;
}
@@ -8918,7 +8921,6 @@
@Override
public boolean getWindowInsets(WindowManager.LayoutParams attrs, int displayId,
InsetsState outInsetsState) {
- final boolean fromLocal = Binder.getCallingPid() == MY_PID;
final int uid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
try {
@@ -8932,10 +8934,8 @@
final float overrideScale = mAtmService.mCompatModePackages.getCompatScale(
attrs.packageName, uid);
final InsetsState state = dc.getInsetsPolicy().getInsetsForWindowMetrics(attrs);
- final boolean hasCompatScale =
- WindowState.hasCompatScale(attrs, token, overrideScale);
- outInsetsState.set(state, hasCompatScale || fromLocal);
- if (hasCompatScale) {
+ outInsetsState.set(state, true /* copySources */);
+ if (WindowState.hasCompatScale(attrs, token, overrideScale)) {
final float compatScale = token != null && token.hasSizeCompatBounds()
? token.getSizeCompatScale() * overrideScale
: overrideScale;
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 95b0645..8f63e93 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -27,6 +27,7 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.server.am.ProcessList.INVALID_ADJ;
import static com.android.server.wm.ActivityRecord.State.DESTROYED;
import static com.android.server.wm.ActivityRecord.State.DESTROYING;
import static com.android.server.wm.ActivityRecord.State.PAUSED;
@@ -125,6 +126,8 @@
private volatile int mCurProcState = PROCESS_STATE_NONEXISTENT;
// Last reported process state;
private volatile int mRepProcState = PROCESS_STATE_NONEXISTENT;
+ // Currently computed oom adj score
+ private volatile int mCurAdj = INVALID_ADJ;
// are we in the process of crashing?
private volatile boolean mCrashing;
// does the app have a not responding dialog?
@@ -319,6 +322,14 @@
return mCurProcState;
}
+ public void setCurrentAdj(int curAdj) {
+ mCurAdj = curAdj;
+ }
+
+ int getCurrentAdj() {
+ return mCurAdj;
+ }
+
/**
* Sets the computed process state from the oom adjustment calculation. This is frequently
* called in activity manager's lock, so don't use window manager lock here.
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 1d56078..d3d69ae 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -491,10 +491,10 @@
compactProcessOrFallback(pid, compactionFlags);
}
-static jint com_android_server_am_CachedAppOptimizer_freezeBinder(
- JNIEnv *env, jobject clazz, jint pid, jboolean freeze) {
-
- jint retVal = IPCThreadState::freeze(pid, freeze, 100 /* timeout [ms] */);
+static jint com_android_server_am_CachedAppOptimizer_freezeBinder(JNIEnv* env, jobject clazz,
+ jint pid, jboolean freeze,
+ jint timeout_ms) {
+ jint retVal = IPCThreadState::freeze(pid, freeze, timeout_ms);
if (retVal != 0 && retVal != -EAGAIN) {
jniThrowException(env, "java/lang/RuntimeException", "Unable to freeze/unfreeze binder");
}
@@ -548,7 +548,7 @@
(void*)com_android_server_am_CachedAppOptimizer_getMemoryFreedCompaction},
{"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
{"compactProcess", "(II)V", (void*)com_android_server_am_CachedAppOptimizer_compactProcess},
- {"freezeBinder", "(IZ)I", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
+ {"freezeBinder", "(IZI)I", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
{"getBinderFreezeInfo", "(I)I",
(void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo},
{"getFreezerCheckPath", "()Ljava/lang/String;",
diff --git a/services/people/java/com/android/server/people/data/ConversationInfo.java b/services/people/java/com/android/server/people/data/ConversationInfo.java
index 16c4c29..6ead44a 100644
--- a/services/people/java/com/android/server/people/data/ConversationInfo.java
+++ b/services/people/java/com/android/server/people/data/ConversationInfo.java
@@ -26,6 +26,7 @@
import android.content.pm.ShortcutInfo.ShortcutFlags;
import android.net.Uri;
import android.text.TextUtils;
+import android.util.Log;
import android.util.Slog;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
@@ -37,6 +38,7 @@
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
+import java.io.EOFException;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -50,6 +52,11 @@
* Represents a conversation that is provided by the app based on {@link ShortcutInfo}.
*/
public class ConversationInfo {
+ private static final boolean DEBUG = false;
+
+ // Schema version for the backup payload. Must be incremented whenever fields are added in
+ // backup payload.
+ private static final int VERSION = 1;
private static final String TAG = ConversationInfo.class.getSimpleName();
@@ -100,6 +107,8 @@
private long mLastEventTimestamp;
+ private long mCreationTimestamp;
+
@ShortcutFlags
private int mShortcutFlags;
@@ -116,6 +125,7 @@
mNotificationChannelId = builder.mNotificationChannelId;
mParentNotificationChannelId = builder.mParentNotificationChannelId;
mLastEventTimestamp = builder.mLastEventTimestamp;
+ mCreationTimestamp = builder.mCreationTimestamp;
mShortcutFlags = builder.mShortcutFlags;
mConversationFlags = builder.mConversationFlags;
mCurrStatuses = builder.mCurrStatuses;
@@ -170,6 +180,13 @@
return mLastEventTimestamp;
}
+ /**
+ * Timestamp of the creation of the conversationInfo.
+ */
+ long getCreationTimestamp() {
+ return mCreationTimestamp;
+ }
+
/** Whether the shortcut for this conversation is set long-lived by the app. */
public boolean isShortcutLongLived() {
return hasShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED);
@@ -241,6 +258,7 @@
&& Objects.equals(mNotificationChannelId, other.mNotificationChannelId)
&& Objects.equals(mParentNotificationChannelId, other.mParentNotificationChannelId)
&& Objects.equals(mLastEventTimestamp, other.mLastEventTimestamp)
+ && mCreationTimestamp == other.mCreationTimestamp
&& mShortcutFlags == other.mShortcutFlags
&& mConversationFlags == other.mConversationFlags
&& Objects.equals(mCurrStatuses, other.mCurrStatuses);
@@ -250,7 +268,7 @@
public int hashCode() {
return Objects.hash(mShortcutId, mLocusId, mContactUri, mContactPhoneNumber,
mNotificationChannelId, mParentNotificationChannelId, mLastEventTimestamp,
- mShortcutFlags, mConversationFlags, mCurrStatuses);
+ mCreationTimestamp, mShortcutFlags, mConversationFlags, mCurrStatuses);
}
@Override
@@ -264,6 +282,7 @@
sb.append(", notificationChannelId=").append(mNotificationChannelId);
sb.append(", parentNotificationChannelId=").append(mParentNotificationChannelId);
sb.append(", lastEventTimestamp=").append(mLastEventTimestamp);
+ sb.append(", creationTimestamp=").append(mCreationTimestamp);
sb.append(", statuses=").append(mCurrStatuses);
sb.append(", shortcutFlags=0x").append(Integer.toHexString(mShortcutFlags));
sb.append(" [");
@@ -329,6 +348,7 @@
mParentNotificationChannelId);
}
protoOutputStream.write(ConversationInfoProto.LAST_EVENT_TIMESTAMP, mLastEventTimestamp);
+ protoOutputStream.write(ConversationInfoProto.CREATION_TIMESTAMP, mCreationTimestamp);
protoOutputStream.write(ConversationInfoProto.SHORTCUT_FLAGS, mShortcutFlags);
protoOutputStream.write(ConversationInfoProto.CONVERSATION_FLAGS, mConversationFlags);
if (mContactPhoneNumber != null) {
@@ -352,6 +372,8 @@
out.writeUTF(mContactPhoneNumber != null ? mContactPhoneNumber : "");
out.writeUTF(mParentNotificationChannelId != null ? mParentNotificationChannelId : "");
out.writeLong(mLastEventTimestamp);
+ out.writeInt(VERSION);
+ out.writeLong(mCreationTimestamp);
// ConversationStatus is a transient object and not persisted
} catch (IOException e) {
Slog.e(TAG, "Failed to write fields to backup payload.", e);
@@ -399,6 +421,9 @@
builder.setLastEventTimestamp(protoInputStream.readLong(
ConversationInfoProto.LAST_EVENT_TIMESTAMP));
break;
+ case (int) ConversationInfoProto.CREATION_TIMESTAMP:
+ builder.setCreationTimestamp(protoInputStream.readLong(
+ ConversationInfoProto.CREATION_TIMESTAMP));
case (int) ConversationInfoProto.SHORTCUT_FLAGS:
builder.setShortcutFlags(protoInputStream.readInt(
ConversationInfoProto.SHORTCUT_FLAGS));
@@ -448,6 +473,10 @@
builder.setParentNotificationChannelId(parentNotificationChannelId);
}
builder.setLastEventTimestamp(in.readLong());
+ int payloadVersion = maybeReadVersion(in);
+ if (payloadVersion == 1) {
+ builder.setCreationTimestamp(in.readLong());
+ }
} catch (IOException e) {
Slog.e(TAG, "Failed to read conversation info fields from backup payload.", e);
return null;
@@ -455,6 +484,16 @@
return builder.build();
}
+ private static int maybeReadVersion(DataInputStream in) throws IOException {
+ try {
+ return in.readInt();
+ } catch (EOFException eofException) {
+ // EOF is expected if using old backup payload protocol.
+ if (DEBUG) Log.d(TAG, "Eof reached for data stream, missing version number");
+ return 0;
+ }
+ }
+
/**
* Builder class for {@link ConversationInfo} objects.
*/
@@ -479,6 +518,8 @@
private long mLastEventTimestamp;
+ private long mCreationTimestamp;
+
@ShortcutFlags
private int mShortcutFlags;
@@ -502,6 +543,7 @@
mNotificationChannelId = conversationInfo.mNotificationChannelId;
mParentNotificationChannelId = conversationInfo.mParentNotificationChannelId;
mLastEventTimestamp = conversationInfo.mLastEventTimestamp;
+ mCreationTimestamp = conversationInfo.mCreationTimestamp;
mShortcutFlags = conversationInfo.mShortcutFlags;
mConversationFlags = conversationInfo.mConversationFlags;
mCurrStatuses = conversationInfo.mCurrStatuses;
@@ -542,6 +584,11 @@
return this;
}
+ Builder setCreationTimestamp(long creationTimestamp) {
+ mCreationTimestamp = creationTimestamp;
+ return this;
+ }
+
Builder setShortcutFlags(@ShortcutFlags int shortcutFlags) {
mShortcutFlags = shortcutFlags;
return this;
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index d305fc5..693f3a0 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -816,10 +816,18 @@
}
private boolean isCachedRecentConversation(ConversationInfo conversationInfo) {
+ return isEligibleForCleanUp(conversationInfo)
+ && conversationInfo.getLastEventTimestamp() > 0L;
+ }
+
+ /**
+ * Conversations that are cached and not customized are eligible for clean-up, even if they
+ * don't have an associated notification event with them.
+ */
+ private boolean isEligibleForCleanUp(ConversationInfo conversationInfo) {
return conversationInfo.isShortcutCachedForNotification()
&& Objects.equals(conversationInfo.getNotificationChannelId(),
- conversationInfo.getParentNotificationChannelId())
- && conversationInfo.getLastEventTimestamp() > 0L;
+ conversationInfo.getParentNotificationChannelId());
}
private boolean hasActiveNotifications(String packageName, @UserIdInt int userId,
@@ -842,14 +850,14 @@
}
// pair of <package name, conversation info>
List<Pair<String, ConversationInfo>> cachedConvos = new ArrayList<>();
- userData.forAllPackages(packageData ->
+ userData.forAllPackages(packageData -> {
packageData.forAllConversations(conversationInfo -> {
- if (isCachedRecentConversation(conversationInfo)) {
+ if (isEligibleForCleanUp(conversationInfo)) {
cachedConvos.add(
Pair.create(packageData.getPackageName(), conversationInfo));
}
- })
- );
+ });
+ });
if (cachedConvos.size() <= targetCachedCount) {
return;
}
@@ -858,7 +866,9 @@
PriorityQueue<Pair<String, ConversationInfo>> maxHeap = new PriorityQueue<>(
numToUncache + 1,
Comparator.comparingLong((Pair<String, ConversationInfo> pair) ->
- pair.second.getLastEventTimestamp()).reversed());
+ Math.max(
+ pair.second.getLastEventTimestamp(),
+ pair.second.getCreationTimestamp())).reversed());
for (Pair<String, ConversationInfo> cached : cachedConvos) {
if (hasActiveNotifications(cached.first, userId, cached.second.getShortcutId())) {
continue;
@@ -893,7 +903,7 @@
}
ConversationInfo.Builder builder = oldConversationInfo != null
? new ConversationInfo.Builder(oldConversationInfo)
- : new ConversationInfo.Builder();
+ : new ConversationInfo.Builder().setCreationTimestamp(System.currentTimeMillis());
builder.setShortcutId(shortcutInfo.getId());
builder.setLocusId(shortcutInfo.getLocusId());
@@ -1326,7 +1336,8 @@
}
}
- private void updateConversationStoreThenNotifyListeners(ConversationStore cs,
+ @VisibleForTesting
+ void updateConversationStoreThenNotifyListeners(ConversationStore cs,
ConversationInfo modifiedConv,
String packageName, int userId) {
cs.addOrUpdate(modifiedConv);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BroadcastHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/BroadcastHelperTest.kt
deleted file mode 100644
index d25649e..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BroadcastHelperTest.kt
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm
-
-import com.android.server.testutils.whenever
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-@RunWith(JUnit4::class)
-class BroadcastHelperTest {
-
- companion object {
- const val TEST_PACKAGE_1 = "com.android.test.package1"
- const val TEST_PACKAGE_2 = "com.android.test.package2"
- const val TEST_UID_1 = 10100
- const val TEST_UID_2 = 10101
- const val TEST_USER_ID = 0
- }
-
- lateinit var broadcastHelper: BroadcastHelper
- lateinit var packagesToChange: Array<String>
- lateinit var uidsToChange: IntArray
-
- @Mock
- lateinit var snapshot: Computer
-
- @Rule
- @JvmField
- val rule = MockSystemRule()
-
- @Before
- open fun setup() {
- MockitoAnnotations.initMocks(this)
- rule.system().stageNominalSystemState()
- broadcastHelper = BroadcastHelper(rule.mocks().injector)
- packagesToChange = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
- uidsToChange = intArrayOf(TEST_UID_1, TEST_UID_2)
- }
-
- @Test
- fun getBroadcastParams_withSameVisibilityAllowList_shouldGroup() {
- val allowList = intArrayOf(10001, 10002, 10003)
- mockVisibilityAllowList(TEST_PACKAGE_1, allowList)
- mockVisibilityAllowList(TEST_PACKAGE_2, allowList)
-
- val broadcastParams: List<BroadcastParams> = broadcastHelper.getBroadcastParams(
- snapshot, packagesToChange, uidsToChange, TEST_USER_ID)
-
- assertThat(broadcastParams).hasSize(1)
- assertThat(broadcastParams[0].packageNames).asList().containsExactlyElementsIn(
- packagesToChange.toCollection(ArrayList()))
- assertThat(broadcastParams[0].uids).asList().containsExactlyElementsIn(
- uidsToChange.toCollection(ArrayList()))
- }
-
- @Test
- fun getBroadcastParams_withDifferentVisibilityAllowList_shouldNotGroup() {
- val allowList1 = intArrayOf(10001, 10002, 10003)
- val allowList2 = intArrayOf(10001, 10002, 10007)
- mockVisibilityAllowList(TEST_PACKAGE_1, allowList1)
- mockVisibilityAllowList(TEST_PACKAGE_2, allowList2)
-
- val broadcastParams: List<BroadcastParams> = broadcastHelper.getBroadcastParams(
- snapshot, packagesToChange, uidsToChange, TEST_USER_ID)
-
- assertThat(broadcastParams).hasSize(2)
- broadcastParams.forEachIndexed { i, params ->
- val changedPackages = params.packageNames
- val changedUids = params.uids
- assertThat(changedPackages[0]).isEqualTo(packagesToChange[i])
- assertThat(changedUids[0]).isEqualTo(uidsToChange[i])
- }
- }
-
- @Test
- fun getBroadcastParams_withNullVisibilityAllowList_shouldNotGroup() {
- val allowList = intArrayOf(10001, 10002, 10003)
- mockVisibilityAllowList(TEST_PACKAGE_1, allowList)
- mockVisibilityAllowList(TEST_PACKAGE_2, null)
-
- val broadcastParams: List<BroadcastParams> = broadcastHelper.getBroadcastParams(
- snapshot, packagesToChange, uidsToChange, TEST_USER_ID)
-
- assertThat(broadcastParams).hasSize(2)
- broadcastParams.forEachIndexed { i, params ->
- val changedPackages = params.packageNames
- val changedUids = params.uids
- assertThat(changedPackages[0]).isEqualTo(packagesToChange[i])
- assertThat(changedUids[0]).isEqualTo(uidsToChange[i])
- }
- }
-
- private fun mockVisibilityAllowList(pkgName: String, list: IntArray?) {
- whenever(snapshot.getVisibilityAllowList(pkgName, TEST_USER_ID))
- .thenReturn(list ?: IntArray(0))
- }
-}
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
index d8770e5..9f1cec3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
@@ -29,7 +29,6 @@
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
-import org.mockito.Mockito.times
import org.mockito.Mockito.verify
@RunWith(JUnit4::class)
@@ -53,7 +52,7 @@
verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), nullable(), nullable())
+ nullable(), nullable(), nullable(), nullable())
val modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
val distractionFlags = bundleCaptor.value.getInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS)
@@ -78,7 +77,8 @@
verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper, never()).sendPackageBroadcast(
eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), bundleCaptor.capture(),
- anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
+ anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable(),
+ nullable())
assertThat(unactionedPackages).isEmpty()
}
@@ -156,7 +156,7 @@
verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), nullable(), nullable())
+ nullable(), nullable(), nullable(), nullable())
val modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
val distractionFlags = bundleCaptor.value.getInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS)
assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
@@ -172,7 +172,7 @@
verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper, never()).sendPackageBroadcast(eq(
Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), nullable(), anyInt(),
- nullable(), nullable(), any(), nullable(), nullable(), nullable())
+ nullable(), nullable(), any(), nullable(), nullable(), nullable(), nullable())
}
@Test
@@ -183,7 +183,7 @@
verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper, never()).sendPackageBroadcast(eq(
Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), nullable(), anyInt(),
- nullable(), nullable(), any(), nullable(), nullable(), nullable())
+ nullable(), nullable(), any(), nullable(), nullable(), nullable(), nullable())
distractingPackageHelper.removeDistractingPackageRestrictions(pms.snapshotComputer(),
arrayOfNulls(0), TEST_USER_ID)
@@ -191,18 +191,17 @@
verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper, never()).sendPackageBroadcast(eq(
Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), nullable(), anyInt(),
- nullable(), nullable(), any(), nullable(), nullable(), nullable())
+ nullable(), nullable(), any(), nullable(), nullable(), nullable(), nullable())
}
@Test
- fun sendDistractingPackagesChanged_withSameVisibilityAllowList() {
- distractingPackageHelper.sendDistractingPackagesChanged(pms.snapshotComputer(),
- packagesToChange, uidsToChange, TEST_USER_ID,
- PackageManager.RESTRICTION_HIDE_NOTIFICATIONS)
+ fun sendDistractingPackagesChanged() {
+ distractingPackageHelper.sendDistractingPackagesChanged(packagesToChange, uidsToChange,
+ TEST_USER_ID, PackageManager.RESTRICTION_HIDE_NOTIFICATIONS)
testHandler.flush()
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), nullable(), nullable())
+ nullable(), nullable(), nullable(), nullable())
var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
@@ -210,49 +209,4 @@
assertThat(changedUids).asList().containsExactly(
packageSetting1.appId, packageSetting2.appId)
}
-
- @Test
- fun sendDistractingPackagesChanged_withDifferentVisibilityAllowList() {
- mockDividedSeparatedBroadcastList(
- intArrayOf(10001, 10002, 10003), intArrayOf(10001, 10002, 10007))
-
- distractingPackageHelper.sendDistractingPackagesChanged(pms.snapshotComputer(),
- packagesToChange, uidsToChange, TEST_USER_ID,
- PackageManager.RESTRICTION_HIDE_NOTIFICATIONS)
- testHandler.flush()
- verify(broadcastHelper, times(2)).sendPackageBroadcast(
- eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), bundleCaptor.capture(),
- anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
-
- bundleCaptor.allValues.forEachIndexed { i, it ->
- var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
- var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
- assertThat(changedPackages?.size).isEqualTo(1)
- assertThat(changedUids?.size).isEqualTo(1)
- assertThat(changedPackages?.get(0)).isEqualTo(packagesToChange[i])
- assertThat(changedUids?.get(0)).isEqualTo(uidsToChange[i])
- }
- }
-
- @Test
- fun sendDistractingPackagesChanged_withNullVisibilityAllowList() {
- mockDividedSeparatedBroadcastList(intArrayOf(10001, 10002, 10003), null)
-
- distractingPackageHelper.sendDistractingPackagesChanged(pms.snapshotComputer(),
- packagesToChange, uidsToChange, TEST_USER_ID,
- PackageManager.RESTRICTION_HIDE_NOTIFICATIONS)
- testHandler.flush()
- verify(broadcastHelper, times(2)).sendPackageBroadcast(
- eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), bundleCaptor.capture(),
- anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
-
- bundleCaptor.allValues.forEachIndexed { i, it ->
- var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
- var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
- assertThat(changedPackages?.size).isEqualTo(1)
- assertThat(changedUids?.size).isEqualTo(1)
- assertThat(changedPackages?.get(0)).isEqualTo(packagesToChange[i])
- assertThat(changedUids?.get(0)).isEqualTo(uidsToChange[i])
- }
- }
-}
\ No newline at end of file
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
index 5f9ef58..4ac5e86 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
@@ -28,7 +28,6 @@
import org.junit.Before
import org.junit.Rule
import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
@@ -102,7 +101,6 @@
whenever(rule.mocks().userManagerService.hasUserRestriction(
eq(UserManager.DISALLOW_UNINSTALL_APPS), eq(TEST_USER_ID))).thenReturn(true)
mockKnownPackages(pms)
- mockUnifiedSeparatedBroadcastList()
}
private fun mockKnownPackages(pms: PackageManagerService) {
@@ -139,26 +137,4 @@
rule.system().validateFinalState()
return pms
}
-
- protected fun mockUnifiedSeparatedBroadcastList() {
- whenever(broadcastHelper.getBroadcastParams(any(Computer::class.java),
- any() as Array<String>, any(IntArray::class.java), anyInt()
- )).thenReturn(ArrayList<BroadcastParams>().apply {
- this.add(BroadcastParams(packagesToChange[0], uidsToChange[0], IntArray(0),
- TEST_USER_ID).apply {
- this.addPackage(packagesToChange[1], uidsToChange[1])
- })
- })
- }
-
- protected fun mockDividedSeparatedBroadcastList(allowlist1: IntArray?, allowlist2: IntArray?) {
- whenever(broadcastHelper.getBroadcastParams(any(Computer::class.java),
- any() as Array<String>, any(IntArray::class.java), anyInt()
- )).thenReturn(ArrayList<BroadcastParams>().apply {
- this.add(BroadcastParams(packagesToChange[0], uidsToChange[0],
- allowlist1 ?: IntArray(0), TEST_USER_ID))
- this.add(BroadcastParams(packagesToChange[1], uidsToChange[1],
- allowlist2 ?: IntArray(0), TEST_USER_ID))
- })
- }
-}
\ No newline at end of file
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
index df53dfe..dc74469 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -45,11 +45,13 @@
verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_SUSPENDED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), nullable(), nullable())
+ nullable(), nullable(), nullable(), nullable())
verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_SUSPENDED), nullable(),
- nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(), nullable(), nullable())
+ nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(), nullable(),
+ nullable(), nullable())
verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_SUSPENDED), nullable(),
- nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(), nullable(), nullable())
+ nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(), nullable(),
+ nullable(), nullable())
var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
@@ -135,13 +137,13 @@
verify(pms, times(2)).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_UNSUSPENDED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), nullable(), nullable())
+ nullable(), nullable(), nullable(), nullable())
verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
nullable(), nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(),
- nullable(), nullable())
+ nullable(), nullable(), nullable())
verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
nullable(), nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(),
- nullable(), nullable())
+ nullable(), nullable(), nullable())
var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
@@ -217,13 +219,13 @@
verify(pms, times(2)).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_UNSUSPENDED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), nullable(), nullable())
+ nullable(), nullable(), nullable(), nullable())
verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
nullable(), nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(),
- nullable(), nullable())
+ nullable(), nullable(), nullable())
verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
nullable(), nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(),
- nullable(), nullable())
+ nullable(), nullable(), nullable())
assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNull()
@@ -297,12 +299,13 @@
@Test
@Throws(Exception::class)
- fun sendPackagesSuspendedForUser_withSameVisibilityAllowList() {
- suspendPackageHelper.sendPackagesSuspendedForUser(pms.snapshotComputer(),
+ fun sendPackagesSuspendedForUser() {
+ suspendPackageHelper.sendPackagesSuspendedForUser(
Intent.ACTION_PACKAGES_SUSPENDED, packagesToChange, uidsToChange, TEST_USER_ID)
testHandler.flush()
verify(broadcastHelper).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
- anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
+ anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable(),
+ nullable())
var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
@@ -313,59 +316,14 @@
@Test
@Throws(Exception::class)
- fun sendPackagesSuspendedForUser_withDifferentVisibilityAllowList() {
- mockDividedSeparatedBroadcastList(
- intArrayOf(10001, 10002, 10003), intArrayOf(10001, 10002, 10007))
-
- suspendPackageHelper.sendPackagesSuspendedForUser(pms.snapshotComputer(),
- Intent.ACTION_PACKAGES_SUSPENDED, packagesToChange, uidsToChange, TEST_USER_ID)
- testHandler.flush()
- verify(broadcastHelper, times(2)).sendPackageBroadcast(
- any(), nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), any(), nullable())
-
- bundleCaptor.allValues.forEachIndexed { i, it ->
- var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
- var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
- assertThat(changedPackages?.size).isEqualTo(1)
- assertThat(changedUids?.size).isEqualTo(1)
- assertThat(changedPackages?.get(0)).isEqualTo(packagesToChange[i])
- assertThat(changedUids?.get(0)).isEqualTo(uidsToChange[i])
- }
- }
-
- @Test
- @Throws(Exception::class)
- fun sendPackagesSuspendedForUser_withNullVisibilityAllowList() {
- mockDividedSeparatedBroadcastList(intArrayOf(10001, 10002, 10003), null)
-
- suspendPackageHelper.sendPackagesSuspendedForUser(pms.snapshotComputer(),
- Intent.ACTION_PACKAGES_SUSPENDED, packagesToChange, uidsToChange, TEST_USER_ID)
- testHandler.flush()
- verify(broadcastHelper, times(2)).sendPackageBroadcast(
- any(), nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), nullable(), nullable())
-
- bundleCaptor.allValues.forEachIndexed { i, it ->
- var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
- var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
- assertThat(changedPackages?.size).isEqualTo(1)
- assertThat(changedUids?.size).isEqualTo(1)
- assertThat(changedPackages?.get(0)).isEqualTo(packagesToChange[i])
- assertThat(changedUids?.get(0)).isEqualTo(uidsToChange[i])
- }
- }
-
- @Test
- @Throws(Exception::class)
fun sendPackagesSuspendModifiedForUser() {
- suspendPackageHelper.sendPackagesSuspendedForUser(pms.snapshotComputer(),
- Intent.ACTION_PACKAGES_SUSPENSION_CHANGED, packagesToChange, uidsToChange,
- TEST_USER_ID)
+ suspendPackageHelper.sendPackagesSuspendedForUser(
+ Intent.ACTION_PACKAGES_SUSPENSION_CHANGED, packagesToChange, uidsToChange, TEST_USER_ID)
testHandler.flush()
verify(broadcastHelper).sendPackageBroadcast(
eq(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED), nullable(), bundleCaptor.capture(),
- anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
+ anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable(),
+ nullable())
var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
var modifiedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
index f9f6fe9..831a69a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
@@ -20,6 +20,8 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import android.app.AlarmManager;
@@ -92,7 +94,7 @@
Ledger ledger = new Ledger();
doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked();
- doReturn(1_000_000L).when(mEconomicPolicy).getMaxSatiatedBalance();
+ doReturn(1_000_000L).when(mEconomicPolicy).getMaxSatiatedBalance(anyInt(), anyString());
Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
@@ -121,7 +123,7 @@
Ledger ledger = new Ledger();
doReturn(1000L).when(mIrs).getConsumptionLimitLocked();
- doReturn(1_000_000L).when(mEconomicPolicy).getMaxSatiatedBalance();
+ doReturn(1_000_000L).when(mEconomicPolicy).getMaxSatiatedBalance(anyInt(), anyString());
Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
@@ -168,7 +170,7 @@
Ledger ledger = new Ledger();
doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked();
- doReturn(1000L).when(mEconomicPolicy).getMaxSatiatedBalance();
+ doReturn(1000L).when(mEconomicPolicy).getMaxSatiatedBalance(anyInt(), anyString());
Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
@@ -187,7 +189,7 @@
assertEquals(1_000, ledger.getCurrentBalance());
// Shouldn't change in normal operation, but adding test case in case it does.
- doReturn(900L).when(mEconomicPolicy).getMaxSatiatedBalance();
+ doReturn(900L).when(mEconomicPolicy).getMaxSatiatedBalance(anyInt(), anyString());
transaction = new Ledger.Transaction(0, 0, 0, null, 500, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
index da7664b..2fac31e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
@@ -63,7 +63,7 @@
}
@Override
- long getMaxSatiatedBalance() {
+ long getMaxSatiatedBalance(int userId, String pkgName) {
return 0;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java
index 2e200c3..fb3e8f2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java
@@ -134,8 +134,11 @@
mEconomicPolicy.getInitialSatiatedConsumptionLimit());
assertEquals(EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES,
mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ final String pkgRestricted = "com.pkg.restricted";
+ when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+ assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
assertEquals(EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES,
- mEconomicPolicy.getMaxSatiatedBalance());
+ mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
final String pkgExempted = "com.pkg.exempted";
when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
assertEquals(EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
@@ -154,7 +157,10 @@
assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
assertEquals(arcToCake(25), mEconomicPolicy.getHardSatiatedConsumptionLimit());
- assertEquals(arcToCake(10), mEconomicPolicy.getMaxSatiatedBalance());
+ final String pkgRestricted = "com.pkg.restricted";
+ when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+ assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
+ assertEquals(arcToCake(10), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
final String pkgExempted = "com.pkg.exempted";
when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
assertEquals(arcToCake(9), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
@@ -172,7 +178,10 @@
assertEquals(arcToCake(1), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
assertEquals(arcToCake(1), mEconomicPolicy.getHardSatiatedConsumptionLimit());
- assertEquals(arcToCake(1), mEconomicPolicy.getMaxSatiatedBalance());
+ final String pkgRestricted = "com.pkg.restricted";
+ when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+ assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
+ assertEquals(arcToCake(1), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
final String pkgExempted = "com.pkg.exempted";
when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
@@ -187,7 +196,8 @@
assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
assertEquals(arcToCake(5), mEconomicPolicy.getHardSatiatedConsumptionLimit());
- assertEquals(arcToCake(13), mEconomicPolicy.getMaxSatiatedBalance());
+ assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
+ assertEquals(arcToCake(13), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java
index 45c97e4..47155a1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java
@@ -142,9 +142,12 @@
assertEquals(EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES
+ EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES,
mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ final String pkgRestricted = "com.pkg.restricted";
+ when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+ assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES
+ EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES,
- mEconomicPolicy.getMaxSatiatedBalance());
+ mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
final String pkgExempted = "com.pkg.exempted";
when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
assertEquals(EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES
@@ -170,7 +173,10 @@
assertEquals(arcToCake(10), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
assertEquals(arcToCake(50), mEconomicPolicy.getHardSatiatedConsumptionLimit());
- assertEquals(arcToCake(20), mEconomicPolicy.getMaxSatiatedBalance());
+ final String pkgRestricted = "com.pkg.restricted";
+ when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+ assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
+ assertEquals(arcToCake(20), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
final String pkgExempted = "com.pkg.exempted";
when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
index 03ce91a..19b798d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
@@ -134,8 +134,11 @@
mEconomicPolicy.getInitialSatiatedConsumptionLimit());
assertEquals(EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES,
mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ final String pkgRestricted = "com.pkg.restricted";
+ when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+ assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
- mEconomicPolicy.getMaxSatiatedBalance());
+ mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
final String pkgExempted = "com.pkg.exempted";
when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
assertEquals(EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
@@ -154,7 +157,10 @@
assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
assertEquals(arcToCake(25), mEconomicPolicy.getHardSatiatedConsumptionLimit());
- assertEquals(arcToCake(10), mEconomicPolicy.getMaxSatiatedBalance());
+ final String pkgRestricted = "com.pkg.restricted";
+ when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+ assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
+ assertEquals(arcToCake(10), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
final String pkgExempted = "com.pkg.exempted";
when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
assertEquals(arcToCake(9), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
@@ -172,7 +178,10 @@
assertEquals(arcToCake(1), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
assertEquals(arcToCake(1), mEconomicPolicy.getHardSatiatedConsumptionLimit());
- assertEquals(arcToCake(1), mEconomicPolicy.getMaxSatiatedBalance());
+ final String pkgRestricted = "com.pkg.restricted";
+ when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+ assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
+ assertEquals(arcToCake(1), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
final String pkgExempted = "com.pkg.exempted";
when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
@@ -187,7 +196,8 @@
assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
assertEquals(arcToCake(5), mEconomicPolicy.getHardSatiatedConsumptionLimit());
- assertEquals(arcToCake(13), mEconomicPolicy.getMaxSatiatedBalance());
+ assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
+ assertEquals(arcToCake(13), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java
index 4a16874..842b23c 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java
@@ -17,13 +17,13 @@
package com.android.server.accessibility;
+import static android.view.accessibility.AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_ANCESTORS;
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST;
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST;
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID;
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS;
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_UNINTERRUPTIBLE;
-import static android.view.accessibility.AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS;
import static android.view.accessibility.AccessibilityNodeInfo.ROOT_NODE_ID;
import static org.junit.Assert.assertEquals;
@@ -528,8 +528,7 @@
// different client that holds different fetch flags for TextView1.
sendNodeRequestToController(nodeId, mMockClientCallback2,
mMockClient2InteractionId,
- FLAG_PREFETCH_SIBLINGS
- | FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS
+ FLAG_PREFETCH_SIBLINGS | FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
| FLAG_PREFETCH_ANCESTORS);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java
index bce99a0..2b6be37 100644
--- a/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -430,7 +430,8 @@
userId,
false /* allowBackgroundActivityStarts */,
null /* activityStartsToken */,
- false /* timeoutExempt */ );
+ false /* timeoutExempt */,
+ null /* filterExtrasForReceiver */);
}
private static int getAppId(int i) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java
deleted file mode 100644
index 8cd58ab..0000000
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Copyright (C) 2022 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.face;
-
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
-import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
-import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK;
-import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
-import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.hardware.biometrics.IBiometricService;
-import android.hardware.face.FaceSensorProperties;
-import android.hardware.face.FaceSensorPropertiesInternal;
-import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
-import android.hardware.face.IFaceService;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@Presubmit
-@SmallTest
-public class FaceServiceRegistryTest {
-
- private static final int SENSOR_ID_1 = 1;
- private static final int SENSOR_ID_2 = 2;
-
- @Rule
- public final MockitoRule mockito = MockitoJUnit.rule();
-
- @Mock
- private IBiometricService mBiometricService;
- @Mock
- private IFaceService mFaceService;
- @Mock
- private ServiceProvider mProvider1;
- @Mock
- private ServiceProvider mProvider2;
- @Captor
- private ArgumentCaptor<Integer> mIdCaptor;
- @Captor
- private ArgumentCaptor<Integer> mStrengthCaptor;
-
- private FaceSensorPropertiesInternal mProvider1Props;
- private FaceSensorPropertiesInternal mProvider2Props;
- private FaceServiceRegistry mRegistry;
-
- @Before
- public void setup() {
- mProvider1Props = new FaceSensorPropertiesInternal(SENSOR_ID_1,
- STRENGTH_WEAK, 5 /* maxEnrollmentsPerUser */,
- List.of(), FaceSensorProperties.TYPE_RGB,
- true /* supportsFace Detection */,
- true /* supportsSelfIllumination */,
- false /* resetLockoutRequiresHardwareAuthToken */);
- mProvider2Props = new FaceSensorPropertiesInternal(SENSOR_ID_2,
- STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
- List.of(), FaceSensorProperties.TYPE_IR,
- true /* supportsFace Detection */,
- true /* supportsSelfIllumination */,
- false /* resetLockoutRequiresHardwareAuthToken */);
-
- when(mProvider1.getSensorProperties()).thenReturn(List.of(mProvider1Props));
- when(mProvider1.containsSensor(eq(SENSOR_ID_1))).thenReturn(true);
- when(mProvider2.getSensorProperties()).thenReturn(List.of(mProvider2Props));
- when(mProvider2.containsSensor(eq(SENSOR_ID_2))).thenReturn(true);
- mRegistry = new FaceServiceRegistry(mFaceService, () -> mBiometricService);
- }
-
- @Test
- public void registersAllProviders() throws Exception {
- mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
-
- assertThat(mRegistry.getProviders()).containsExactly(mProvider1, mProvider2);
- assertThat(mRegistry.getAllProperties()).containsExactly(mProvider1Props, mProvider2Props);
- verify(mBiometricService, times(2)).registerAuthenticator(
- mIdCaptor.capture(), eq(TYPE_FACE), mStrengthCaptor.capture(), any());
- assertThat(mIdCaptor.getAllValues()).containsExactly(SENSOR_ID_1, SENSOR_ID_2);
- assertThat(mStrengthCaptor.getAllValues())
- .containsExactly(BIOMETRIC_WEAK, BIOMETRIC_STRONG);
- }
-
- @Test
- public void getsProviderById() {
- mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
-
- assertThat(mRegistry.getProviderForSensor(SENSOR_ID_1)).isSameInstanceAs(mProvider1);
- assertThat(mRegistry.getProviderForSensor(SENSOR_ID_2)).isSameInstanceAs(mProvider2);
- assertThat(mRegistry.getProviderForSensor(500)).isNull();
- }
-
- @Test
- public void getsSingleProvider() {
- mRegistry.registerAllInBackground(() -> List.of(mProvider1));
-
- assertThat(mRegistry.getSingleProvider().second).isSameInstanceAs(mProvider1);
- assertThat(mRegistry.getProviders()).containsExactly(mProvider1);
- assertThat(mRegistry.getProviderForSensor(SENSOR_ID_1)).isSameInstanceAs(mProvider1);
- }
-
- @Test
- public void registersListenerBeforeAllRegistered() {
- final List<FaceSensorPropertiesInternal> all = new ArrayList<>();
- mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
- @Override
- public void onAllAuthenticatorsRegistered(
- List<FaceSensorPropertiesInternal> sensors) {
- all.addAll(sensors);
- }
- });
-
- mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
-
- assertThat(all).containsExactly(mProvider1Props, mProvider2Props);
- }
-
- @Test
- public void registersListenerAfterAllRegistered() {
- mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
-
- final List<FaceSensorPropertiesInternal> all = new ArrayList<>();
- mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
- @Override
- public void onAllAuthenticatorsRegistered(
- List<FaceSensorPropertiesInternal> sensors) {
- all.addAll(sensors);
- }
- });
-
- assertThat(all).containsExactly(mProvider1Props, mProvider2Props);
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
index 0e30782..5f88c99 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
@@ -16,8 +16,6 @@
package com.android.server.biometrics.sensors.fingerprint;
-import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
-
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -26,73 +24,38 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricStateListener;
-import android.hardware.biometrics.SensorPropertiesInternal;
-import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import com.android.server.biometrics.sensors.AuthenticationClient;
-import com.android.server.biometrics.sensors.BiometricServiceProvider;
import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.EnrollClient;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.List;
+import org.mockito.MockitoAnnotations;
@Presubmit
@SmallTest
public class BiometricStateCallbackTest {
- private static final int USER_ID = 10;
- private static final int SENSOR_ID = 2;
-
- @Rule
- public final MockitoRule mockito = MockitoJUnit.rule();
-
- private BiometricStateCallback<FakeProvider, SensorPropertiesInternal> mCallback;
+ private BiometricStateCallback mCallback;
@Mock
- private UserManager mUserManager;
- @Mock
- private BiometricStateListener mBiometricStateListener;
- @Mock
- private FakeProvider mFakeProvider;
-
- private SensorPropertiesInternal mFakeProviderProps;
+ BiometricStateListener mBiometricStateListener;
@Before
public void setup() {
- mFakeProviderProps = new SensorPropertiesInternal(SENSOR_ID, STRENGTH_STRONG,
- 5 /* maxEnrollmentsPerUser */, List.of(),
- false /* resetLockoutRequiresHardwareAuthToken */,
- false /* resetLockoutRequiresChallenge */);
- when(mFakeProvider.getSensorProperties()).thenReturn(List.of(mFakeProviderProps));
- when(mFakeProvider.getSensorProperties(eq(SENSOR_ID))).thenReturn(mFakeProviderProps);
- when(mFakeProvider.hasEnrollments(eq(SENSOR_ID), eq(USER_ID))).thenReturn(true);
- when(mUserManager.getAliveUsers()).thenReturn(
- List.of(new UserInfo(USER_ID, "name", 0)));
+ MockitoAnnotations.initMocks(this);
- mCallback = new BiometricStateCallback<>(mUserManager);
+ mCallback = new BiometricStateCallback();
mCallback.registerBiometricStateListener(mBiometricStateListener);
}
@Test
- public void startNotifiesEnrollments() {
- mCallback.start(List.of(mFakeProvider));
-
- verify(mBiometricStateListener).onEnrollmentsChanged(eq(USER_ID), eq(SENSOR_ID), eq(true));
- }
-
- @Test
public void testNoEnrollmentsToEnrollments_callbackNotified() {
testEnrollmentCallback(true /* changed */, true /* isNowEnrolled */,
true /* expectCallback */, true /* expectedCallbackValue */);
@@ -139,6 +102,4 @@
verify(mBiometricStateListener, never()).onEnrollmentsChanged(anyInt(), anyInt(),
anyBoolean());
}
-
- private interface FakeProvider extends BiometricServiceProvider<SensorPropertiesInternal> {}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java
deleted file mode 100644
index 67d94a8..0000000
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2022 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.fingerprint;
-
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
-import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
-import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK;
-import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
-import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.hardware.biometrics.IBiometricService;
-import android.hardware.fingerprint.FingerprintSensorProperties;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
-import android.hardware.fingerprint.IFingerprintService;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@Presubmit
-@SmallTest
-public class FingerprintServiceRegistryTest {
-
- private static final int SENSOR_ID_1 = 1;
- private static final int SENSOR_ID_2 = 2;
-
- @Rule
- public final MockitoRule mockito = MockitoJUnit.rule();
-
- @Mock
- private IBiometricService mBiometricService;
- @Mock
- private IFingerprintService mFingerprintService;
- @Mock
- private ServiceProvider mProvider1;
- @Mock
- private ServiceProvider mProvider2;
- @Captor
- private ArgumentCaptor<Integer> mIdCaptor;
- @Captor
- private ArgumentCaptor<Integer> mStrengthCaptor;
-
- private FingerprintSensorPropertiesInternal mProvider1Props;
- private FingerprintSensorPropertiesInternal mProvider2Props;
- private FingerprintServiceRegistry mRegistry;
-
- @Before
- public void setup() {
- mProvider1Props = new FingerprintSensorPropertiesInternal(SENSOR_ID_1,
- STRENGTH_WEAK, 5 /* maxEnrollmentsPerUser */,
- List.of(), FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
- false /* resetLockoutRequiresHardwareAuthToken */);
- mProvider2Props = new FingerprintSensorPropertiesInternal(SENSOR_ID_2,
- STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
- List.of(), FingerprintSensorProperties.TYPE_UNKNOWN,
- false /* resetLockoutRequiresHardwareAuthToken */);
-
- when(mProvider1.getSensorProperties()).thenReturn(List.of(mProvider1Props));
- when(mProvider1.containsSensor(eq(SENSOR_ID_1))).thenReturn(true);
- when(mProvider2.getSensorProperties()).thenReturn(List.of(mProvider2Props));
- when(mProvider2.containsSensor(eq(SENSOR_ID_2))).thenReturn(true);
- mRegistry = new FingerprintServiceRegistry(mFingerprintService, () -> mBiometricService);
- }
-
- @Test
- public void registersAllProviders() throws Exception {
- mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
-
- assertThat(mRegistry.getProviders()).containsExactly(mProvider1, mProvider2);
- assertThat(mRegistry.getAllProperties()).containsExactly(mProvider1Props, mProvider2Props);
- verify(mBiometricService, times(2)).registerAuthenticator(
- mIdCaptor.capture(), eq(TYPE_FINGERPRINT), mStrengthCaptor.capture(), any());
- assertThat(mIdCaptor.getAllValues()).containsExactly(SENSOR_ID_1, SENSOR_ID_2);
- assertThat(mStrengthCaptor.getAllValues())
- .containsExactly(BIOMETRIC_WEAK, BIOMETRIC_STRONG);
- }
-
- @Test
- public void getsProviderById() {
- mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
-
- assertThat(mRegistry.getProviderForSensor(SENSOR_ID_1)).isSameInstanceAs(mProvider1);
- assertThat(mRegistry.getProviderForSensor(SENSOR_ID_2)).isSameInstanceAs(mProvider2);
- assertThat(mRegistry.getProviderForSensor(500)).isNull();
- }
-
- @Test
- public void getsSingleProvider() {
- mRegistry.registerAllInBackground(() -> List.of(mProvider1));
-
- assertThat(mRegistry.getSingleProvider().second).isSameInstanceAs(mProvider1);
- assertThat(mRegistry.getProviders()).containsExactly(mProvider1);
- assertThat(mRegistry.getProviderForSensor(SENSOR_ID_1)).isSameInstanceAs(mProvider1);
- }
-
- @Test
- public void registersListenerBeforeAllRegistered() {
- final List<FingerprintSensorPropertiesInternal> all = new ArrayList<>();
- mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
- @Override
- public void onAllAuthenticatorsRegistered(
- List<FingerprintSensorPropertiesInternal> sensors) {
- all.addAll(sensors);
- }
- });
-
- mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
-
- assertThat(all).containsExactly(mProvider1Props, mProvider2Props);
- }
-
- @Test
- public void registersListenerAfterAllRegistered() {
- mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
-
- final List<FingerprintSensorPropertiesInternal> all = new ArrayList<>();
- mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
- @Override
- public void onAllAuthenticatorsRegistered(
- List<FingerprintSensorPropertiesInternal> sensors) {
- all.addAll(sensors);
- }
- });
-
- assertThat(all).containsExactly(mProvider1Props, mProvider2Props);
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index a4048a2..ca3677e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -32,8 +32,7 @@
import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.SensorLocation;
import android.hardware.biometrics.fingerprint.SensorProps;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
+import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.testing.TestableContext;
@@ -53,8 +52,6 @@
import org.mockito.junit.MockitoRule;
import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
@Presubmit
@SmallTest
@@ -97,12 +94,9 @@
mContext.getTestablePermissions().setPermission(
USE_BIOMETRIC_INTERNAL, PackageManager.PERMISSION_GRANTED);
- }
- private void initServiceWith(String... aidlInstances) {
mService = new FingerprintService(mContext, mBiometricContext,
() -> mIBiometricService,
- () -> aidlInstances,
(fqName) -> {
if (fqName.endsWith(NAME_DEFAULT)) return mIFingerprintDefault;
if (fqName.endsWith(NAME_VIRTUAL)) return mIFingerprintVirtual;
@@ -111,50 +105,29 @@
}
@Test
- public void registerAuthenticators_defaultOnly() throws Exception {
- initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
-
- mService.mServiceWrapper.registerAuthenticators(List.of());
- waitForRegistration();
+ public void registerAuthenticators_defaultOnly() throws RemoteException {
+ mService.registerAuthenticatorsForService(List.of(NAME_DEFAULT, NAME_VIRTUAL), List.of());
verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT), anyInt(), anyInt(), any());
}
@Test
- public void registerAuthenticators_virtualOnly() throws Exception {
- initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
+ public void registerAuthenticators_virtualOnly() throws RemoteException {
Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
- mService.mServiceWrapper.registerAuthenticators(List.of());
- waitForRegistration();
+ mService.registerAuthenticatorsForService(List.of(NAME_DEFAULT, NAME_VIRTUAL), List.of());
verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
}
@Test
- public void registerAuthenticators_virtualAlwaysWhenNoOther() throws Exception {
- initServiceWith(NAME_VIRTUAL);
-
- mService.mServiceWrapper.registerAuthenticators(List.of());
- waitForRegistration();
+ public void registerAuthenticators_virtualAlwaysWhenNoOther() throws RemoteException {
+ mService.registerAuthenticatorsForService(List.of(NAME_VIRTUAL), List.of());
verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
}
- private void waitForRegistration() throws Exception {
- final CountDownLatch latch = new CountDownLatch(1);
- mService.mServiceWrapper.addAuthenticatorsRegisteredCallback(
- new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
- @Override
- public void onAllAuthenticatorsRegistered(
- List<FingerprintSensorPropertiesInternal> sensors) {
- latch.countDown();
- }
- });
- latch.await(5, TimeUnit.SECONDS);
- }
-
private static SensorProps createProps(int id, byte strength, byte type) {
final SensorProps props = new SensorProps();
props.commonProps = new CommonProps();
diff --git a/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java b/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
index 8139310..c90064e 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
@@ -58,6 +58,7 @@
.setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
.setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID)
.setLastEventTimestamp(100L)
+ .setCreationTimestamp(200L)
.setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED
| ShortcutInfo.FLAG_CACHED_NOTIFICATIONS)
.setImportant(true)
@@ -79,6 +80,7 @@
assertEquals(PARENT_NOTIFICATION_CHANNEL_ID,
conversationInfo.getParentNotificationChannelId());
assertEquals(100L, conversationInfo.getLastEventTimestamp());
+ assertEquals(200L, conversationInfo.getCreationTimestamp());
assertTrue(conversationInfo.isShortcutLongLived());
assertTrue(conversationInfo.isShortcutCachedForNotification());
assertTrue(conversationInfo.isImportant());
@@ -105,6 +107,7 @@
assertNull(conversationInfo.getNotificationChannelId());
assertNull(conversationInfo.getParentNotificationChannelId());
assertEquals(0L, conversationInfo.getLastEventTimestamp());
+ assertEquals(0L, conversationInfo.getCreationTimestamp());
assertFalse(conversationInfo.isShortcutLongLived());
assertFalse(conversationInfo.isShortcutCachedForNotification());
assertFalse(conversationInfo.isImportant());
@@ -131,6 +134,7 @@
.setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
.setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID)
.setLastEventTimestamp(100L)
+ .setCreationTimestamp(200L)
.setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED)
.setImportant(true)
.setNotificationSilenced(true)
@@ -154,6 +158,7 @@
assertEquals(NOTIFICATION_CHANNEL_ID, destination.getNotificationChannelId());
assertEquals(PARENT_NOTIFICATION_CHANNEL_ID, destination.getParentNotificationChannelId());
assertEquals(100L, destination.getLastEventTimestamp());
+ assertEquals(200L, destination.getCreationTimestamp());
assertTrue(destination.isShortcutLongLived());
assertFalse(destination.isImportant());
assertTrue(destination.isNotificationSilenced());
@@ -164,4 +169,105 @@
assertThat(destination.getStatuses()).contains(cs);
assertThat(destination.getStatuses()).contains(cs2);
}
+
+ @Test
+ public void testBuildFromAnotherConversation_identicalConversation() {
+ ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_ANNIVERSARY).build();
+ ConversationStatus cs2 = new ConversationStatus.Builder("id2", ACTIVITY_GAME).build();
+
+ ConversationInfo source = new ConversationInfo.Builder()
+ .setShortcutId(SHORTCUT_ID)
+ .setLocusId(LOCUS_ID)
+ .setContactUri(CONTACT_URI)
+ .setContactPhoneNumber(PHONE_NUMBER)
+ .setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
+ .setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID)
+ .setLastEventTimestamp(100L)
+ .setCreationTimestamp(200L)
+ .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED)
+ .setImportant(true)
+ .setNotificationSilenced(true)
+ .setBubbled(true)
+ .setPersonImportant(true)
+ .setPersonBot(true)
+ .setContactStarred(true)
+ .addOrUpdateStatus(cs)
+ .addOrUpdateStatus(cs2)
+ .build();
+
+ ConversationInfo destination = new ConversationInfo.Builder(source).build();
+
+ assertEquals(SHORTCUT_ID, destination.getShortcutId());
+ assertEquals(LOCUS_ID, destination.getLocusId());
+ assertEquals(CONTACT_URI, destination.getContactUri());
+ assertEquals(PHONE_NUMBER, destination.getContactPhoneNumber());
+ assertEquals(NOTIFICATION_CHANNEL_ID, destination.getNotificationChannelId());
+ assertEquals(PARENT_NOTIFICATION_CHANNEL_ID, destination.getParentNotificationChannelId());
+ assertEquals(100L, destination.getLastEventTimestamp());
+ assertEquals(200L, destination.getCreationTimestamp());
+ assertTrue(destination.isShortcutLongLived());
+ assertTrue(destination.isImportant());
+ assertTrue(destination.isNotificationSilenced());
+ assertTrue(destination.isBubbled());
+ assertTrue(destination.isPersonImportant());
+ assertTrue(destination.isPersonBot());
+ assertTrue(destination.isContactStarred());
+ assertThat(destination.getStatuses()).contains(cs);
+ assertThat(destination.getStatuses()).contains(cs2);
+ // Also check equals() implementation
+ assertTrue(source.equals(destination));
+ assertTrue(destination.equals(source));
+ }
+
+ @Test
+ public void testBuildFromBackupPayload() {
+ ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_ANNIVERSARY).build();
+ ConversationStatus cs2 = new ConversationStatus.Builder("id2", ACTIVITY_GAME).build();
+
+ ConversationInfo conversationInfo = new ConversationInfo.Builder()
+ .setShortcutId(SHORTCUT_ID)
+ .setLocusId(LOCUS_ID)
+ .setContactUri(CONTACT_URI)
+ .setContactPhoneNumber(PHONE_NUMBER)
+ .setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
+ .setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID)
+ .setLastEventTimestamp(100L)
+ .setCreationTimestamp(200L)
+ .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED
+ | ShortcutInfo.FLAG_CACHED_NOTIFICATIONS)
+ .setImportant(true)
+ .setNotificationSilenced(true)
+ .setBubbled(true)
+ .setDemoted(true)
+ .setPersonImportant(true)
+ .setPersonBot(true)
+ .setContactStarred(true)
+ .addOrUpdateStatus(cs)
+ .addOrUpdateStatus(cs2)
+ .build();
+
+ ConversationInfo conversationInfoFromBackup =
+ ConversationInfo.readFromBackupPayload(conversationInfo.getBackupPayload());
+
+ assertEquals(SHORTCUT_ID, conversationInfoFromBackup.getShortcutId());
+ assertEquals(LOCUS_ID, conversationInfoFromBackup.getLocusId());
+ assertEquals(CONTACT_URI, conversationInfoFromBackup.getContactUri());
+ assertEquals(PHONE_NUMBER, conversationInfoFromBackup.getContactPhoneNumber());
+ assertEquals(
+ NOTIFICATION_CHANNEL_ID, conversationInfoFromBackup.getNotificationChannelId());
+ assertEquals(PARENT_NOTIFICATION_CHANNEL_ID,
+ conversationInfoFromBackup.getParentNotificationChannelId());
+ assertEquals(100L, conversationInfoFromBackup.getLastEventTimestamp());
+ assertEquals(200L, conversationInfoFromBackup.getCreationTimestamp());
+ assertTrue(conversationInfoFromBackup.isShortcutLongLived());
+ assertTrue(conversationInfoFromBackup.isShortcutCachedForNotification());
+ assertTrue(conversationInfoFromBackup.isImportant());
+ assertTrue(conversationInfoFromBackup.isNotificationSilenced());
+ assertTrue(conversationInfoFromBackup.isBubbled());
+ assertTrue(conversationInfoFromBackup.isDemoted());
+ assertTrue(conversationInfoFromBackup.isPersonImportant());
+ assertTrue(conversationInfoFromBackup.isPersonBot());
+ assertTrue(conversationInfoFromBackup.isContactStarred());
+ // ConversationStatus is a transient object and not persisted
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 2a4896a..66c3f07 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -291,7 +291,8 @@
mShortcutChangeCallbackCaptor.capture());
mShortcutChangeCallback = mShortcutChangeCallbackCaptor.getValue();
- verify(mContext, times(2)).registerReceiver(any(), any());
+ verify(mContext, times(1)).registerReceiver(any(), any());
+ verify(mContext, times(1)).registerReceiver(any(), any(), anyInt());
}
@After
@@ -1163,6 +1164,76 @@
}
@Test
+ public void testUncacheOldestCachedShortcut_missingNotificationEvents() {
+ mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+ for (int i = 0; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
+ String shortcutId = TEST_SHORTCUT_ID + i;
+ ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId,
+ buildPerson());
+ shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+ mShortcutChangeCallback.onShortcutsAddedOrUpdated(
+ TEST_PKG_NAME,
+ Collections.singletonList(shortcut),
+ UserHandle.of(USER_ID_PRIMARY));
+ mLooper.dispatchAll();
+ }
+
+ // Only the shortcut #0 is uncached, all the others are not.
+ verify(mShortcutServiceInternal).uncacheShortcuts(
+ anyInt(), any(), eq(TEST_PKG_NAME),
+ eq(Collections.singletonList(TEST_SHORTCUT_ID + 0)), eq(USER_ID_PRIMARY),
+ eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
+ for (int i = 1; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
+ verify(mShortcutServiceInternal, never()).uncacheShortcuts(
+ anyInt(), anyString(), anyString(),
+ eq(Collections.singletonList(TEST_SHORTCUT_ID + i)), anyInt(),
+ eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
+ }
+ }
+
+ @Test
+ public void testUncacheOldestCachedShortcut_legacyConversation() {
+ mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+ // Add an extra conversation with a legacy type (no creationTime)
+ ConversationStore conversationStore = mDataManager
+ .getUserDataForTesting(USER_ID_PRIMARY)
+ .getOrCreatePackageData(TEST_PKG_NAME)
+ .getConversationStore();
+ ConversationInfo.Builder builder = new ConversationInfo.Builder();
+ builder.setShortcutId(TEST_SHORTCUT_ID + 0);
+ builder.setShortcutFlags(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+ mDataManager.updateConversationStoreThenNotifyListeners(
+ conversationStore,
+ builder.build(),
+ TEST_PKG_NAME, USER_ID_PRIMARY);
+ for (int i = 1; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
+ String shortcutId = TEST_SHORTCUT_ID + i;
+ ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId,
+ buildPerson());
+ shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+ mShortcutChangeCallback.onShortcutsAddedOrUpdated(
+ TEST_PKG_NAME,
+ Collections.singletonList(shortcut),
+ UserHandle.of(USER_ID_PRIMARY));
+ mLooper.dispatchAll();
+ }
+
+ // Only the shortcut #0 is uncached, all the others are not.
+ verify(mShortcutServiceInternal).uncacheShortcuts(
+ anyInt(), any(), eq(TEST_PKG_NAME),
+ eq(Collections.singletonList(TEST_SHORTCUT_ID + 0)), eq(USER_ID_PRIMARY),
+ eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
+ for (int i = 1; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
+ verify(mShortcutServiceInternal, never()).uncacheShortcuts(
+ anyInt(), anyString(), anyString(),
+ eq(Collections.singletonList(TEST_SHORTCUT_ID + i)), anyInt(),
+ eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
+ }
+ }
+
+ @Test
public void testBackupAndRestoration()
throws IntentFilter.MalformedMimeTypeException {
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
diff --git a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
index 36e988f..3848bab 100644
--- a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
@@ -381,6 +381,7 @@
assertEquals(0, Arrays.asList(mService.mService.getCurrentTemperaturesWithType(
Temperature.TYPE_SKIN)).size());
assertEquals(Temperature.THROTTLING_NONE, mService.mService.getCurrentThermalStatus());
+ assertTrue(Float.isNaN(mService.mService.getThermalHeadroom(0)));
}
@Test
@@ -397,6 +398,14 @@
}
@Test
+ public void testGetThermalHeadroomInputRange() throws RemoteException {
+ assertTrue(Float.isNaN(mService.mService.getThermalHeadroom(
+ ThermalManagerService.MIN_FORECAST_SEC - 1)));
+ assertTrue(Float.isNaN(mService.mService.getThermalHeadroom(
+ ThermalManagerService.MAX_FORECAST_SEC + 1)));
+ }
+
+ @Test
public void testTemperatureWatcherUpdateSevereThresholds() throws RemoteException {
ThermalManagerService.TemperatureWatcher watcher = mService.mTemperatureWatcher;
watcher.mSevereThresholds.erase();
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
index fa3fcd9..235849c 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -127,9 +127,17 @@
}
@Override
- public long compose(PrimitiveSegment[] effects, long vibrationId) {
+ public long compose(PrimitiveSegment[] primitives, long vibrationId) {
+ if (mSupportedPrimitives == null) {
+ return 0;
+ }
+ for (PrimitiveSegment primitive : primitives) {
+ if (Arrays.binarySearch(mSupportedPrimitives, primitive.getPrimitiveId()) < 0) {
+ return 0;
+ }
+ }
long duration = 0;
- for (PrimitiveSegment primitive : effects) {
+ for (PrimitiveSegment primitive : primitives) {
duration += EFFECT_DURATION + primitive.getDelay();
recordEffectSegment(vibrationId, primitive);
}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationTest.java
new file mode 100644
index 0000000..b469299
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 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.vibrator;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.util.stream.Collectors.toList;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+
+/**
+ * Tests for {@link Vibration}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:VibrationTest
+ */
+@Presubmit
+public class VibrationTest {
+
+ @Test
+ public void status_hasUniqueProtoEnumValues() {
+ assertThat(
+ Arrays.stream(Vibration.Status.values())
+ .map(Vibration.Status::getProtoEnumValue)
+ .collect(toList()))
+ .containsNoDuplicates();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index de5f6ed..ca162ef 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -257,13 +257,18 @@
assertTrue(mThread.isRunningVibrationId(vibrationId));
assertTrue(mControllers.get(VIBRATOR_ID).isVibrating());
- conductor.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false);
+ Vibration.EndInfo cancelVibrationInfo = new Vibration.EndInfo(
+ Vibration.Status.CANCELLED_SUPERSEDED, /* endedByUid= */ 1,
+ /* endedByUsage= */ VibrationAttributes.USAGE_ALARM);
+ conductor.notifyCancelled(
+ cancelVibrationInfo,
+ /* immediate= */ false);
waitForCompletion();
assertFalse(mThread.isRunningVibrationId(vibrationId));
verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong());
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_SUPERSEDED);
+ verifyCallbacksTriggered(vibrationId, cancelVibrationInfo);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
List<Float> playedAmplitudes = fakeVibrator.getAmplitudes();
@@ -288,7 +293,9 @@
VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
- conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
+ conductor.notifyCancelled(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+ /* immediate= */ false);
waitForCompletion();
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
@@ -319,7 +326,9 @@
assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
TEST_TIMEOUT_MILLIS));
- conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
+ conductor.notifyCancelled(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+ /* immediate= */ false);
waitForCompletion();
// PWLE size max was used to generate a single vibrate call with 10 segments.
@@ -348,11 +357,13 @@
assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
TEST_TIMEOUT_MILLIS));
- conductor.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false);
+ conductor.notifyCancelled(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+ /* immediate= */ false);
waitForCompletion();
// Composition size max was used to generate a single vibrate call with 10 primitives.
- verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_SUPERSEDED);
+ verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(10, fakeVibrator.getEffectSegments(vibrationId).size());
}
@@ -370,7 +381,9 @@
VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
- conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
+ conductor.notifyCancelled(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+ /* immediate= */ false);
waitForCompletion();
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
@@ -394,7 +407,9 @@
assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibrationId).size() > 1,
5000 + TEST_TIMEOUT_MILLIS));
- conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
+ conductor.notifyCancelled(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+ /* immediate= */ false);
waitForCompletion();
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
@@ -414,6 +429,8 @@
public void vibrate_singleVibratorPredefinedCancel_cancelsVibrationImmediately()
throws Exception {
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_CLICK);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.startComposition()
@@ -431,7 +448,9 @@
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
Thread cancellingThread =
new Thread(() -> conductor.notifyCancelled(
- Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE, /* immediate= */ false));
+ new Vibration.EndInfo(
+ Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
+ /* immediate= */ false));
cancellingThread.start();
waitForCompletion(/* timeout= */ 50);
@@ -458,7 +477,9 @@
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
Thread cancellingThread =
new Thread(() -> conductor.notifyCancelled(
- Vibration.Status.CANCELLED_BY_SCREEN_OFF, /* immediate= */ false));
+ new Vibration.EndInfo(
+ Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+ /* immediate= */ false));
cancellingThread.start();
waitForCompletion(/* timeout= */ 50);
@@ -519,7 +540,7 @@
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
- verify(mManagerHooks, never()).noteVibratorOn(eq(UID), anyLong());
+ verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
@@ -530,6 +551,8 @@
public void vibrate_singleVibratorComposed_runsVibration() throws Exception {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK,
+ VibrationEffect.Composition.PRIMITIVE_TICK);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.startComposition()
@@ -559,7 +582,7 @@
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
- verify(mManagerHooks, never()).noteVibratorOn(eq(UID), anyLong());
+ verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
@@ -570,6 +593,10 @@
public void vibrate_singleVibratorLargeComposition_splitsVibratorComposeCalls() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ fakeVibrator.setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_CLICK,
+ VibrationEffect.Composition.PRIMITIVE_TICK,
+ VibrationEffect.Composition.PRIMITIVE_SPIN);
fakeVibrator.setCompositionSizeMax(2);
long vibrationId = 1;
@@ -809,6 +836,8 @@
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
mVibratorProviders.get(3).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(4).setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_CLICK);
long vibrationId = 1;
VibrationEffect composed = VibrationEffect.startComposition()
@@ -854,6 +883,8 @@
mockVibrators(1, 2, 3);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(2).setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_CLICK);
mVibratorProviders.get(3).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
long vibrationId = 1;
@@ -902,7 +933,11 @@
long vibrationId = 1;
mockVibrators(vibratorIds);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(1).setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_CLICK);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(2).setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_CLICK);
when(mManagerHooks.prepareSyncedVibration(anyLong(), eq(vibratorIds))).thenReturn(true);
when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true);
@@ -939,6 +974,8 @@
mockVibrators(vibratorIds);
mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(4).setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_CLICK);
when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(true);
@@ -1125,7 +1162,9 @@
// fail at waitForCompletion(cancellingThread).
Thread cancellingThread = new Thread(
() -> conductor.notifyCancelled(
- Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false));
+ new Vibration.EndInfo(
+ Vibration.Status.CANCELLED_BY_USER),
+ /* immediate= */ false));
cancellingThread.start();
// Cancelling the vibration should be fast and return right away, even if the thread is
@@ -1143,6 +1182,8 @@
mockVibrators(1, 2);
mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(2).setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_CLICK);
long vibrationId = 1;
CombinedVibration effect = CombinedVibration.startParallel()
@@ -1163,13 +1204,15 @@
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
Thread cancellingThread = new Thread(
() -> conductor.notifyCancelled(
- Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false));
+ new Vibration.EndInfo(
+ Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+ /* immediate= */ false));
cancellingThread.start();
waitForCompletion(/* timeout= */ 50);
cancellingThread.join();
- verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_SUPERSEDED);
+ verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
}
@@ -1195,9 +1238,11 @@
// Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
- Thread cancellingThread =
- new Thread(() -> conductor.notifyCancelled(
- Vibration.Status.CANCELLED_BY_SCREEN_OFF, /* immediate= */ false));
+ Thread cancellingThread = new Thread(
+ () -> conductor.notifyCancelled(
+ new Vibration.EndInfo(
+ Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+ /* immediate= */ false));
cancellingThread.start();
waitForCompletion(/* timeout= */ 50);
@@ -1266,7 +1311,7 @@
// Vibration completed but vibrator not yet released.
verify(mManagerHooks, timeout(TEST_TIMEOUT_MILLIS)).onVibrationCompleted(eq(vibrationId),
- eq(Vibration.Status.FINISHED));
+ eq(new Vibration.EndInfo(Vibration.Status.FINISHED)));
verify(mManagerHooks, never()).onVibrationThreadReleased(anyLong());
// Thread still running ramp down.
@@ -1278,12 +1323,13 @@
// Will stop the ramp down right away.
conductor.notifyCancelled(
- Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE, /* immediate= */ true);
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
+ /* immediate= */ true);
waitForCompletion();
// Does not cancel already finished vibration, but releases vibrator.
verify(mManagerHooks, never()).onVibrationCompleted(eq(vibrationId),
- eq(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE));
+ eq(new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE)));
verify(mManagerHooks).onVibrationThreadReleased(vibrationId);
}
@@ -1299,7 +1345,9 @@
VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
TEST_TIMEOUT_MILLIS));
- conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
+ conductor.notifyCancelled(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+ /* immediate= */ false);
waitForCompletion();
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
@@ -1422,7 +1470,9 @@
VibrationStepConductor conductor2 = startThreadAndDispatcher(vibrationId2, effect2);
// Effect2 won't complete on its own. Cancel it after a couple of repeats.
Thread.sleep(150); // More than two TICKs.
- conductor2.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
+ conductor2.notifyCancelled(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+ /* immediate= */ false);
waitForCompletion();
startThreadAndDispatcher(vibrationId3, effect3);
@@ -1431,7 +1481,9 @@
// Effect4 is a long oneshot, but it gets cancelled as fast as possible.
long start4 = System.currentTimeMillis();
VibrationStepConductor conductor4 = startThreadAndDispatcher(vibrationId4, effect4);
- conductor4.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ true);
+ conductor4.notifyCancelled(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+ /* immediate= */ true);
waitForCompletion();
long duration4 = System.currentTimeMillis() - start4;
@@ -1469,7 +1521,7 @@
fakeVibrator.getEffectSegments(vibrationId3));
// Effect4: cancelled quickly.
- verifyCallbacksTriggered(vibrationId4, Vibration.Status.CANCELLED_SUPERSEDED);
+ verifyCallbacksTriggered(vibrationId4, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
assertTrue("Tested duration=" + duration4, duration4 < 2000);
// Effect5: normal oneshot. Don't worry about amplitude, as effect4 may or may not have
@@ -1580,7 +1632,11 @@
}
private void verifyCallbacksTriggered(long vibrationId, Vibration.Status expectedStatus) {
- verify(mManagerHooks).onVibrationCompleted(eq(vibrationId), eq(expectedStatus));
+ verifyCallbacksTriggered(vibrationId, new Vibration.EndInfo(expectedStatus));
+ }
+
+ private void verifyCallbacksTriggered(long vibrationId, Vibration.EndInfo expectedEndInfo) {
+ verify(mManagerHooks).onVibrationCompleted(eq(vibrationId), eq(expectedEndInfo));
verify(mManagerHooks).onVibrationThreadReleased(vibrationId);
}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
new file mode 100644
index 0000000..c1ab1db
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 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.vibrator;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Handler;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/**
+ * Tests for {@link VibratorFrameworkStatsLogger}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:VibratorFrameworkStatsLoggerTest
+ */
+@Presubmit
+public class VibratorFrameworkStatsLoggerTest {
+
+ @Rule public MockitoRule rule = MockitoJUnit.rule();
+
+ private TestLooper mTestLooper;
+ private VibratorFrameworkStatsLogger mLogger;
+
+ @Before
+ public void setUp() {
+ mTestLooper = new TestLooper();
+ }
+
+ @Test
+ public void writeVibrationReportedAsync_afterMinInterval_writesRightAway() {
+ setUpLogger(/* minIntervalMillis= */ 10, /* queueMaxSize= */ 10);
+
+ VibrationStats.StatsInfo firstStats = newEmptyStatsInfo();
+ assertFalse(firstStats.isWritten());
+
+ mLogger.writeVibrationReportedAsync(firstStats);
+ mTestLooper.dispatchAll();
+ assertTrue(firstStats.isWritten());
+ }
+
+ @Test
+ public void writeVibrationReportedAsync_rightAfterLogging_schedulesToRunAfterRemainingDelay() {
+ setUpLogger(/* minIntervalMillis= */ 100, /* queueMaxSize= */ 10);
+
+ VibrationStats.StatsInfo firstStats = newEmptyStatsInfo();
+ VibrationStats.StatsInfo secondStats = newEmptyStatsInfo();
+ assertFalse(firstStats.isWritten());
+ assertFalse(secondStats.isWritten());
+
+ // Write first message at current SystemClock.uptimeMillis
+ mLogger.writeVibrationReportedAsync(firstStats);
+ mTestLooper.dispatchAll();
+ assertTrue(firstStats.isWritten());
+
+ // Second message is not written right away, it needs to wait the configured interval.
+ mLogger.writeVibrationReportedAsync(secondStats);
+ mTestLooper.dispatchAll();
+ assertFalse(secondStats.isWritten());
+
+ // Second message is written after delay passes.
+ mTestLooper.moveTimeForward(100);
+ mTestLooper.dispatchAll();
+ assertTrue(secondStats.isWritten());
+ }
+
+ @Test
+ public void writeVibrationReportedAsync_tooFast_logsUsingIntervalAndDropsMessagesFromQueue() {
+ setUpLogger(/* minIntervalMillis= */ 100, /* queueMaxSize= */ 2);
+
+ VibrationStats.StatsInfo firstStats = newEmptyStatsInfo();
+ VibrationStats.StatsInfo secondStats = newEmptyStatsInfo();
+ VibrationStats.StatsInfo thirdStats = newEmptyStatsInfo();
+
+ mLogger.writeVibrationReportedAsync(firstStats);
+ mLogger.writeVibrationReportedAsync(secondStats);
+ mLogger.writeVibrationReportedAsync(thirdStats);
+
+ // Only first message is logged.
+ mTestLooper.dispatchAll();
+ assertTrue(firstStats.isWritten());
+ assertFalse(secondStats.isWritten());
+ assertFalse(thirdStats.isWritten());
+
+ // Wait one interval to check only the second one is logged.
+ mTestLooper.moveTimeForward(100);
+ mTestLooper.dispatchAll();
+ assertTrue(secondStats.isWritten());
+ assertFalse(thirdStats.isWritten());
+
+ // Wait a long interval to check the third one was dropped and will never be logged.
+ mTestLooper.moveTimeForward(1_000);
+ mTestLooper.dispatchAll();
+ assertFalse(thirdStats.isWritten());
+ }
+
+ private void setUpLogger(int minIntervalMillis, int queueMaxSize) {
+ mLogger = new VibratorFrameworkStatsLogger(new Handler(mTestLooper.getLooper()),
+ minIntervalMillis, queueMaxSize);
+ }
+
+ private static VibrationStats.StatsInfo newEmptyStatsInfo() {
+ return new VibrationStats.StatsInfo(
+ 0, 0, 0, Vibration.Status.FINISHED, new VibrationStats(), 0L);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 8a96feb..36bec75 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -34,6 +34,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -75,11 +76,13 @@
import android.os.vibrator.VibrationEffectSegment;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
+import android.util.SparseBooleanArray;
import android.view.InputDevice;
import androidx.test.InstrumentationRegistry;
import com.android.internal.app.IBatteryStats;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.LocalServices;
@@ -148,6 +151,8 @@
private IInputManager mIInputManagerMock;
@Mock
private IBatteryStats mBatteryStatsMock;
+ @Mock
+ private VibratorFrameworkStatsLogger mVibratorFrameworkStatsLoggerMock;
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
@@ -233,6 +238,11 @@
}
@Override
+ VibratorFrameworkStatsLogger getFrameworkStatsLogger(Handler handler) {
+ return mVibratorFrameworkStatsLoggerMock;
+ }
+
+ @Override
VibratorController createVibratorController(int vibratorId,
VibratorController.OnVibrationCompleteListener listener) {
return mVibratorProviders.get(vibratorId)
@@ -806,11 +816,11 @@
service, TEST_TIMEOUT_MILLIS));
VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
- new long[]{10_000, 10_000}, new int[]{128, 255}, 1);
+ new long[]{10, 10}, new int[]{128, 255}, 1);
vibrate(service, repeatingEffect, NOTIFICATION_ATTRS);
// VibrationThread will start this vibration async, so wait before checking it started.
- assertTrue(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1,
+ assertTrue(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 2,
service, TEST_TIMEOUT_MILLIS));
// The second vibration should have recorded that the vibrators were turned on.
@@ -916,7 +926,11 @@
mockCapabilities(IVibratorManager.CAP_SYNC, IVibratorManager.CAP_PREPARE_COMPOSE);
mockVibrators(1, 2);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(1).setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_CLICK);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(2).setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_CLICK);
// Mock alarm intensity equals to default value to avoid scaling in this test.
setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY,
mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_ALARM));
@@ -1078,6 +1092,8 @@
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
IVibrator.CAP_COMPOSE_EFFECTS);
+ fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK,
+ VibrationEffect.Composition.PRIMITIVE_TICK);
VibratorManagerService service = createSystemReadyService();
vibrate(service, VibrationEffect.startComposition()
@@ -1380,6 +1396,373 @@
assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
}
+ @Test
+ public void frameworkStats_externalVibration_reportsAllMetrics() throws Exception {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ createSystemReadyService();
+
+ AudioAttributes audioAttrs = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_ALARM)
+ .build();
+
+ ExternalVibration vib = new ExternalVibration(UID, PACKAGE_NAME, audioAttrs,
+ mock(IExternalVibrationController.class));
+ mExternalVibratorService.onExternalVibrationStart(vib);
+
+ Thread.sleep(10);
+ mExternalVibratorService.onExternalVibrationStop(vib);
+
+ ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+ ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibrationReportedAsync(argumentCaptor.capture());
+
+ VibrationStats.StatsInfo statsInfo = argumentCaptor.getValue();
+ assertEquals(UID, statsInfo.uid);
+ assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL,
+ statsInfo.vibrationType);
+ assertEquals(VibrationAttributes.USAGE_ALARM, statsInfo.usage);
+ assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), statsInfo.status);
+ assertTrue(statsInfo.totalDurationMillis > 0);
+ assertTrue(
+ "Expected vibrator ON for at least 10ms, got " + statsInfo.vibratorOnMillis + "ms",
+ statsInfo.vibratorOnMillis >= 10);
+ assertEquals(2, statsInfo.halSetExternalControlCount);
+ }
+
+ @Test
+ public void frameworkStats_waveformVibration_reportsAllMetrics() throws Exception {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+ VibratorManagerService service = createSystemReadyService();
+ vibrateAndWaitUntilFinished(service,
+ VibrationEffect.createWaveform(new long[] {0, 10, 20, 10}, -1), RINGTONE_ATTRS);
+
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibratorStateOnAsync(eq(UID), anyLong());
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibratorStateOffAsync(eq(UID));
+
+ ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+ ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibrationReportedAsync(argumentCaptor.capture());
+
+ VibrationStats.StatsInfo metrics = argumentCaptor.getValue();
+ assertEquals(UID, metrics.uid);
+ assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE,
+ metrics.vibrationType);
+ assertEquals(VibrationAttributes.USAGE_RINGTONE, metrics.usage);
+ assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), metrics.status);
+ assertTrue("Total duration was too low, " + metrics.totalDurationMillis + "ms",
+ metrics.totalDurationMillis >= 20);
+ assertTrue("Vibrator ON duration was too low, " + metrics.vibratorOnMillis + "ms",
+ metrics.vibratorOnMillis >= 20);
+
+ // All unrelated metrics are empty.
+ assertEquals(0, metrics.repeatCount);
+ assertEquals(0, metrics.halComposeCount);
+ assertEquals(0, metrics.halComposePwleCount);
+ assertEquals(0, metrics.halPerformCount);
+ assertEquals(0, metrics.halSetExternalControlCount);
+ assertEquals(0, metrics.halCompositionSize);
+ assertEquals(0, metrics.halPwleSize);
+ assertNull(metrics.halSupportedCompositionPrimitivesUsed);
+ assertNull(metrics.halSupportedEffectsUsed);
+ assertNull(metrics.halUnsupportedCompositionPrimitivesUsed);
+ assertNull(metrics.halUnsupportedEffectsUsed);
+
+ // Accommodate for ramping off config that might add extra setAmplitudes.
+ assertEquals(2, metrics.halOnCount);
+ assertTrue(metrics.halOffCount > 0);
+ assertTrue(metrics.halSetAmplitudeCount >= 2);
+ }
+
+ @Test
+ public void frameworkStats_repeatingVibration_reportsAllMetrics() throws Exception {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+ VibratorManagerService service = createSystemReadyService();
+ vibrate(service, VibrationEffect.createWaveform(new long[] {10, 100}, 1), RINGTONE_ATTRS);
+
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibratorStateOnAsync(eq(UID), anyLong());
+
+ // Wait for at least one loop before cancelling it.
+ Thread.sleep(100);
+ service.cancelVibrate(VibrationAttributes.USAGE_RINGTONE, service);
+
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibratorStateOffAsync(eq(UID));
+
+ ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+ ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibrationReportedAsync(argumentCaptor.capture());
+
+ VibrationStats.StatsInfo metrics = argumentCaptor.getValue();
+ assertEquals(UID, metrics.uid);
+ assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED,
+ metrics.vibrationType);
+ assertEquals(VibrationAttributes.USAGE_RINGTONE, metrics.usage);
+ assertEquals(Vibration.Status.CANCELLED_BY_USER.getProtoEnumValue(), metrics.status);
+ assertTrue("Total duration was too low, " + metrics.totalDurationMillis + "ms",
+ metrics.totalDurationMillis >= 100);
+ assertTrue("Vibrator ON duration was too low, " + metrics.vibratorOnMillis + "ms",
+ metrics.vibratorOnMillis >= 100);
+
+ // All unrelated metrics are empty.
+ assertTrue(metrics.repeatCount > 0);
+ assertEquals(0, metrics.halComposeCount);
+ assertEquals(0, metrics.halComposePwleCount);
+ assertEquals(0, metrics.halPerformCount);
+ assertEquals(0, metrics.halSetExternalControlCount);
+ assertEquals(0, metrics.halCompositionSize);
+ assertEquals(0, metrics.halPwleSize);
+ assertNull(metrics.halSupportedCompositionPrimitivesUsed);
+ assertNull(metrics.halSupportedEffectsUsed);
+ assertNull(metrics.halUnsupportedCompositionPrimitivesUsed);
+ assertNull(metrics.halUnsupportedEffectsUsed);
+
+ // Accommodate for ramping off config that might add extra setAmplitudes.
+ assertTrue(metrics.halOnCount > 0);
+ assertTrue(metrics.halOffCount > 0);
+ assertTrue(metrics.halSetAmplitudeCount > 0);
+ }
+
+ @Test
+ public void frameworkStats_prebakedAndComposedVibrations_reportsAllMetrics() throws Exception {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ mVibratorProviders.get(1).setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_TICK);
+
+ VibratorManagerService service = createSystemReadyService();
+ vibrateAndWaitUntilFinished(service,
+ VibrationEffect.startComposition()
+ .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+ .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
+ .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK))
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .compose(),
+ ALARM_ATTRS);
+
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibratorStateOnAsync(eq(UID), anyLong());
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibratorStateOffAsync(eq(UID));
+
+ ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+ ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibrationReportedAsync(argumentCaptor.capture());
+
+ VibrationStats.StatsInfo metrics = argumentCaptor.getValue();
+ assertEquals(UID, metrics.uid);
+ assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE,
+ metrics.vibrationType);
+ assertEquals(VibrationAttributes.USAGE_ALARM, metrics.usage);
+ assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), metrics.status);
+
+ // At least 4 effect/primitive played, 20ms each, plus configured fallback.
+ assertTrue("Total duration was too low, " + metrics.totalDurationMillis + "ms",
+ metrics.totalDurationMillis >= 80);
+ assertTrue("Vibrator ON duration was too low, " + metrics.vibratorOnMillis + "ms",
+ metrics.vibratorOnMillis >= 80);
+
+ // Related metrics were collected.
+ assertEquals(2, metrics.halComposeCount); // TICK+TICK, then CLICK+CLICK
+ assertEquals(3, metrics.halPerformCount); // CLICK, TICK, then CLICK
+ assertEquals(4, metrics.halCompositionSize); // 2*TICK + 2*CLICK
+ // No repetitions in reported effect/primitive IDs.
+ assertArrayEquals(new int[] {VibrationEffect.Composition.PRIMITIVE_TICK},
+ metrics.halSupportedCompositionPrimitivesUsed);
+ assertArrayEquals(new int[] {VibrationEffect.Composition.PRIMITIVE_CLICK},
+ metrics.halUnsupportedCompositionPrimitivesUsed);
+ assertArrayEquals(new int[] {VibrationEffect.EFFECT_CLICK},
+ metrics.halSupportedEffectsUsed);
+ assertArrayEquals(new int[] {VibrationEffect.EFFECT_TICK},
+ metrics.halUnsupportedEffectsUsed);
+
+ // All unrelated metrics are empty.
+ assertEquals(0, metrics.repeatCount);
+ assertEquals(0, metrics.halComposePwleCount);
+ assertEquals(0, metrics.halSetExternalControlCount);
+ assertEquals(0, metrics.halPwleSize);
+
+ // Accommodate for ramping off config that might add extra setAmplitudes
+ // for the effect that plays the fallback instead of "perform".
+ assertTrue(metrics.halOnCount > 0);
+ assertTrue(metrics.halOffCount > 0);
+ assertTrue(metrics.halSetAmplitudeCount > 0);
+ }
+
+ @Test
+ public void frameworkStats_interruptingVibrations_reportsAllMetrics() throws Exception {
+ mockVibrators(1);
+ VibratorManagerService service = createSystemReadyService();
+
+ vibrate(service, VibrationEffect.createOneShot(1_000, 128), HAPTIC_FEEDBACK_ATTRS);
+
+ // VibrationThread will start this vibration async, so wait until vibration is triggered.
+ assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
+ service, TEST_TIMEOUT_MILLIS));
+
+ vibrateAndWaitUntilFinished(service, VibrationEffect.createOneShot(10, 255), ALARM_ATTRS);
+
+ ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+ ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS).times(2))
+ .writeVibrationReportedAsync(argumentCaptor.capture());
+
+ VibrationStats.StatsInfo touchMetrics = argumentCaptor.getAllValues().get(0);
+ assertEquals(UID, touchMetrics.uid);
+ assertEquals(VibrationAttributes.USAGE_TOUCH, touchMetrics.usage);
+ assertEquals(Vibration.Status.CANCELLED_SUPERSEDED.getProtoEnumValue(),
+ touchMetrics.status);
+ assertTrue(touchMetrics.endedBySameUid);
+ assertEquals(VibrationAttributes.USAGE_ALARM, touchMetrics.endedByUsage);
+ assertEquals(-1, touchMetrics.interruptedUsage);
+
+ VibrationStats.StatsInfo alarmMetrics = argumentCaptor.getAllValues().get(1);
+ assertEquals(UID, alarmMetrics.uid);
+ assertEquals(VibrationAttributes.USAGE_ALARM, alarmMetrics.usage);
+ assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), alarmMetrics.status);
+ assertFalse(alarmMetrics.endedBySameUid);
+ assertEquals(-1, alarmMetrics.endedByUsage);
+ assertEquals(VibrationAttributes.USAGE_TOUCH, alarmMetrics.interruptedUsage);
+ }
+
+ @Test
+ public void frameworkStats_ignoredVibration_reportsStatus() throws Exception {
+ setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
+ Vibrator.VIBRATION_INTENSITY_OFF);
+
+ mockVibrators(1);
+ VibratorManagerService service = createSystemReadyService();
+ mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+
+ // Haptic feedback ignored in low power state
+ vibrateAndWaitUntilFinished(service, VibrationEffect.createOneShot(100, 128),
+ HAPTIC_FEEDBACK_ATTRS);
+ // Ringtone vibration user settings are off
+ vibrateAndWaitUntilFinished(service, VibrationEffect.createOneShot(200, 128),
+ RINGTONE_ATTRS);
+
+ ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+ ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS).times(2))
+ .writeVibrationReportedAsync(argumentCaptor.capture());
+
+ VibrationStats.StatsInfo touchMetrics = argumentCaptor.getAllValues().get(0);
+ assertEquals(UID, touchMetrics.uid);
+ assertEquals(VibrationAttributes.USAGE_TOUCH, touchMetrics.usage);
+ assertEquals(Vibration.Status.IGNORED_FOR_POWER.getProtoEnumValue(), touchMetrics.status);
+
+ VibrationStats.StatsInfo ringtoneMetrics = argumentCaptor.getAllValues().get(1);
+ assertEquals(UID, ringtoneMetrics.uid);
+ assertEquals(VibrationAttributes.USAGE_RINGTONE, ringtoneMetrics.usage);
+ assertEquals(Vibration.Status.IGNORED_FOR_SETTINGS.getProtoEnumValue(),
+ ringtoneMetrics.status);
+
+ for (VibrationStats.StatsInfo metrics : argumentCaptor.getAllValues()) {
+ // Latencies are empty since vibrations never started
+ assertEquals(0, metrics.startLatencyMillis);
+ assertEquals(0, metrics.endLatencyMillis);
+ assertEquals(0, metrics.vibratorOnMillis);
+
+ // All unrelated metrics are empty.
+ assertEquals(0, metrics.repeatCount);
+ assertEquals(0, metrics.halComposeCount);
+ assertEquals(0, metrics.halComposePwleCount);
+ assertEquals(0, metrics.halOffCount);
+ assertEquals(0, metrics.halOnCount);
+ assertEquals(0, metrics.halPerformCount);
+ assertEquals(0, metrics.halSetExternalControlCount);
+ assertEquals(0, metrics.halCompositionSize);
+ assertEquals(0, metrics.halPwleSize);
+ assertNull(metrics.halSupportedCompositionPrimitivesUsed);
+ assertNull(metrics.halSupportedEffectsUsed);
+ assertNull(metrics.halUnsupportedCompositionPrimitivesUsed);
+ assertNull(metrics.halUnsupportedEffectsUsed);
+ }
+ }
+
+ @Test
+ public void frameworkStats_multiVibrators_reportsAllMetrics() throws Exception {
+ mockVibrators(1, 2);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(1).setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_TICK);
+ mVibratorProviders.get(2).setSupportedEffects(VibrationEffect.EFFECT_TICK);
+
+ VibratorManagerService service = createSystemReadyService();
+ vibrateAndWaitUntilFinished(service,
+ CombinedVibration.startParallel()
+ .addVibrator(1,
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+ .compose())
+ .addVibrator(2,
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK))
+ .combine(),
+ NOTIFICATION_ATTRS);
+
+ SparseBooleanArray expectedEffectsUsed = new SparseBooleanArray();
+ expectedEffectsUsed.put(VibrationEffect.EFFECT_TICK, true);
+
+ SparseBooleanArray expectedPrimitivesUsed = new SparseBooleanArray();
+ expectedPrimitivesUsed.put(VibrationEffect.Composition.PRIMITIVE_TICK, true);
+
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibratorStateOnAsync(eq(UID), anyLong());
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibratorStateOffAsync(eq(UID));
+
+ ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+ ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibrationReportedAsync(argumentCaptor.capture());
+
+ VibrationStats.StatsInfo metrics = argumentCaptor.getValue();
+ assertEquals(UID, metrics.uid);
+ assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE,
+ metrics.vibrationType);
+ assertEquals(VibrationAttributes.USAGE_NOTIFICATION, metrics.usage);
+ assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), metrics.status);
+ assertTrue(metrics.totalDurationMillis >= 20);
+
+ // vibratorOnMillis accumulates both vibrators, it's 20 for each constant.
+ assertEquals(40, metrics.vibratorOnMillis);
+
+ // Related metrics were collected.
+ assertEquals(1, metrics.halComposeCount);
+ assertEquals(1, metrics.halPerformCount);
+ assertEquals(1, metrics.halCompositionSize);
+ assertEquals(2, metrics.halOffCount);
+ assertArrayEquals(new int[] {VibrationEffect.Composition.PRIMITIVE_TICK},
+ metrics.halSupportedCompositionPrimitivesUsed);
+ assertArrayEquals(new int[] {VibrationEffect.EFFECT_TICK},
+ metrics.halSupportedEffectsUsed);
+
+ // All unrelated metrics are empty.
+ assertEquals(0, metrics.repeatCount);
+ assertEquals(0, metrics.halComposePwleCount);
+ assertEquals(0, metrics.halOnCount);
+ assertEquals(0, metrics.halSetAmplitudeCount);
+ assertEquals(0, metrics.halSetExternalControlCount);
+ assertEquals(0, metrics.halPwleSize);
+ assertNull(metrics.halUnsupportedCompositionPrimitivesUsed);
+ assertNull(metrics.halUnsupportedEffectsUsed);
+ }
+
private VibrationEffectSegment expectedPrebaked(int effectId) {
return expectedPrebaked(effectId, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
}
@@ -1429,6 +1812,20 @@
mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
}
+ private void vibrateAndWaitUntilFinished(VibratorManagerService service, VibrationEffect effect,
+ VibrationAttributes attrs) throws InterruptedException {
+ vibrateAndWaitUntilFinished(service, CombinedVibration.createParallel(effect), attrs);
+ }
+
+ private void vibrateAndWaitUntilFinished(VibratorManagerService service,
+ CombinedVibration effect, VibrationAttributes attrs) throws InterruptedException {
+ Vibration vib =
+ service.vibrateInternal(UID, PACKAGE_NAME, effect, attrs, "some reason", service);
+ if (vib != null) {
+ vib.waitForEnd();
+ }
+ }
+
private void vibrate(VibratorManagerService service, VibrationEffect effect,
VibrationAttributes attrs) {
vibrate(service, CombinedVibration.createParallel(effect), attrs);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 44ca9f4..40cda34 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -150,6 +150,7 @@
import android.graphics.Color;
import android.graphics.drawable.Icon;
import android.media.AudioManager;
+import android.media.IRingtonePlayer;
import android.media.session.MediaSession;
import android.net.Uri;
import android.os.Binder;
@@ -7738,6 +7739,34 @@
}
@Test
+ public void testOnBubbleMetadataChangedToSuppressNotification_soundStopped()
+ throws RemoteException {
+ IRingtonePlayer mockPlayer = mock(IRingtonePlayer.class);
+ when(mAudioManager.getRingtonePlayer()).thenReturn(mockPlayer);
+ // Set up volume to be above 0 for the sound to actually play
+ when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10);
+
+ setUpPrefsForBubbles(PKG, mUid,
+ true /* global */,
+ BUBBLE_PREFERENCE_ALL /* app */,
+ true /* channel */);
+
+ // Post a bubble notification
+ NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "tag");
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+
+ // Test: suppress notification via bubble metadata update
+ mService.mNotificationDelegate.onBubbleMetadataFlagChanged(nr.getKey(),
+ Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
+ waitForIdle();
+
+ // Check audio is stopped
+ verify(mockPlayer).stopAsync();
+ }
+
+ @Test
public void testGrantInlineReplyUriPermission_recordExists() throws Exception {
int userId = UserManager.isHeadlessSystemUserMode()
? UserHandle.getUserId(UID_HEADLESS)
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
index 5a4ce5da..3a6c0eb 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
@@ -20,10 +20,8 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
@@ -75,9 +73,6 @@
@Test
public void testScheduleJob() {
- // if asked, the job doesn't currently exist yet
- when(mMockJobScheduler.getPendingJob(anyInt())).thenReturn(null);
-
final int rescheduleTimeMillis = 350; // arbitrary number
// attempt to schedule the job
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
index 8ac729e..c7905a0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
@@ -8,7 +8,7 @@
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distriZenbuted on an "AS IS" BASIS,
+ * 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.
@@ -397,10 +397,10 @@
Bundle inputWrong = makeExtrasBundleWithPeople(new String[]{"mailto:nope"});
assertTrue(ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- inputMatches, null, 0, 0));
+ inputMatches, null, 0, 0, 0));
assertFalse(ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- inputWrong, null, 0, 0));
+ inputWrong, null, 0, 0, 0));
}
@Test
@@ -428,19 +428,19 @@
assertTrue("identical numbers should match",
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- identical, null, 0, 0));
+ identical, null, 0, 0, 0));
assertTrue("equivalent but non-identical numbers should match",
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- same, null, 0, 0));
+ same, null, 0, 0, 0));
assertFalse("non-equivalent numbers should not match",
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- different, null, 0, 0));
+ different, null, 0, 0, 0));
assertFalse("non-tel strings should not match",
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- garbage, null, 0, 0));
+ garbage, null, 0, 0, 0));
}
@Test
@@ -469,23 +469,23 @@
assertTrue("same number 1 should match",
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- same1, null, 0, 0));
+ same1, null, 0, 0, 0));
assertTrue("same number 2 should match",
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- same2, null, 0, 0));
+ same2, null, 0, 0, 0));
assertTrue("same number 3 should match",
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- same3, null, 0, 0));
+ same3, null, 0, 0, 0));
assertFalse("different number 1 should not match",
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- different1, null, 0, 0));
+ different1, null, 0, 0, 0));
assertFalse("different number 2 should not match",
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- different2, null, 0, 0));
+ different2, null, 0, 0, 0));
}
@Test
@@ -516,14 +516,14 @@
assertTrue("contact number 1 should match",
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- tel1, null, 0, 0));
+ tel1, null, 0, 0, 0));
assertTrue("contact number 2 should match",
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- tel2, null, 0, 0));
+ tel2, null, 0, 0, 0));
assertFalse("different number should not match",
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- different, null, 0, 0));
+ different, null, 0, 0, 0));
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 5146616..15d1a3c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2314,6 +2314,8 @@
assertEquals(launchCookie, activity2.mLaunchCookie);
assertNull(activity1.mLaunchCookie);
+ activity2.makeFinishingLocked();
+ assertTrue(activity1.getTask().getTaskInfo().launchCookies.contains(launchCookie));
}
private void verifyProcessInfoUpdate(ActivityRecord activity, State state,
@@ -2464,6 +2466,7 @@
activity.addWindow(appWindow);
spyOn(appWindow);
doNothing().when(appWindow).onStartFreezingScreen();
+ doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
// Set initial orientation and update.
performRotation(displayRotation, Surface.ROTATION_90);
@@ -2472,8 +2475,6 @@
// Update the rotation to perform 180 degree rotation and check that resize was reported.
performRotation(displayRotation, Surface.ROTATION_270);
assertTrue(appWindow.mResizeReported);
-
- appWindow.removeImmediately();
}
private void performRotation(DisplayRotation spiedRotation, int rotationToReport) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 28d2aa1..7a73f08 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1832,19 +1832,18 @@
@Test
public void testRemoteRotation() {
- DisplayContent dc = createNewDisplay();
-
+ final DisplayContent dc = mDisplayContent;
final DisplayRotation dr = dc.getDisplayRotation();
- doCallRealMethod().when(dr).updateRotationUnchecked(anyBoolean());
+ spyOn(dr);
// Rotate 180 degree so the display doesn't have configuration change. This condition is
// used for the later verification of stop-freezing (without setting mWaitingForConfig).
doReturn((dr.getRotation() + 2) % 4).when(dr).rotationForOrientation(anyInt(), anyInt());
final boolean[] continued = new boolean[1];
- doAnswer(
- invocation -> {
- continued[0] = true;
- return true;
- }).when(dc).updateDisplayOverrideConfigurationLocked();
+ doAnswer(invocation -> {
+ continued[0] = true;
+ mAtm.addWindowLayoutReasons(ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED);
+ return true;
+ }).when(dc).updateDisplayOverrideConfigurationLocked();
final boolean[] called = new boolean[1];
mWm.mDisplayChangeController =
new IDisplayChangeWindowController.Stub() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 05cc0cf..46b4b76 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -24,6 +24,7 @@
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
@@ -41,6 +42,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -52,16 +54,20 @@
import android.graphics.Rect;
import android.os.Binder;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
+import android.util.MergedConfiguration;
import android.view.IWindowSessionCallback;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.InsetsVisibilities;
+import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowManager;
+import android.window.ClientWindowFrames;
import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
@@ -166,6 +172,32 @@
}
@Test
+ public void testRelayoutExitingWindow() {
+ final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, "appWin");
+ final WindowSurfaceController surfaceController = mock(WindowSurfaceController.class);
+ doReturn(true).when(surfaceController).hasSurface();
+ spyOn(win);
+ doReturn(true).when(win).isExitAnimationRunningSelfOrParent();
+ win.mWinAnimator.mSurfaceController = surfaceController;
+ win.mViewVisibility = View.VISIBLE;
+ win.mHasSurface = true;
+ win.mActivityRecord.mAppStopped = true;
+ win.mActivityRecord.mVisibleRequested = false;
+ win.mActivityRecord.setVisible(false);
+ mWm.mWindowMap.put(win.mClient.asBinder(), win);
+ final int w = 100;
+ final int h = 200;
+ mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.GONE, 0, 0, 0,
+ new ClientWindowFrames(), new MergedConfiguration(), new SurfaceControl(),
+ new InsetsState(), new InsetsSourceControl[0], new Bundle());
+ // Because the window is already invisible, it doesn't need to apply exiting animation
+ // and WMS#tryStartExitingAnimation() will destroy the surface directly.
+ assertFalse(win.mAnimatingExit);
+ assertFalse(win.mHasSurface);
+ assertNull(win.mWinAnimator.mSurfaceController);
+ }
+
+ @Test
public void testMoveWindowTokenToDisplay_NullToken_DoNothing() {
mWm.moveWindowTokenToDisplay(null, mDisplayContent.getDisplayId());
diff --git a/startop/view_compiler/dex_builder_test/Android.bp b/startop/view_compiler/dex_builder_test/Android.bp
index 2048964..bcba2fe 100644
--- a/startop/view_compiler/dex_builder_test/Android.bp
+++ b/startop/view_compiler/dex_builder_test/Android.bp
@@ -46,7 +46,6 @@
android_test {
name: "dex-builder-test",
srcs: [
- "src/android/startop/test/ApkLayoutCompilerTest.java",
"src/android/startop/test/DexBuilderTest.java",
"src/android/startop/test/LayoutCompilerTest.java",
"src/android/startop/test/TestClass.java",
diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/ApkLayoutCompilerTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/ApkLayoutCompilerTest.java
deleted file mode 100644
index 230e8df..0000000
--- a/startop/view_compiler/dex_builder_test/src/android/startop/test/ApkLayoutCompilerTest.java
+++ /dev/null
@@ -1,57 +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.startop.test;
-
-import android.content.Context;
-import androidx.test.InstrumentationRegistry;
-import android.view.View;
-import dalvik.system.PathClassLoader;
-import java.lang.reflect.Method;
-import org.junit.Assert;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-public class ApkLayoutCompilerTest {
- static ClassLoader loadDexFile() throws Exception {
- Context context = InstrumentationRegistry.getTargetContext();
- return new PathClassLoader(context.getCodeCacheDir() + "/compiled_view.dex",
- ClassLoader.getSystemClassLoader());
- }
-
- @BeforeClass
- public static void setup() throws Exception {
- // ensure PackageManager has compiled the layouts.
- Process pm = Runtime.getRuntime().exec("pm compile --compile-layouts android.startop.test");
- pm.waitFor();
- }
-
- @Test
- public void loadAndInflateLayout1() throws Exception {
- ClassLoader dex_file = loadDexFile();
- Class compiled_view = dex_file.loadClass("android.startop.test.CompiledView");
- Method layout1 = compiled_view.getMethod("layout1", Context.class, int.class);
- Context context = InstrumentationRegistry.getTargetContext();
- layout1.invoke(null, context, R.layout.layout1);
- }
-
- @Test
- public void loadAndInflateLayout2() throws Exception {
- ClassLoader dex_file = loadDexFile();
- Class compiled_view = dex_file.loadClass("android.startop.test.CompiledView");
- Method layout2 = compiled_view.getMethod("layout2", Context.class, int.class);
- Context context = InstrumentationRegistry.getTargetContext();
- layout2.invoke(null, context, R.layout.layout2);
- }
-}
diff --git a/telephony/OWNERS b/telephony/OWNERS
index 9681ee8..e0c5f8f 100644
--- a/telephony/OWNERS
+++ b/telephony/OWNERS
@@ -10,3 +10,10 @@
chinmayd@google.com
amruthr@google.com
sasindran@google.com
+
+# Temporarily reduced the owner during refactoring
+per-file SubscriptionManager.java=set noparent
+per-file SubscriptionManager.java=jackyu@google.com,amruthr@google.com
+per-file SubscriptionInfo.java=set noparent
+per-file SubscriptionInfo.java=jackyu@google.com,amruthr@google.com
+
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 5ad4edf..4af8cde 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1175,14 +1175,55 @@
"carrier_data_call_permanent_failure_strings";
/**
- * Default APN types that are metered by the carrier
- * @hide
+ * A string array indicating the default APN types that are metered by the carrier.
+ *
+ * The string in the array is the name of the APN type. For example, "default" for
+ * {@link ApnSetting#TYPE_DEFAULT}, "mms" for {@link ApnSetting#TYPE_MMS}, etc.
+ *
+ * The default value is {@code {"default", "mms", "dun", "supl"}}.
+ *
+ * @see ApnSetting#TYPE_DEFAULT
+ * @see ApnSetting#TYPE_MMS
+ * @see ApnSetting#TYPE_SUPL
+ * @see ApnSetting#TYPE_DUN
+ * @see ApnSetting#TYPE_HIPRI
+ * @see ApnSetting#TYPE_FOTA
+ * @see ApnSetting#TYPE_IMS
+ * @see ApnSetting#TYPE_CBS
+ * @see ApnSetting#TYPE_IA
+ * @see ApnSetting#TYPE_EMERGENCY
+ * @see ApnSetting#TYPE_MCX
+ * @see ApnSetting#TYPE_XCAP
+ * @see ApnSetting#TYPE_BIP
+ * @see ApnSetting#TYPE_VSIM
+ * @see ApnSetting#TYPE_ENTERPRISE
*/
public static final String KEY_CARRIER_METERED_APN_TYPES_STRINGS =
"carrier_metered_apn_types_strings";
+
/**
- * Default APN types that are roaming-metered by the carrier
- * @hide
+ * A string array indicating the default APN types that are roaming-metered by the carrier.
+ *
+ * The string in the array is the name of the APN type. For example, "default" for
+ * {@link ApnSetting#TYPE_DEFAULT}, "mms" for {@link ApnSetting#TYPE_MMS}, etc.
+ *
+ * The default value is {@code {"default", "mms", "dun", "supl"}}.
+ *
+ * @see ApnSetting#TYPE_DEFAULT
+ * @see ApnSetting#TYPE_MMS
+ * @see ApnSetting#TYPE_SUPL
+ * @see ApnSetting#TYPE_DUN
+ * @see ApnSetting#TYPE_HIPRI
+ * @see ApnSetting#TYPE_FOTA
+ * @see ApnSetting#TYPE_IMS
+ * @see ApnSetting#TYPE_CBS
+ * @see ApnSetting#TYPE_IA
+ * @see ApnSetting#TYPE_EMERGENCY
+ * @see ApnSetting#TYPE_MCX
+ * @see ApnSetting#TYPE_XCAP
+ * @see ApnSetting#TYPE_BIP
+ * @see ApnSetting#TYPE_VSIM
+ * @see ApnSetting#TYPE_ENTERPRISE
*/
public static final String KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS =
"carrier_metered_roaming_apn_types_strings";
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 2453f3c..c9a63c6 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -9088,7 +9088,7 @@
* @param executor The executor through which the callback should be invoked. Since the scan
* request may trigger multiple callbacks and they must be invoked in the same order as
* they are received by the platform, the user should provide an executor which executes
- * tasks one at a time in serial order. For example AsyncTask.SERIAL_EXECUTOR.
+ * tasks one at a time in serial order.
* @param callback Returns network scan results or errors.
* @return A NetworkScan obj which contains a callback which can be used to stop the scan.
*/
@@ -9132,7 +9132,7 @@
* @param executor The executor through which the callback should be invoked. Since the scan
* request may trigger multiple callbacks and they must be invoked in the same order as
* they are received by the platform, the user should provide an executor which executes
- * tasks one at a time in serial order. For example AsyncTask.SERIAL_EXECUTOR.
+ * tasks one at a time in serial order.
* @param callback Returns network scan results or errors.
* @return A NetworkScan obj which contains a callback which can be used to stop the scan.
*/
@@ -16444,7 +16444,12 @@
* the appropriate callback method on the callback object and passes the current (updated)
* values.
* <p>
- *
+ * Note: Be aware of the permission requirements stated on the {@link TelephonyCallback}
+ * listeners you implement. Your application must be granted these permissions in order to
+ * register a {@link TelephonyCallback} which requires them; a {@link SecurityException} will be
+ * thrown if you do not hold the required permissions for all {@link TelephonyCallback}
+ * listeners you implement.
+ * <p>
* If this TelephonyManager object has been created with {@link #createForSubscriptionId},
* applies to the given subId. Otherwise, applies to
* {@link SubscriptionManager#getDefaultSubscriptionId()}. To register events for multiple
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index 5e21252..472a0fa 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -43,6 +43,19 @@
}
/**
+ * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the start
+ * and end of the WM trace
+ */
+fun FlickerTestParameter.navBarWindowIsVisibleAtStartAndEnd() {
+ assertWmStart {
+ this.isAboveAppWindowVisible(ComponentMatcher.NAV_BAR)
+ }
+ assertWmEnd {
+ this.isAboveAppWindowVisible(ComponentMatcher.NAV_BAR)
+ }
+}
+
+/**
* Checks that [ComponentMatcher.TASK_BAR] window is visible and above the app windows in
* all WM trace entries
*/
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
index 457e973..a8c0a0b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
@@ -17,14 +17,18 @@
package com.android.server.wm.flicker.ime
import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
+import com.android.server.wm.traces.common.ComponentMatcher
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -65,4 +69,20 @@
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Ignore("Nav bar window becomes invisible during quick switch")
+ @Test
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+ /**
+ * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the start
+ * and end of the WM trace
+ */
+ @Presubmit
+ @Test
+ fun navBarWindowIsVisibleAtStartAndEnd() {
+ Assume.assumeFalse(testSpec.isTablet)
+ testSpec.navBarWindowIsVisibleAtStartAndEnd()
+ }
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
index e007fe3..2607ee5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
@@ -17,14 +17,18 @@
package com.android.server.wm.flicker.quickswitch
import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
+import com.android.server.wm.traces.common.ComponentMatcher
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -76,4 +80,20 @@
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Ignore("Nav bar window becomes invisible during quick switch")
+ @Test
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+ /**
+ * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the start
+ * and end of the WM trace
+ */
+ @Presubmit
+ @Test
+ fun navBarWindowIsVisibleAtStartAndEnd() {
+ Assume.assumeFalse(testSpec.isTablet)
+ testSpec.navBarWindowIsVisibleAtStartAndEnd()
+ }
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
index 6f78ba8..27ae125 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
@@ -17,14 +17,18 @@
package com.android.server.wm.flicker.quickswitch
import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
+import com.android.server.wm.traces.common.ComponentMatcher
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -66,4 +70,20 @@
@FlakyTest(bugId = 228009808)
@Test
override fun endsWithApp2BeingOnTop() = super.endsWithApp2BeingOnTop()
+
+ /** {@inheritDoc} */
+ @Ignore("Nav bar window becomes invisible during quick switch")
+ @Test
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+ /**
+ * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the start
+ * and end of the WM trace
+ */
+ @Presubmit
+ @Test
+ fun navBarWindowIsVisibleAtStartAndEnd() {
+ Assume.assumeFalse(testSpec.isTablet)
+ testSpec.navBarWindowIsVisibleAtStartAndEnd()
+ }
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index 510043b..c79b552 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -29,9 +29,12 @@
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
import com.android.server.wm.traces.common.ComponentMatcher
import com.android.server.wm.traces.common.Rect
+import org.junit.Assume
import org.junit.FixMethodOrder
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -296,6 +299,22 @@
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ /** {@inheritDoc} */
+ @Ignore("Nav bar window becomes invisible during quick switch")
+ @Test
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+ /**
+ * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the start
+ * and end of the WM trace
+ */
+ @Presubmit
+ @Test
+ fun navBarWindowIsVisibleAtStartAndEnd() {
+ Assume.assumeFalse(testSpec.isTablet)
+ testSpec.navBarWindowIsVisibleAtStartAndEnd()
+ }
+
companion object {
/** {@inheritDoc} */
private var startDisplayBounds = Rect.EMPTY
diff --git a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
index 1664746..a2842b6 100644
--- a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
+++ b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
@@ -22,6 +22,7 @@
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.os.Bundle;
+import android.os.Trace;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
@@ -30,9 +31,9 @@
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
-import android.os.Trace;
import android.view.Window;
import android.view.WindowManager;
+
import java.math.RoundingMode;
import java.text.DecimalFormat;
@@ -280,6 +281,17 @@
WindowManager.LayoutParams params = w.getAttributes();
int modeIndex = (mCurrentModeIndex + 1) % mDisplayModes.length;
+ while (modeIndex != mCurrentModeIndex) {
+ // skip modes with different resolutions
+ Mode currentMode = mDisplayModes[mCurrentModeIndex];
+ Mode nextMode = mDisplayModes[modeIndex];
+ if (currentMode.getPhysicalHeight() == nextMode.getPhysicalHeight()
+ && currentMode.getPhysicalWidth() == nextMode.getPhysicalWidth()) {
+ break;
+ }
+ modeIndex = (modeIndex + 1) % mDisplayModes.length;
+ }
+
params.preferredDisplayModeId = mDisplayModes[modeIndex].getModeId();
w.setAttributes(params);