diff options
641 files changed, 13961 insertions, 12041 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index 76d1935be563..1be07fdfcceb 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -79,6 +79,7 @@ import android.os.ShellCommand; import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; +import android.os.WearModeManagerInternal; import android.provider.DeviceConfig; import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; @@ -126,6 +127,7 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.function.Consumer; import java.util.stream.Collectors; /** @@ -373,10 +375,9 @@ public class DeviceIdleController extends SystemService @GuardedBy("this") private boolean mBatterySaverEnabled; @GuardedBy("this") - private boolean mIsOffBody; + private boolean mModeManagerRequestedQuickDoze; @GuardedBy("this") - private boolean mForceBodyState; - private Sensor mOffBodySensor; + private boolean mForceModeManagerQuickDozeRequest; /** Time in the elapsed realtime timebase when this listener last received a motion event. */ @GuardedBy("this") @@ -435,7 +436,7 @@ public class DeviceIdleController extends SystemService private static final int ACTIVE_REASON_FORCED = 6; private static final int ACTIVE_REASON_ALARM = 7; private static final int ACTIVE_REASON_EMERGENCY_CALL = 8; - private static final int ACTIVE_REASON_ONBODY = 9; + private static final int ACTIVE_REASON_MODE_MANAGER = 9; @VisibleForTesting static String stateToString(int state) { @@ -832,64 +833,35 @@ public class DeviceIdleController extends SystemService } } - /** - * LowLatencyOffBodyListener monitors if a device is on body or off body. - */ @VisibleForTesting - final class LowLatencyOffBodyListener implements SensorEventListener { + class ModeManagerQuickDozeRequestConsumer implements Consumer<Boolean> { @Override - public void onSensorChanged(SensorEvent event) { - if (DEBUG) { - Slog.d(TAG, "LowLatencyOffBodyListener detects onSensorChanged event, values are: " - + Arrays.toString(event.values)); - } - if (event.values == null || event.values.length == 0) { - // The event returned should contain a single value to indicate off-body state. - // No value indicates something went wrong. Take no action and log an error. - Slog.e(TAG, - "LowLatencyOffBodyListener detects onSensorChanged event but no event " - + "value returns."); - return; - } + public void accept(Boolean enabled) { + Slog.d(TAG, "Mode manager quick doze request: " + enabled); synchronized (DeviceIdleController.this) { - final boolean isOffBody = (event.values[0] == 0); - if (!mForceBodyState && mIsOffBody != isOffBody) { - // Only consider the sensor value change when mForceBodyState is false, which - // is used to enforce the mIsOffBody to be set by the adb shell command. - mIsOffBody = isOffBody; - onOffBodyChangedLocked(); + if (!mForceModeManagerQuickDozeRequest + && mModeManagerRequestedQuickDoze != enabled) { + mModeManagerRequestedQuickDoze = enabled; + onModeManagerRequestChangedLocked(); } } } @GuardedBy("DeviceIdleController.this") - public void onOffBodyChangedLocked() { - // Get into quick doze faster when the device is off body instead of taking + public void onModeManagerRequestChangedLocked() { + // Get into quick doze faster when mode manager requests instead of taking // traditional multi-stage approach. updateQuickDozeFlagLocked(); - if (!mIsOffBody && !mBatterySaverEnabled) { - mActiveReason = ACTIVE_REASON_ONBODY; - becomeActiveLocked("onbody", Process.myUid()); + if (!mModeManagerRequestedQuickDoze && !mBatterySaverEnabled) { + mActiveReason = ACTIVE_REASON_MODE_MANAGER; + becomeActiveLocked("mode_manager", Process.myUid()); } } - - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) {} - - public void registerLocked() { - mOffBodySensor = - mSensorManager.getDefaultSensor(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT, true); - if (mOffBodySensor == null) { - Slog.w(TAG, "Body sensor is NULL, unable to register mOffBodySensor."); - return; - } - mSensorManager.registerListener(this, mOffBodySensor, - SensorManager.SENSOR_DELAY_NORMAL); - } } @VisibleForTesting - final LowLatencyOffBodyListener mLowLatencyOffBodyListener = new LowLatencyOffBodyListener(); + final ModeManagerQuickDozeRequestConsumer mModeManagerQuickDozeRequestConsumer = + new ModeManagerQuickDozeRequestConsumer(); @VisibleForTesting final class MotionListener extends TriggerEventListener @@ -1052,7 +1024,7 @@ public class DeviceIdleController extends SystemService */ private static final String KEY_WAIT_FOR_UNLOCK = "wait_for_unlock"; private static final String KEY_USE_WINDOW_ALARMS = "use_window_alarms"; - private static final String KEY_USE_BODY_SENSOR = "use_body_sensor"; + private static final String KEY_USE_MODE_MANAGER = "use_mode_manager"; private long mDefaultFlexTimeShort = !COMPRESS_TIME ? 60 * 1000L : 5 * 1000L; @@ -1112,7 +1084,7 @@ public class DeviceIdleController extends SystemService private long mDefaultNotificationAllowlistDurationMs = 30 * 1000L; private boolean mDefaultWaitForUnlock = true; private boolean mDefaultUseWindowAlarms = true; - private boolean mDefaultUseBodySensor = false; + private boolean mDefaultUseModeManager = false; /** * A somewhat short alarm window size that we will tolerate for various alarm timings. @@ -1356,7 +1328,7 @@ public class DeviceIdleController extends SystemService /** * Whether to use an on/off body signal to affect state transition policy. */ - public boolean USE_BODY_SENSOR = mDefaultUseBodySensor; + public boolean USE_MODE_MANAGER = mDefaultUseModeManager; private final boolean mSmallBatteryDevice; @@ -1464,8 +1436,8 @@ public class DeviceIdleController extends SystemService com.android.internal.R.bool.device_idle_wait_for_unlock); mDefaultUseWindowAlarms = res.getBoolean( com.android.internal.R.bool.device_idle_use_window_alarms); - mDefaultUseBodySensor = res.getBoolean( - com.android.internal.R.bool.device_idle_use_body_sensor); + mDefaultUseModeManager = res.getBoolean( + com.android.internal.R.bool.device_idle_use_mode_manager); FLEX_TIME_SHORT = mDefaultFlexTimeShort; LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = mDefaultLightIdleAfterInactiveTimeout; @@ -1499,7 +1471,7 @@ public class DeviceIdleController extends SystemService NOTIFICATION_ALLOWLIST_DURATION_MS = mDefaultNotificationAllowlistDurationMs; WAIT_FOR_UNLOCK = mDefaultWaitForUnlock; USE_WINDOW_ALARMS = mDefaultUseWindowAlarms; - USE_BODY_SENSOR = mDefaultUseBodySensor; + USE_MODE_MANAGER = mDefaultUseModeManager; } private long getTimeout(long defTimeout, long compTimeout) { @@ -1661,9 +1633,9 @@ public class DeviceIdleController extends SystemService USE_WINDOW_ALARMS = properties.getBoolean( KEY_USE_WINDOW_ALARMS, mDefaultUseWindowAlarms); break; - case KEY_USE_BODY_SENSOR: - USE_BODY_SENSOR = properties.getBoolean( - KEY_USE_BODY_SENSOR, mDefaultUseBodySensor); + case KEY_USE_MODE_MANAGER: + USE_MODE_MANAGER = properties.getBoolean( + KEY_USE_MODE_MANAGER, mDefaultUseModeManager); break; default: Slog.e(TAG, "Unknown configuration key: " + name); @@ -1802,8 +1774,8 @@ public class DeviceIdleController extends SystemService pw.print(" "); pw.print(KEY_USE_WINDOW_ALARMS); pw.print("="); pw.println(USE_WINDOW_ALARMS); - pw.print(" "); pw.print(KEY_USE_BODY_SENSOR); pw.print("="); - pw.println(USE_BODY_SENSOR); + pw.print(" "); pw.print(KEY_USE_MODE_MANAGER); pw.print("="); + pw.println(USE_MODE_MANAGER); } } @@ -2668,8 +2640,15 @@ public class DeviceIdleController extends SystemService mPowerSaveWhitelistAllAppIdArray, mPowerSaveWhitelistExceptIdleAppIdArray); mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray); - if (mConstants.USE_BODY_SENSOR) { - mLowLatencyOffBodyListener.registerLocked(); + if (mConstants.USE_MODE_MANAGER) { + WearModeManagerInternal modeManagerInternal = LocalServices.getService( + WearModeManagerInternal.class); + if (modeManagerInternal != null) { + modeManagerInternal.addActiveStateChangeListener( + WearModeManagerInternal.QUICK_DOZE_REQUEST_IDENTIFIER, + AppSchedulingModuleThread.getExecutor(), + mModeManagerQuickDozeRequestConsumer); + } } mLocalPowerManager.registerLowPowerModeObserver(ServiceType.QUICK_DOZE, state -> { @@ -3374,9 +3353,10 @@ public class DeviceIdleController extends SystemService /** Calls to {@link #updateQuickDozeFlagLocked(boolean)} by considering appropriate signals. */ @GuardedBy("this") private void updateQuickDozeFlagLocked() { - if (mConstants.USE_BODY_SENSOR) { - // Only disable the quick doze flag when the device is on body and battery saver is off. - updateQuickDozeFlagLocked(mIsOffBody || mBatterySaverEnabled); + if (mConstants.USE_MODE_MANAGER) { + // Only disable the quick doze flag when mode manager request is false and + // battery saver is off. + updateQuickDozeFlagLocked(mModeManagerRequestedQuickDoze || mBatterySaverEnabled); } else { updateQuickDozeFlagLocked(mBatterySaverEnabled); } @@ -4482,7 +4462,7 @@ public class DeviceIdleController extends SystemService pw.println(" unforce"); pw.println( " Resume normal functioning after force-idle or force-inactive or " - + "force-offbody or force-onbody."); + + "force-modemanager-quickdoze."); pw.println(" get [light|deep|force|screen|charging|network|offbody|forcebodystate]"); pw.println(" Retrieve the current given state."); pw.println(" disable [light|deep|all]"); @@ -4517,14 +4497,9 @@ public class DeviceIdleController extends SystemService + "and any [-d] is ignored"); pw.println(" motion"); pw.println(" Simulate a motion event to bring the device out of deep doze"); - pw.println(" force-offbody"); - pw.println( - " Simulate a low latency body sensor detecting a device is offbody. " - + "mForceBodyState will be set to true to ignore body sensor reading."); - pw.println(" force-onbody"); - pw.println( - " Simulate a low latency body sensor detecting a device is onbody. " - + "mForceBodyState will be set to true to ignore body sensor reading."); + pw.println(" force-modemanager-quickdoze [true|false]"); + pw.println(" Simulate mode manager request to enable (true) or disable (false) " + + "quick doze. Mode manager changes will be ignored until unforce is called."); } class Shell extends ShellCommand { @@ -4656,8 +4631,9 @@ public class DeviceIdleController extends SystemService pw.print(lightStateToString(mLightState)); pw.print(", deep state: "); pw.println(stateToString(mState)); - mForceBodyState = false; - pw.println("mForceBodyState: " + mForceBodyState); + mForceModeManagerQuickDozeRequest = false; + pw.println("mForceModeManagerQuickDozeRequest: " + + mForceModeManagerQuickDozeRequest); } finally { Binder.restoreCallingIdentity(token); } @@ -4678,8 +4654,12 @@ public class DeviceIdleController extends SystemService case "screen": pw.println(mScreenOn); break; case "charging": pw.println(mCharging); break; case "network": pw.println(mNetworkConnected); break; - case "offbody": pw.println(mIsOffBody); break; - case "forcebodystate": pw.println(mForceBodyState); break; + case "modemanagerquick": + pw.println(mModeManagerRequestedQuickDoze); + break; + case "forcemodemanagerquick": + pw.println(mForceModeManagerQuickDozeRequest); + break; default: pw.println("Unknown get option: " + arg); break; } } finally { @@ -4976,35 +4956,31 @@ public class DeviceIdleController extends SystemService Binder.restoreCallingIdentity(token); } } - } else if ("force-offbody".equals(cmd)) { - getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, - null); - synchronized (DeviceIdleController.this) { - final long token = Binder.clearCallingIdentity(); - try { - mForceBodyState = true; - pw.println("mForceBodyState: " + mForceBodyState); - mIsOffBody = true; - pw.println("mIsOffBody: " + mIsOffBody); - mLowLatencyOffBodyListener.onOffBodyChangedLocked(); - } finally { - Binder.restoreCallingIdentity(token); - } - } - } else if ("force-onbody".equals(cmd)) { + } else if ("force-modemanager-quickdoze".equals(cmd)) { getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); - synchronized (DeviceIdleController.this) { - final long token = Binder.clearCallingIdentity(); - try { - mForceBodyState = true; - pw.println("mForceBodyState: " + mForceBodyState); - mIsOffBody = false; - pw.println("mIsOffBody: " + mIsOffBody); - mLowLatencyOffBodyListener.onOffBodyChangedLocked(); - } finally { - Binder.restoreCallingIdentity(token); + String arg = shell.getNextArg(); + + if ("true".equalsIgnoreCase(arg) || "false".equalsIgnoreCase(arg)) { + boolean enabled = Boolean.parseBoolean(arg); + + synchronized (DeviceIdleController.this) { + final long token = Binder.clearCallingIdentity(); + try { + mForceModeManagerQuickDozeRequest = true; + pw.println("mForceModeManagerQuickDozeRequest: " + + mForceModeManagerQuickDozeRequest); + mModeManagerRequestedQuickDoze = enabled; + pw.println("mModeManagerRequestedQuickDoze: " + + mModeManagerRequestedQuickDoze); + mModeManagerQuickDozeRequestConsumer.onModeManagerRequestChangedLocked(); + } finally { + Binder.restoreCallingIdentity(token); + } } + } else { + pw.println("Provide true or false argument after force-modemanager-quickdoze"); + return -1; } } else { return shell.handleDefaultCommands(cmd); diff --git a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING index 6a4a52a5658b..9ec799f73b41 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING +++ b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING @@ -4,7 +4,6 @@ "name": "CtsUsageStatsTestCases", "options": [ {"include-filter": "android.app.usage.cts.UsageStatsTest"}, - {"include-filter": "android.app.usage.cts.BroadcastResponseStatsTest"}, {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.MediumTest"}, @@ -12,6 +11,13 @@ ] }, { + "name": "CtsBRSTestCases", + "options": [ + {"exclude-annotation": "androidx.test.filters.FlakyTest"}, + {"exclude-annotation": "org.junit.Ignore"} + ] + }, + { "name": "FrameworksServicesTests", "options": [ {"include-filter": "com.android.server.usage"}, diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp index b94b3b458065..d76ca5bdce42 100644 --- a/cmds/idmap2/idmap2d/Idmap2Service.cpp +++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp @@ -265,7 +265,8 @@ Status Idmap2Service::createFabricatedOverlay( res.configuration.value_or(std::string())); } else if (res.binaryData.has_value()) { builder.SetResourceValue(res.resourceName, res.binaryData->get(), - res.configuration.value_or(std::string())); + res.binaryDataOffset, res.binaryDataSize, + res.configuration.value_or(std::string())); } else { builder.SetResourceValue(res.resourceName, res.dataType, res.data, res.configuration.value_or(std::string())); diff --git a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl index 3ad6d58e8253..8ebd454705f0 100644 --- a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl +++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl @@ -26,4 +26,6 @@ parcelable FabricatedOverlayInternalEntry { @nullable @utf8InCpp String stringData; @nullable ParcelFileDescriptor binaryData; @nullable @utf8InCpp String configuration; + long binaryDataOffset; + long binaryDataSize; }
\ No newline at end of file diff --git a/cmds/idmap2/include/idmap2/FabricatedOverlay.h b/cmds/idmap2/include/idmap2/FabricatedOverlay.h index a29fa8f3e1ab..1e7d4c28f45c 100644 --- a/cmds/idmap2/include/idmap2/FabricatedOverlay.h +++ b/cmds/idmap2/include/idmap2/FabricatedOverlay.h @@ -49,6 +49,8 @@ struct FabricatedOverlay { Builder& SetResourceValue(const std::string& resource_name, std::optional<android::base::borrowed_fd>&& binary_value, + off64_t data_binary_offset, + size_t data_binary_size, const std::string& configuration); inline Builder& setFrroPath(std::string frro_path) { @@ -65,6 +67,8 @@ struct FabricatedOverlay { DataValue data_value; std::string data_string_value; std::optional<android::base::borrowed_fd> data_binary_value; + off64_t data_binary_offset; + size_t data_binary_size; std::string configuration; }; @@ -76,6 +80,12 @@ struct FabricatedOverlay { std::vector<Entry> entries_; }; + struct BinaryData { + android::base::borrowed_fd file_descriptor; + off64_t offset; + size_t size; + }; + Result<Unit> ToBinaryStream(std::ostream& stream) const; static Result<FabricatedOverlay> FromBinaryStream(std::istream& stream); @@ -92,13 +102,13 @@ struct FabricatedOverlay { explicit FabricatedOverlay(pb::FabricatedOverlay&& overlay, std::string&& string_pool_data_, - std::vector<android::base::borrowed_fd> binary_files_, + std::vector<FabricatedOverlay::BinaryData> binary_files_, off_t total_binary_bytes_, std::optional<uint32_t> crc_from_disk = {}); pb::FabricatedOverlay overlay_pb_; std::string string_pool_data_; - std::vector<android::base::borrowed_fd> binary_files_; + std::vector<FabricatedOverlay::BinaryData> binary_files_; uint32_t total_binary_bytes_; std::optional<uint32_t> crc_from_disk_; mutable std::optional<SerializedData> data_; diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h index c2b0abed442c..d4490ef47b25 100644 --- a/cmds/idmap2/include/idmap2/ResourceUtils.h +++ b/cmds/idmap2/include/idmap2/ResourceUtils.h @@ -43,6 +43,8 @@ struct TargetValue { DataValue data_value; std::string data_string_value; std::optional<android::base::borrowed_fd> data_binary_value; + off64_t data_binary_offset; + size_t data_binary_size; }; struct TargetValueWithConfig { diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp index dd5be21cd164..47daf23c6381 100644 --- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp +++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp @@ -55,7 +55,7 @@ void Write32(std::ostream& stream, uint32_t value) { FabricatedOverlay::FabricatedOverlay(pb::FabricatedOverlay&& overlay, std::string&& string_pool_data, - std::vector<android::base::borrowed_fd> binary_files, + std::vector<FabricatedOverlay::BinaryData> binary_files, off_t total_binary_bytes, std::optional<uint32_t> crc_from_disk) : overlay_pb_(std::forward<pb::FabricatedOverlay>(overlay)), @@ -81,7 +81,7 @@ FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue( const std::string& resource_name, uint8_t data_type, uint32_t data_value, const std::string& configuration) { entries_.emplace_back( - Entry{resource_name, data_type, data_value, "", std::nullopt, configuration}); + Entry{resource_name, data_type, data_value, "", std::nullopt, 0, 0, configuration}); return *this; } @@ -89,14 +89,15 @@ FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue( const std::string& resource_name, uint8_t data_type, const std::string& data_string_value, const std::string& configuration) { entries_.emplace_back( - Entry{resource_name, data_type, 0, data_string_value, std::nullopt, configuration}); + Entry{resource_name, data_type, 0, data_string_value, std::nullopt, 0, 0, configuration}); return *this; } FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue( const std::string& resource_name, std::optional<android::base::borrowed_fd>&& binary_value, - const std::string& configuration) { - entries_.emplace_back(Entry{resource_name, 0, 0, "", binary_value, configuration}); + off64_t data_binary_offset, size_t data_binary_size, const std::string& configuration) { + entries_.emplace_back(Entry{resource_name, 0, 0, "", binary_value, + data_binary_offset, data_binary_size, configuration}); return *this; } @@ -148,7 +149,8 @@ Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() { } value->second = TargetValue{res_entry.data_type, res_entry.data_value, - res_entry.data_string_value, res_entry.data_binary_value}; + res_entry.data_string_value, res_entry.data_binary_value, + res_entry.data_binary_offset, res_entry.data_binary_size}; } pb::FabricatedOverlay overlay_pb; @@ -157,7 +159,7 @@ Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() { overlay_pb.set_target_package_name(target_package_name_); overlay_pb.set_target_overlayable(target_overlayable_); - std::vector<android::base::borrowed_fd> binary_files; + std::vector<FabricatedOverlay::BinaryData> binary_files; size_t total_binary_bytes = 0; // 16 for the number of bytes in the frro file before the binary data const size_t FRRO_HEADER_SIZE = 16; @@ -182,16 +184,15 @@ Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() { pb_value->set_data_value(ref.index()); } else if (value.second.data_binary_value.has_value()) { pb_value->set_data_type(Res_value::TYPE_STRING); - struct stat s; - if (fstat(value.second.data_binary_value->get(), &s) == -1) { - return Error("unable to get size of binary file: %d", errno); - } std::string uri = StringPrintf("frro:/%s?offset=%d&size=%d", frro_path_.c_str(), static_cast<int> (FRRO_HEADER_SIZE + total_binary_bytes), - static_cast<int> (s.st_size)); - total_binary_bytes += s.st_size; - binary_files.emplace_back(value.second.data_binary_value->get()); + static_cast<int> (value.second.data_binary_size)); + total_binary_bytes += value.second.data_binary_size; + binary_files.emplace_back(FabricatedOverlay::BinaryData{ + value.second.data_binary_value->get(), + value.second.data_binary_offset, + value.second.data_binary_size}); auto ref = string_pool.MakeRef(std::move(uri)); pb_value->set_data_value(ref.index()); } else { @@ -310,8 +311,9 @@ Result<Unit> FabricatedOverlay::ToBinaryStream(std::ostream& stream) const { Write32(stream, (*data)->pb_crc); Write32(stream, total_binary_bytes_); std::string file_contents; - for (const android::base::borrowed_fd fd : binary_files_) { - if (!ReadFdToString(fd, &file_contents)) { + for (const FabricatedOverlay::BinaryData fd : binary_files_) { + file_contents.resize(fd.size); + if (!ReadFullyAtOffset(fd.file_descriptor, file_contents.data(), fd.size, fd.offset)) { return Error("Failed to read binary file data."); } stream.write(file_contents.data(), file_contents.length()); diff --git a/cmds/idmap2/self_targeting/SelfTargeting.cpp b/cmds/idmap2/self_targeting/SelfTargeting.cpp index a8aa03309b16..c7f5cf3632c5 100644 --- a/cmds/idmap2/self_targeting/SelfTargeting.cpp +++ b/cmds/idmap2/self_targeting/SelfTargeting.cpp @@ -52,6 +52,7 @@ CreateFrroFile(std::string& out_err_result, const std::string& packageName, const auto dataType = entry_params.data_type; if (entry_params.data_binary_value.has_value()) { builder.SetResourceValue(entry_params.resource_name, *entry_params.data_binary_value, + entry_params.binary_data_offset, entry_params.binary_data_size, entry_params.configuration); } else if (dataType >= Res_value::TYPE_FIRST_INT && dataType <= Res_value::TYPE_LAST_INT) { builder.SetResourceValue(entry_params.resource_name, dataType, diff --git a/cmds/idmap2/tests/FabricatedOverlayTests.cpp b/cmds/idmap2/tests/FabricatedOverlayTests.cpp index e13a0eb5d488..b460bb33f559 100644 --- a/cmds/idmap2/tests/FabricatedOverlayTests.cpp +++ b/cmds/idmap2/tests/FabricatedOverlayTests.cpp @@ -59,7 +59,7 @@ TEST(FabricatedOverlayTests, SetResourceValue) { Res_value::TYPE_STRING, "foobar", "en-rUS-normal-xxhdpi-v21") - .SetResourceValue("com.example.target:drawable/dr1", fd, "port-xxhdpi-v7") + .SetResourceValue("com.example.target:drawable/dr1", fd, 0, 8341, "port-xxhdpi-v7") .setFrroPath("/foo/bar/biz.frro") .Build(); ASSERT_TRUE(overlay); diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp index f6e48ba7a1f4..a3448fda60d9 100644 --- a/cmds/idmap2/tests/IdmapTests.cpp +++ b/cmds/idmap2/tests/IdmapTests.cpp @@ -269,7 +269,7 @@ TEST(IdmapTests, FabricatedOverlay) { .SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U, "land-xxhdpi-v7") .SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000, "land") .SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar", "xxhdpi-v7") - .SetResourceValue("drawable/dr1", fd, "port-xxhdpi-v7") + .SetResourceValue("drawable/dr1", fd, 0, 8341, "port-xxhdpi-v7") .setFrroPath("/foo/bar/biz.frro") .Build(); diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp index 380e462a3aba..40f98c2f351b 100644 --- a/cmds/idmap2/tests/ResourceMappingTests.cpp +++ b/cmds/idmap2/tests/ResourceMappingTests.cpp @@ -212,7 +212,7 @@ TEST(ResourceMappingTests, FabricatedOverlay) { .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", "") - .SetResourceValue("drawable/dr1", fd, "") + .SetResourceValue("drawable/dr1", fd, 0, 8341, "") .setFrroPath("/foo/bar/biz.frro") .Build(); diff --git a/core/api/current.txt b/core/api/current.txt index 3392d250d426..f40cbb61d4a3 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9530,6 +9530,7 @@ package android.companion { method @Nullable public CharSequence getDisplayName(); method public int getId(); method public int getSystemDataSyncFlags(); + method @Nullable public String getTag(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.companion.AssociationInfo> CREATOR; } @@ -9599,6 +9600,7 @@ package android.companion { method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public void attachSystemDataTransport(int, @NonNull java.io.InputStream, @NonNull java.io.OutputStream) throws android.companion.DeviceNotAssociatedException; method @Nullable public android.content.IntentSender buildAssociationCancellationIntent(); method @Nullable public android.content.IntentSender buildPermissionTransferUserConsentIntent(int) throws android.companion.DeviceNotAssociatedException; + method public void clearAssociationTag(int); method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public void detachSystemDataTransport(int) throws android.companion.DeviceNotAssociatedException; method public void disableSystemDataSyncForTypes(int, int); method @Deprecated public void disassociate(@NonNull String); @@ -9608,6 +9610,7 @@ package android.companion { method @NonNull public java.util.List<android.companion.AssociationInfo> getMyAssociations(); method @Deprecated public boolean hasNotificationAccess(android.content.ComponentName); method public void requestNotificationAccess(android.content.ComponentName); + method public void setAssociationTag(int, @NonNull String); method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException; method public void startSystemDataTransfer(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.companion.CompanionException>) throws android.companion.DeviceNotAssociatedException; method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException; @@ -11699,6 +11702,7 @@ package android.content.om { method @NonNull public void setResourceValue(@NonNull String, @IntRange(from=android.util.TypedValue.TYPE_FIRST_INT, to=android.util.TypedValue.TYPE_LAST_INT) int, int, @Nullable String); method @NonNull public void setResourceValue(@NonNull String, int, @NonNull String, @Nullable String); method @NonNull public void setResourceValue(@NonNull String, @NonNull android.os.ParcelFileDescriptor, @Nullable String); + method @NonNull public void setResourceValue(@NonNull String, @NonNull android.content.res.AssetFileDescriptor, @Nullable String); method public void setTargetOverlayable(@Nullable String); } @@ -33699,7 +33703,7 @@ package android.os { method public boolean isInteractive(); method public boolean isLowPowerStandbyEnabled(); method public boolean isPowerSaveMode(); - method public boolean isRebootingUserspaceSupported(); + method @Deprecated public boolean isRebootingUserspaceSupported(); method @Deprecated public boolean isScreenOn(); method public boolean isSustainedPerformanceModeSupported(); method public boolean isWakeLockLevelSupported(int); @@ -44999,6 +45003,7 @@ package android.telephony { field public static final int NR_STATE_RESTRICTED = 1; // 0x1 field public static final int SERVICE_TYPE_DATA = 2; // 0x2 field public static final int SERVICE_TYPE_EMERGENCY = 5; // 0x5 + field public static final int SERVICE_TYPE_MMS = 6; // 0x6 field public static final int SERVICE_TYPE_SMS = 3; // 0x3 field public static final int SERVICE_TYPE_UNKNOWN = 0; // 0x0 field public static final int SERVICE_TYPE_VIDEO = 4; // 0x4 diff --git a/core/api/system-current.txt b/core/api/system-current.txt index a02fd84ce33c..eca2015fa3ed 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -10748,7 +10748,7 @@ package android.os { field @RequiresPermission(android.Manifest.permission.MANAGE_LOW_POWER_STANDBY) public static final String ACTION_LOW_POWER_STANDBY_PORTS_CHANGED = "android.os.action.LOW_POWER_STANDBY_PORTS_CHANGED"; field public static final int POWER_SAVE_MODE_TRIGGER_DYNAMIC = 1; // 0x1 field public static final int POWER_SAVE_MODE_TRIGGER_PERCENTAGE = 0; // 0x0 - field public static final String REBOOT_USERSPACE = "userspace"; + field @Deprecated public static final String REBOOT_USERSPACE = "userspace"; field public static final int SOUND_TRIGGER_MODE_ALL_DISABLED = 2; // 0x2 field public static final int SOUND_TRIGGER_MODE_ALL_ENABLED = 0; // 0x0 field public static final int SOUND_TRIGGER_MODE_CRITICAL_ONLY = 1; // 0x1 diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 833d9c6d2420..76d4386a1cad 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3864,11 +3864,14 @@ package android.view.inputmethod { public final class InputMethodManager { method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void addVirtualStylusIdForTestSession(); method public int getDisplayId(); + method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull android.os.UserHandle); + method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(@NonNull String, boolean, @NonNull android.os.UserHandle); method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int); method public boolean hasActiveInputConnection(@Nullable android.view.View); method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean hasPendingImeVisibilityRequests(); method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isCurrentRootView(@NonNull android.view.View); method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isInputMethodPickerShown(); + method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public boolean isStylusHandwritingAvailableAsUser(@NonNull android.os.UserHandle); method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void setStylusWindowIdleTimeoutForTest(long); field public static final long CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING = 214016041L; // 0xcc1a029L } diff --git a/core/java/android/app/IWindowToken.aidl b/core/java/android/app/IWindowToken.aidl deleted file mode 100644 index 3627b0f13a7f..000000000000 --- a/core/java/android/app/IWindowToken.aidl +++ /dev/null @@ -1,35 +0,0 @@ -/* - ** Copyright 2020, The Android Open Source Project - ** - ** Licensed under the Apache License, Version 2.0 (the "License"); - ** you may not use this file except in compliance with the License. - ** You may obtain a copy of the License at - ** - ** http://www.apache.org/licenses/LICENSE-2.0 - ** - ** Unless required by applicable law or agreed to in writing, software - ** distributed under the License is distributed on an "AS IS" BASIS, - ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ** See the License for the specific language governing permissions and - ** limitations under the License. - */ -package android.app; - -import android.content.res.Configuration; -import android.view.IWindow; - -/** - * Callback to receive configuration changes from {@link com.android.server.WindowToken}. - * WindowToken can be regarded to as a group of {@link android.view.IWindow} added from the same - * visual context, such as {@link Activity} or one created with - * {@link android.content.Context#createWindowContext(int)}. When WindowToken receives configuration - * changes and/or when it is moved between displays, it will propagate the changes to client side - * via this interface. - * @see android.content.Context#createWindowContext(int) - * {@hide} - */ -oneway interface IWindowToken { - void onConfigurationChanged(in Configuration newConfig, int newDisplayId); - - void onWindowTokenRemoved(); -} diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 8647dd29b71a..e57849957f13 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -237,8 +237,6 @@ import android.view.contentcapture.ContentCaptureManager; import android.view.contentcapture.IContentCaptureManager; import android.view.displayhash.DisplayHashManager; import android.view.inputmethod.InputMethodManager; -import android.view.selectiontoolbar.ISelectionToolbarManager; -import android.view.selectiontoolbar.SelectionToolbarManager; import android.view.textclassifier.TextClassificationManager; import android.view.textservice.TextServicesManager; import android.view.translation.ITranslationManager; @@ -379,17 +377,6 @@ public final class SystemServiceRegistry { return new TextClassificationManager(ctx); }}); - registerService(Context.SELECTION_TOOLBAR_SERVICE, SelectionToolbarManager.class, - new CachedServiceFetcher<SelectionToolbarManager>() { - @Override - public SelectionToolbarManager createService(ContextImpl ctx) - throws ServiceNotFoundException { - IBinder b = ServiceManager.getServiceOrThrow( - Context.SELECTION_TOOLBAR_SERVICE); - return new SelectionToolbarManager(ctx.getOuterContext(), - ISelectionToolbarManager.Stub.asInterface(b)); - }}); - registerService(Context.FONT_SERVICE, FontManager.class, new CachedServiceFetcher<FontManager>() { @Override diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index b7ec7b55d7db..d66fca8945f1 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -22,6 +22,7 @@ import android.os.PooledStringWriter; import android.os.RemoteException; import android.os.SystemClock; import android.service.autofill.FillRequest; +import android.text.Spanned; import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -1557,6 +1558,10 @@ public class AssistStructure implements Parcelable { /** * Returns any text associated with the node that is displayed to the user, or null * if there is none. + * + * <p> The text will be stripped of any spans that could potentially contain reference to + * the activity context, to avoid memory leak. If the text contained a span, a plain + * string version of the text will be returned. */ @Nullable public CharSequence getText() { @@ -1996,14 +2001,16 @@ public class AssistStructure implements Parcelable { @Override public void setText(CharSequence text) { ViewNodeText t = getNodeText(); - t.mText = TextUtils.trimNoCopySpans(text); + // Strip spans from the text to avoid memory leak + t.mText = TextUtils.trimToParcelableSize(stripAllSpansFromText(text)); t.mTextSelectionStart = t.mTextSelectionEnd = -1; } @Override public void setText(CharSequence text, int selectionStart, int selectionEnd) { ViewNodeText t = getNodeText(); - t.mText = TextUtils.trimNoCopySpans(text); + // Strip spans from the text to avoid memory leak + t.mText = stripAllSpansFromText(text); t.mTextSelectionStart = selectionStart; t.mTextSelectionEnd = selectionEnd; } @@ -2222,6 +2229,13 @@ public class AssistStructure implements Parcelable { public void setHtmlInfo(@NonNull HtmlInfo htmlInfo) { mNode.mHtmlInfo = htmlInfo; } + + private CharSequence stripAllSpansFromText(CharSequence text) { + if (text instanceof Spanned) { + return text.toString(); + } + return text; + } } private static final class HtmlInfoNode extends HtmlInfo implements Parcelable { diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java index 0958a806a5ff..7d62c79e7519 100644 --- a/core/java/android/companion/AssociationInfo.java +++ b/core/java/android/companion/AssociationInfo.java @@ -57,6 +57,7 @@ public final class AssociationInfo implements Parcelable { private final boolean mSelfManaged; private final boolean mNotifyOnDeviceNearby; private final int mSystemDataSyncFlags; + private final String mTag; /** * Indicates that the association has been revoked (removed), but we keep the association @@ -78,10 +79,11 @@ public final class AssociationInfo implements Parcelable { * @hide */ public AssociationInfo(int id, @UserIdInt int userId, @NonNull String packageName, - @Nullable MacAddress macAddress, @Nullable CharSequence displayName, - @Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice, - boolean selfManaged, boolean notifyOnDeviceNearby, boolean revoked, - long timeApprovedMs, long lastTimeConnectedMs, int systemDataSyncFlags) { + @Nullable String tag, @Nullable MacAddress macAddress, + @Nullable CharSequence displayName, @Nullable String deviceProfile, + @Nullable AssociatedDevice associatedDevice, boolean selfManaged, + boolean notifyOnDeviceNearby, boolean revoked, long timeApprovedMs, + long lastTimeConnectedMs, int systemDataSyncFlags) { if (id <= 0) { throw new IllegalArgumentException("Association ID should be greater than 0"); } @@ -97,6 +99,7 @@ public final class AssociationInfo implements Parcelable { mDeviceMacAddress = macAddress; mDisplayName = displayName; + mTag = tag; mDeviceProfile = deviceProfile; mAssociatedDevice = associatedDevice; @@ -116,6 +119,14 @@ public final class AssociationInfo implements Parcelable { } /** + * @return the tag of this association. + * @see CompanionDeviceManager#setAssociationTag(int, String) + */ + public @Nullable String getTag() { + return mTag; + } + + /** * @return the ID of the user who "owns" this association. * @hide */ @@ -287,6 +298,7 @@ public final class AssociationInfo implements Parcelable { + "mId=" + mId + ", mUserId=" + mUserId + ", mPackageName='" + mPackageName + '\'' + + ", mTag='" + mTag + '\'' + ", mDeviceMacAddress=" + mDeviceMacAddress + ", mDisplayName='" + mDisplayName + '\'' + ", mDeviceProfile='" + mDeviceProfile + '\'' @@ -315,6 +327,7 @@ public final class AssociationInfo implements Parcelable { && mTimeApprovedMs == that.mTimeApprovedMs && mLastTimeConnectedMs == that.mLastTimeConnectedMs && Objects.equals(mPackageName, that.mPackageName) + && Objects.equals(mTag, that.mTag) && Objects.equals(mDeviceMacAddress, that.mDeviceMacAddress) && Objects.equals(mDisplayName, that.mDisplayName) && Objects.equals(mDeviceProfile, that.mDeviceProfile) @@ -324,7 +337,7 @@ public final class AssociationInfo implements Parcelable { @Override public int hashCode() { - return Objects.hash(mId, mUserId, mPackageName, mDeviceMacAddress, mDisplayName, + return Objects.hash(mId, mUserId, mPackageName, mTag, mDeviceMacAddress, mDisplayName, mDeviceProfile, mAssociatedDevice, mSelfManaged, mNotifyOnDeviceNearby, mRevoked, mTimeApprovedMs, mLastTimeConnectedMs, mSystemDataSyncFlags); } @@ -340,6 +353,7 @@ public final class AssociationInfo implements Parcelable { dest.writeInt(mUserId); dest.writeString(mPackageName); + dest.writeString(mTag); dest.writeTypedObject(mDeviceMacAddress, 0); dest.writeCharSequence(mDisplayName); @@ -359,6 +373,7 @@ public final class AssociationInfo implements Parcelable { mUserId = in.readInt(); mPackageName = in.readString(); + mTag = in.readString(); mDeviceMacAddress = in.readTypedObject(MacAddress.CREATOR); mDisplayName = in.readCharSequence(); @@ -413,9 +428,11 @@ public final class AssociationInfo implements Parcelable { private boolean mRevoked; private long mLastTimeConnectedMs; private int mSystemDataSyncFlags; + private String mTag; private Builder(@NonNull AssociationInfo info) { mOriginalInfo = info; + mTag = info.mTag; mNotifyOnDeviceNearby = info.mNotifyOnDeviceNearby; mRevoked = info.mRevoked; mLastTimeConnectedMs = info.mLastTimeConnectedMs; @@ -460,12 +477,21 @@ public final class AssociationInfo implements Parcelable { } /** @hide */ + @Override + @NonNull + public Builder setTag(String tag) { + mTag = tag; + return this; + } + + /** @hide */ @NonNull public AssociationInfo build() { return new AssociationInfo( mOriginalInfo.mId, mOriginalInfo.mUserId, mOriginalInfo.mPackageName, + mTag, mOriginalInfo.mDeviceMacAddress, mOriginalInfo.mDisplayName, mOriginalInfo.mDeviceProfile, @@ -508,5 +534,8 @@ public final class AssociationInfo implements Parcelable { /** @hide */ @NonNull Builder setSystemDataSyncFlags(int flags); + + /** @hide */ + Builder setTag(String tag); } } diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index 69e1653eb8ed..3aa287724263 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -1372,6 +1372,50 @@ public final class CompanionDeviceManager { } } + /** + * Sets the {@link AssociationInfo#getTag() tag} for this association. + * + * <p>The length of the tag must be at most 20 characters. + * + * <p>This allows to store useful information about the associated devices. + * + * @param associationId The unique {@link AssociationInfo#getId ID} assigned to the Association + * of the companion device recorded by CompanionDeviceManager + * @param tag the tag of this association + */ + @UserHandleAware + public void setAssociationTag(int associationId, @NonNull String tag) { + Objects.requireNonNull(tag, "tag cannot be null"); + + if (tag.length() > 20) { + throw new IllegalArgumentException("Length of the tag must be at most 20 characters"); + } + + try { + mService.setAssociationTag(associationId, tag); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Clears the {@link AssociationInfo#getTag() tag} for this association. + * + * <p>The tag will be set to null for this association when calling this API. + * + * @param associationId The unique {@link AssociationInfo#getId ID} assigned to the Association + * of the companion device recorded by CompanionDeviceManager + * @see CompanionDeviceManager#setAssociationTag(int, String) + */ + @UserHandleAware + public void clearAssociationTag(int associationId) { + try { + mService.clearAssociationTag(associationId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private boolean checkFeaturePresent() { boolean featurePresent = mService != null; if (!featurePresent && DEBUG) { diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl index 463dba24fb5a..e01543d50e55 100644 --- a/core/java/android/companion/ICompanionDeviceManager.aidl +++ b/core/java/android/companion/ICompanionDeviceManager.aidl @@ -115,4 +115,8 @@ interface ICompanionDeviceManager { @EnforcePermission("MANAGE_COMPANION_DEVICES") void enableSecureTransport(boolean enabled); + + void setAssociationTag(int associationId, String tag); + + void clearAssociationTag(int associationId); } diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java index d0bb2b9b6eeb..8f35ca25ab22 100644 --- a/core/java/android/content/AttributionSource.java +++ b/core/java/android/content/AttributionSource.java @@ -517,7 +517,11 @@ public final class AttributionSource implements Parcelable { } /** - * The device ID for which permissions are checked. + * Gets the device ID for this attribution source. Attribution source can set the device ID + * using {@link Builder#setDeviceId(int)}, the default device ID is + * {@link Context#DEVICE_ID_DEFAULT}. + * <p> + * This device ID is used for permissions checking during attribution source validation. */ public int getDeviceId() { return mAttributionSourceState.deviceId; diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java index 7e787c9c3a8c..c4547b8acc2b 100644 --- a/core/java/android/content/om/FabricatedOverlay.java +++ b/core/java/android/content/om/FabricatedOverlay.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.res.AssetFileDescriptor; import android.os.FabricatedOverlayInternal; import android.os.FabricatedOverlayInternalEntry; import android.os.ParcelFileDescriptor; @@ -269,7 +270,7 @@ public class FabricatedOverlay { * @param configuration The string representation of the config this overlay is enabled for * @return the builder itself * @deprecated Framework should use {@link FabricatedOverlay#setResourceValue(String, - ParcelFileDescriptor, String)} instead. + ParcelFileDescriptor, String)} instead. * @hide */ @Deprecated(since = "Please use FabricatedOverlay#setResourceValue instead") @@ -285,6 +286,30 @@ public class FabricatedOverlay { } /** + * Sets the value of the fabricated overlay for the file descriptor type. + * + * @param resourceName name of the target resource to overlay (in the form + * [package]:type/entry) + * @param value the file descriptor whose contents are the value of the frro + * @param configuration The string representation of the config this overlay is enabled for + * @return the builder itself + * @deprecated Framework should use {@link FabricatedOverlay#setResourceValue(String, + ParcelFileDescriptor, String)} instead. + * @hide + */ + @Deprecated(since = "Please use FabricatedOverlay#setResourceValue instead") + @NonNull + public Builder setResourceValue( + @NonNull String resourceName, + @NonNull AssetFileDescriptor value, + @Nullable String configuration) { + ensureValidResourceName(resourceName); + mEntries.add( + generateFabricatedOverlayInternalEntry(resourceName, value, configuration)); + return this; + } + + /** * Builds an immutable fabricated overlay. * * @return the fabricated overlay @@ -421,6 +446,21 @@ public class FabricatedOverlay { entry.resourceName = resourceName; entry.binaryData = Objects.requireNonNull(parcelFileDescriptor); entry.configuration = configuration; + entry.binaryDataOffset = 0; + entry.binaryDataSize = parcelFileDescriptor.getStatSize(); + return entry; + } + + @NonNull + private static FabricatedOverlayInternalEntry generateFabricatedOverlayInternalEntry( + @NonNull String resourceName, @NonNull AssetFileDescriptor assetFileDescriptor, + @Nullable String configuration) { + final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry(); + entry.resourceName = resourceName; + entry.binaryData = Objects.requireNonNull(assetFileDescriptor.getParcelFileDescriptor()); + entry.binaryDataOffset = assetFileDescriptor.getStartOffset(); + entry.binaryDataSize = assetFileDescriptor.getLength(); + entry.configuration = configuration; return entry; } @@ -495,4 +535,23 @@ public class FabricatedOverlay { mOverlay.entries.add( generateFabricatedOverlayInternalEntry(resourceName, value, configuration)); } + + /** + * Sets the resource value in the fabricated overlay for the file descriptor type with the + * configuration. + * + * @param resourceName name of the target resource to overlay (in the form + * [package]:type/entry) + * @param value the file descriptor whose contents are the value of the frro + * @param configuration The string representation of the config this overlay is enabled for + */ + @NonNull + public void setResourceValue( + @NonNull String resourceName, + @NonNull AssetFileDescriptor value, + @Nullable String configuration) { + ensureValidResourceName(resourceName); + mOverlay.entries.add( + generateFabricatedOverlayInternalEntry(resourceName, value, configuration)); + } } diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 19539c25dcb0..8cb96ba09748 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -3028,6 +3028,8 @@ public class PackageInstaller { * <li>{@code requireUserAction} is set to {@link #USER_ACTION_NOT_REQUIRED}.</li> * <li>The app being installed targets: * <ul> + * <li>{@link android.os.Build.VERSION_CODES#Q API 29} or higher on + * Android S ({@link android.os.Build.VERSION_CODES#S API 31})</li> * <li>{@link android.os.Build.VERSION_CODES#R API 30} or higher on * Android T ({@link android.os.Build.VERSION_CODES#TIRAMISU API 33})</li> * <li>{@link android.os.Build.VERSION_CODES#S API 31} or higher <b>after</b> diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING index 6419f8c6f0d9..ea21d51b0e9e 100644 --- a/core/java/android/content/pm/TEST_MAPPING +++ b/core/java/android/content/pm/TEST_MAPPING @@ -114,7 +114,9 @@ "exclude-annotation":"org.junit.Ignore" } ] - }, + } + ], + "presubmit-large":[ { "name":"CtsPackageManagerTestCases", "options":[ @@ -123,11 +125,12 @@ }, { "exclude-annotation":"org.junit.Ignore" + }, + { + "exclude-filter": "android.content.pm.cts.PackageManagerShellCommandMultiUserTest" } ] - } - ], - "presubmit-large":[ + }, { "name":"CtsUsesNativeLibraryTest", "options":[ @@ -162,6 +165,14 @@ }, { "name":"CtsInstallHostTestCases" + }, + { + "name": "CtsPackageManagerTestCases", + "options": [ + { + "include-filter": "android.content.pm.cts.PackageManagerShellCommandMultiUserTest" + } + ] } ] -}
\ No newline at end of file +} diff --git a/core/java/android/content/res/Element.java b/core/java/android/content/res/Element.java index a86c0c9a4463..e931fe8526f1 100644 --- a/core/java/android/content/res/Element.java +++ b/core/java/android/content/res/Element.java @@ -42,6 +42,8 @@ public class Element { public static final int MAX_ATTR_LEN_PATH = 4000; public static final int MAX_ATTR_LEN_DATA_VALUE = 4000; + private static final String BAD_COMPONENT_NAME_CHARS = ";,[](){}:?-%^*|/\\"; + private static final String TAG = "PackageParsing"; protected static final String TAG_ACTION = "action"; protected static final String TAG_ACTIVITY = "activity"; @@ -128,6 +130,7 @@ public class Element { protected static final String TAG_ATTR_VALUE = "value"; protected static final String TAG_ATTR_VERSION_NAME = "versionName"; protected static final String TAG_ATTR_WRITE_PERMISSION = "writePermission"; + protected static final String TAG_ATTR_ZYGOTE_PRELOAD_NAME = "zygotePreloadName"; // The length of mTagCounters corresponds to the number of tags defined in getCounterIdx. If new // tags are added then the size here should be increased to match. @@ -374,6 +377,7 @@ public class Element { case TAG_ATTR_TASK_AFFINITY: case TAG_ATTR_WRITE_PERMISSION: case TAG_ATTR_VERSION_NAME: + case TAG_ATTR_ZYGOTE_PRELOAD_NAME: return MAX_ATTR_LEN_NAME; case TAG_ATTR_PATH: case TAG_ATTR_PATH_ADVANCED_PATTERN: @@ -488,6 +492,7 @@ public class Element { case R.styleable.AndroidManifestApplication_requiredAccountType: case R.styleable.AndroidManifestApplication_restrictedAccountType: case R.styleable.AndroidManifestApplication_taskAffinity: + case R.styleable.AndroidManifestApplication_zygotePreloadName: return MAX_ATTR_LEN_NAME; default: return DEFAULT_MAX_STRING_ATTR_LENGTH; @@ -738,6 +743,7 @@ public class Element { switch (name) { case TAG_ATTR_BACKUP_AGENT: case TAG_ATTR_NAME: + case TAG_ATTR_ZYGOTE_PRELOAD_NAME: return true; default: return false; @@ -766,7 +772,8 @@ public class Element { return index == R.styleable.AndroidManifestActivityAlias_targetActivity; case TAG_APPLICATION: return index == R.styleable.AndroidManifestApplication_backupAgent - || index == R.styleable.AndroidManifestApplication_name; + || index == R.styleable.AndroidManifestApplication_name + || index == R.styleable.AndroidManifestApplication_zygotePreloadName; case TAG_INSTRUMENTATION: return index == R.styleable.AndroidManifestInstrumentation_name; case TAG_PROVIDER: @@ -785,33 +792,13 @@ public class Element { } void validateComponentName(CharSequence name) { - int i = 0; - if (name.charAt(0) == '.') { - i = 1; - } boolean isStart = true; - for (; i < name.length(); i++) { - if (name.charAt(i) == '.') { - if (isStart) { - break; - } - isStart = true; - } else { - if (isStart) { - if (Character.isJavaIdentifierStart(name.charAt(i))) { - isStart = false; - } else { - break; - } - } else if (!Character.isJavaIdentifierPart(name.charAt(i))) { - break; - } + for (int i = 0; i < name.length(); i++) { + if (BAD_COMPONENT_NAME_CHARS.indexOf(name.charAt(i)) >= 0) { + Slog.e(TAG, name + " is not a valid Java class name"); + throw new SecurityException(name + " is not a valid Java class name"); } } - if ((i < name.length()) || (name.charAt(name.length() - 1) == '.')) { - Slog.e(TAG, name + " is not a valid Java class name"); - throw new SecurityException(name + " is not a valid Java class name"); - } } void validateStrAttr(String attrName, String attrValue) { diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index c872516014db..59408191cdf5 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -4194,9 +4194,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <p>This control allows Camera extension clients to configure the strength of the applied * extension effect. Strength equal to 0 means that the extension must not apply any * post-processing and return a regular captured frame. Strength equal to 100 is the - * default level of post-processing applied when the control is not supported or not set - * by the client. Values between 0 and 100 will have different effect depending on the - * extension type as described below:</p> + * maximum level of post-processing. Values between 0 and 100 will have different effect + * depending on the extension type as described below:</p> * <ul> * <li>{@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_BOKEH BOKEH} - * the strength is expected to control the amount of blur.</li> @@ -4211,7 +4210,9 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * {@link android.hardware.camera2.CameraExtensionCharacteristics#getAvailableCaptureRequestKeys }. * The control is only defined and available to clients sending capture requests via * {@link android.hardware.camera2.CameraExtensionSession }. - * The default value is 100.</p> + * If the client doesn't specify the extension strength value, then a default value will + * be set by the extension. Clients can retrieve the default value by checking the + * corresponding capture result.</p> * <p><b>Range of valid values:</b><br> * 0 - 100</p> * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 57f7bca1f67e..905f98de75ff 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -5694,9 +5694,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>This control allows Camera extension clients to configure the strength of the applied * extension effect. Strength equal to 0 means that the extension must not apply any * post-processing and return a regular captured frame. Strength equal to 100 is the - * default level of post-processing applied when the control is not supported or not set - * by the client. Values between 0 and 100 will have different effect depending on the - * extension type as described below:</p> + * maximum level of post-processing. Values between 0 and 100 will have different effect + * depending on the extension type as described below:</p> * <ul> * <li>{@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_BOKEH BOKEH} - * the strength is expected to control the amount of blur.</li> @@ -5711,7 +5710,9 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * {@link android.hardware.camera2.CameraExtensionCharacteristics#getAvailableCaptureRequestKeys }. * The control is only defined and available to clients sending capture requests via * {@link android.hardware.camera2.CameraExtensionSession }. - * The default value is 100.</p> + * If the client doesn't specify the extension strength value, then a default value will + * be set by the extension. Clients can retrieve the default value by checking the + * corresponding capture result.</p> * <p><b>Range of valid values:</b><br> * 0 - 100</p> * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index 1ae1b050d32f..37bd67a4b883 100644 --- a/core/java/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -537,8 +537,15 @@ public class VpnService extends Service { } /** - * Sets an HTTP proxy for the VPN network. This proxy is only a recommendation - * and it is possible that some apps will ignore it. PAC proxies are not supported. + * Sets an HTTP proxy for the VPN network. + * <p class="note">This proxy is only a recommendation and it is possible that some apps + * will ignore it. + * <p class="note">PAC proxies are not supported over VPNs. + * <p class="note">Apps that do use the proxy cannot distinguish between routes handled + * and not handled by the VPN and will try to access HTTP resources over the proxy + * regardless of the destination. In practice this means using a proxy with a split + * tunnel generally won't work as expected, because HTTP accesses on routes not handled by + * the VPN will not reach as the proxy won't be available outside of the VPN network. */ @NonNull public Builder setHttpProxy(@NonNull ProxyInfo proxyInfo) { diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 8596e543a148..43219bc38e2d 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -1968,6 +1968,9 @@ public abstract class BatteryStats { public static final int STATE2_GPS_SIGNAL_QUALITY_SHIFT = 7; public static final int STATE2_GPS_SIGNAL_QUALITY_MASK = 0x3 << STATE2_GPS_SIGNAL_QUALITY_SHIFT; + // Values for NR_STATE_* + public static final int STATE2_NR_STATE_SHIFT = 9; + public static final int STATE2_NR_STATE_MASK = 0x3 << STATE2_NR_STATE_SHIFT; public static final int STATE2_POWER_SAVE_FLAG = 1<<31; public static final int STATE2_VIDEO_ON_FLAG = 1<<30; @@ -2763,6 +2766,14 @@ public abstract class BatteryStats { */ public abstract Timer getPhoneDataConnectionTimer(int dataType); + /** + * Returns the time in microseconds that the phone's data connection was in NR NSA mode while + * on battery. + * + * {@hide} + */ + public abstract long getNrNsaTime(long elapsedRealtimeUs); + /** @hide */ public static final int RADIO_ACCESS_TECHNOLOGY_OTHER = 0; /** @hide */ @@ -3029,7 +3040,11 @@ public abstract class BatteryStats { "cellular_high_tx_power", "Chtp"), new BitDescription(HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK, HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT, "gps_signal_quality", "Gss", - new String[] { "poor", "good", "none"}, new String[] { "poor", "good", "none"}) + new String[] { "poor", "good", "none"}, new String[] { "poor", "good", "none"}), + new BitDescription(HistoryItem.STATE2_NR_STATE_MASK, + HistoryItem.STATE2_NR_STATE_SHIFT, "nr_state", "nrs", + new String[]{"none", "restricted", "not_restricted", "connected"}, + new String[]{"0", "1", "2", "3"}), }; public static final String[] HISTORY_EVENT_NAMES = new String[] { @@ -5612,20 +5627,36 @@ public abstract class BatteryStats { sb.append(prefix); sb.append(" Cellular Radio Access Technology:"); didOne = false; - for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) { - final long time = getPhoneDataConnectionTime(i, rawRealtime, which); + for (int connType = 0; connType < NUM_DATA_CONNECTION_TYPES; connType++) { + final long time = getPhoneDataConnectionTime(connType, rawRealtime, which); if (time == 0) { continue; } sb.append("\n "); sb.append(prefix); didOne = true; - sb.append(i < DATA_CONNECTION_NAMES.length ? DATA_CONNECTION_NAMES[i] : "ERROR"); + sb.append(connType < DATA_CONNECTION_NAMES.length ? + DATA_CONNECTION_NAMES[connType] : "ERROR"); sb.append(" "); formatTimeMs(sb, time/1000); sb.append("("); sb.append(formatRatioLocked(time, whichBatteryRealtime)); sb.append(") "); + + if (connType == TelephonyManager.NETWORK_TYPE_LTE) { + // Report any of the LTE time was spent in NR NSA mode. + final long nrNsaTime = getNrNsaTime(rawRealtime); + if (nrNsaTime != 0) { + sb.append("\n "); + sb.append(prefix); + sb.append("nr_nsa"); + sb.append(" "); + formatTimeMs(sb, nrNsaTime / 1000); + sb.append("("); + sb.append(formatRatioLocked(nrNsaTime, whichBatteryRealtime)); + sb.append(") "); + } + } } if (!didOne) sb.append(" (no activity)"); pw.println(sb.toString()); diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index 538ed423626f..a07735e7540e 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -178,23 +178,6 @@ public class GraphicsEnvironment { } /** - * Query to determine if the Game Mode has enabled ANGLE. - */ - private boolean isAngleEnabledByGameMode(Context context, String packageName) { - try { - final boolean gameModeEnabledAngle = - (mGameManager != null) && mGameManager.isAngleEnabled(packageName); - Log.v(TAG, "ANGLE GameManagerService for " + packageName + ": " + gameModeEnabledAngle); - return gameModeEnabledAngle; - } catch (SecurityException e) { - Log.e(TAG, "Caught exception while querying GameManagerService if ANGLE is enabled " - + "for package: " + packageName); - } - - return false; - } - - /** * Query to determine the ANGLE driver choice. */ private String queryAngleChoice(Context context, Bundle coreSettings, @@ -422,8 +405,7 @@ public class GraphicsEnvironment { * 2) The per-application switch (i.e. Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS and * Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES; which corresponds to the * “angle_gl_driver_selection_pkgs” and “angle_gl_driver_selection_values” settings); if it - * forces a choice; - * 3) Use ANGLE if isAngleEnabledByGameMode() returns true; + * forces a choice. */ private String queryAngleChoiceInternal(Context context, Bundle bundle, String packageName) { @@ -457,10 +439,6 @@ public class GraphicsEnvironment { Log.v(TAG, " angle_gl_driver_selection_pkgs=" + optInPackages); Log.v(TAG, " angle_gl_driver_selection_values=" + optInValues); - final String gameModeChoice = isAngleEnabledByGameMode(context, packageName) - ? ANGLE_GL_DRIVER_CHOICE_ANGLE - : ANGLE_GL_DRIVER_CHOICE_DEFAULT; - // Make sure we have good settings to use if (optInPackages.size() == 0 || optInPackages.size() != optInValues.size()) { Log.v(TAG, @@ -469,7 +447,7 @@ public class GraphicsEnvironment { + optInPackages.size() + ", " + "number of values: " + optInValues.size()); - return gameModeChoice; + return ANGLE_GL_DRIVER_CHOICE_DEFAULT; } // See if this application is listed in the per-application settings list @@ -477,7 +455,7 @@ public class GraphicsEnvironment { if (pkgIndex < 0) { Log.v(TAG, packageName + " is not listed in per-application setting"); - return gameModeChoice; + return ANGLE_GL_DRIVER_CHOICE_DEFAULT; } mAngleOptInIndex = pkgIndex; @@ -491,11 +469,9 @@ public class GraphicsEnvironment { return ANGLE_GL_DRIVER_CHOICE_ANGLE; } else if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) { return ANGLE_GL_DRIVER_CHOICE_NATIVE; - } else { - // The user either chose default or an invalid value; go with the default driver or what - // the game mode indicates - return gameModeChoice; } + // The user either chose default or an invalid value; go with the default driver. + return ANGLE_GL_DRIVER_CHOICE_DEFAULT; } /** diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java index 977ef60c43b6..bcea7978a804 100644 --- a/core/java/android/os/PerformanceHintManager.java +++ b/core/java/android/os/PerformanceHintManager.java @@ -55,15 +55,20 @@ public final class PerformanceHintManager { * duration. * * @param tids The list of threads to be associated with this session. They must be part of - * this process' thread group. + * this process' thread group * @param initialTargetWorkDurationNanos The desired duration in nanoseconds for the new - * session. + * session * @return the new session if it is supported on this device, null if hint session is not - * supported on this device. + * supported on this device or the tid doesn't belong to the application + * @throws IllegalArgumentException if the thread id list is empty, or + * initialTargetWorkDurationNanos is non-positive */ @Nullable public Session createHintSession(@NonNull int[] tids, long initialTargetWorkDurationNanos) { - Preconditions.checkNotNull(tids, "tids cannot be null"); + Objects.requireNonNull(tids, "tids cannot be null"); + if (tids.length == 0) { + throw new IllegalArgumentException("thread id list can't be empty."); + } Preconditions.checkArgumentPositive(initialTargetWorkDurationNanos, "the hint target duration should be positive."); long nativeSessionPtr = nativeCreateSession(mNativeManagerPtr, tids, @@ -75,7 +80,7 @@ public final class PerformanceHintManager { /** * Get preferred update rate information for this device. * - * @return the preferred update rate supported by device software. + * @return the preferred update rate supported by device software */ public long getPreferredUpdateRateNanos() { return nativeGetPreferredUpdateRateNanos(mNativeManagerPtr); @@ -209,7 +214,7 @@ public final class PerformanceHintManager { /** * Sends performance hints to inform the hint session of changes in the workload. * - * @param hint The hint to send to the session. + * @param hint The hint to send to the session * * @hide */ @@ -230,11 +235,11 @@ public final class PerformanceHintManager { * Note that this is not an oneway method. * * @param tids The list of threads to be associated with this session. They must be - * part of this app's thread group. + * part of this app's thread group * - * @throws IllegalStateException if the hint session is not in the foreground. - * @throws IllegalArgumentException if the thread id list is empty. - * @throws SecurityException if any thread id doesn't belong to the application. + * @throws IllegalStateException if the hint session is not in the foreground + * @throws IllegalArgumentException if the thread id list is empty + * @throws SecurityException if any thread id doesn't belong to the application */ public void setThreads(@NonNull int[] tids) { if (mNativeSessionPtr == 0) { diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index c6b9d20b450d..d676509d9317 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -33,7 +33,6 @@ import android.app.PropertyInvalidatedCache; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.service.dreams.Sandman; -import android.sysprop.InitProperties; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; @@ -868,6 +867,8 @@ public final class PowerManager { /** * The 'reason' value used for rebooting userspace. + * + * @deprecated userspace reboot is not supported * @hide */ @SystemApi @@ -1824,16 +1825,18 @@ public final class PowerManager { * <p>This method exists solely for the sake of re-using same logic between {@code PowerManager} * and {@code PowerManagerService}. * + * @deprecated TODO(b/292469129): remove this method. * @hide */ public static boolean isRebootingUserspaceSupportedImpl() { - return InitProperties.is_userspace_reboot_supported().orElse(false); + return false; } /** * Returns {@code true} if this device supports rebooting userspace. + * + * @deprecated userspace reboot is deprecated, this method always returns {@code false}. */ - // TODO(b/138605180): add link to documentation once it's ready. public boolean isRebootingUserspaceSupported() { return isRebootingUserspaceSupportedImpl(); } diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING index dae9b5ea6a43..60622f18fe3b 100644 --- a/core/java/android/os/TEST_MAPPING +++ b/core/java/android/os/TEST_MAPPING @@ -99,13 +99,10 @@ "BatteryStats[^/]*\\.java", "BatteryUsageStats[^/]*\\.java", "PowerComponents\\.java", + "PowerMonitor[^/]*\\.java", "[^/]*BatteryConsumer[^/]*\\.java" ], - "name": "FrameworksServicesTests", - "options": [ - { "include-filter": "com.android.server.power.stats" }, - { "exclude-filter": "com.android.server.power.stats.BatteryStatsTests" } - ] + "name": "PowerStatsTests" }, { "file_patterns": [ diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java index 0527ed692da4..cac7f3b74185 100644 --- a/core/java/android/os/UserHandle.java +++ b/core/java/android/os/UserHandle.java @@ -125,7 +125,8 @@ public final class UserHandle implements Parcelable { public static final int MIN_SECONDARY_USER_ID = 10; /** @hide */ - public static final int MAX_SECONDARY_USER_ID = Integer.MAX_VALUE / UserHandle.PER_USER_RANGE; + public static final int MAX_SECONDARY_USER_ID = + Integer.MAX_VALUE / UserHandle.PER_USER_RANGE - 1; /** * (Arbitrary) user handle cache size. diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 2abf02ec3ed3..bf58eaa47e83 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -19175,6 +19175,14 @@ public final class Settings { * @hide */ public static final String WEAR_LAUNCHER_UI_MODE = "wear_launcher_ui_mode"; + + /** Whether Wear Power Anomaly Service is enabled. + * + * (0 = false, 1 = true) + * @hide + */ + public static final String WEAR_POWER_ANOMALY_SERVICE_ENABLED = + "wear_power_anomaly_service_enabled"; } } diff --git a/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java b/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java deleted file mode 100644 index ad73a53cfd87..000000000000 --- a/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (C) 2021 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.service.selectiontoolbar; - -import static android.view.selectiontoolbar.SelectionToolbarManager.ERROR_DO_NOT_ALLOW_MULTIPLE_TOOL_BAR; -import static android.view.selectiontoolbar.SelectionToolbarManager.NO_TOOLBAR_ID; - -import android.util.Pair; -import android.util.Slog; -import android.util.SparseArray; -import android.view.selectiontoolbar.ShowInfo; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.UUID; - -/** - * The default implementation of {@link SelectionToolbarRenderService}. - * - * <p><b>NOTE:<b/> The requests are handled on the service main thread. - * - * @hide - */ -// TODO(b/214122495): fix class not found then move to system service folder -public final class DefaultSelectionToolbarRenderService extends SelectionToolbarRenderService { - - private static final String TAG = "DefaultSelectionToolbarRenderService"; - - // TODO(b/215497659): handle remove if the client process dies. - // Only show one toolbar, dismiss the old ones and remove from cache - private final SparseArray<Pair<Long, RemoteSelectionToolbar>> mToolbarCache = - new SparseArray<>(); - - /** - * Only allow one package to create one toolbar. - */ - private boolean canShowToolbar(int uid, ShowInfo showInfo) { - if (showInfo.getWidgetToken() != NO_TOOLBAR_ID) { - return true; - } - return mToolbarCache.indexOfKey(uid) < 0; - } - - @Override - public void onShow(int callingUid, ShowInfo showInfo, - SelectionToolbarRenderService.RemoteCallbackWrapper callbackWrapper) { - if (!canShowToolbar(callingUid, showInfo)) { - Slog.e(TAG, "Do not allow multiple toolbar for the app."); - callbackWrapper.onError(ERROR_DO_NOT_ALLOW_MULTIPLE_TOOL_BAR); - return; - } - long widgetToken = showInfo.getWidgetToken() == NO_TOOLBAR_ID - ? UUID.randomUUID().getMostSignificantBits() - : showInfo.getWidgetToken(); - - if (mToolbarCache.indexOfKey(callingUid) < 0) { - RemoteSelectionToolbar toolbar = new RemoteSelectionToolbar(this, - widgetToken, showInfo, - callbackWrapper, this::transferTouch); - mToolbarCache.put(callingUid, new Pair<>(widgetToken, toolbar)); - } - Slog.v(TAG, "onShow() for " + widgetToken); - Pair<Long, RemoteSelectionToolbar> toolbarPair = mToolbarCache.get(callingUid); - if (toolbarPair.first == widgetToken) { - toolbarPair.second.show(showInfo); - } else { - Slog.w(TAG, "onShow() for unknown " + widgetToken); - } - } - - @Override - public void onHide(long widgetToken) { - RemoteSelectionToolbar toolbar = getRemoteSelectionToolbarByTokenLocked(widgetToken); - if (toolbar != null) { - Slog.v(TAG, "onHide() for " + widgetToken); - toolbar.hide(widgetToken); - } - } - - @Override - public void onDismiss(long widgetToken) { - RemoteSelectionToolbar toolbar = getRemoteSelectionToolbarByTokenLocked(widgetToken); - if (toolbar != null) { - Slog.v(TAG, "onDismiss() for " + widgetToken); - toolbar.dismiss(widgetToken); - removeRemoteSelectionToolbarByTokenLocked(widgetToken); - } - } - - @Override - public void onToolbarShowTimeout(int callingUid) { - Slog.w(TAG, "onToolbarShowTimeout for callingUid = " + callingUid); - Pair<Long, RemoteSelectionToolbar> toolbarPair = mToolbarCache.get(callingUid); - if (toolbarPair != null) { - RemoteSelectionToolbar remoteToolbar = toolbarPair.second; - remoteToolbar.dismiss(toolbarPair.first); - remoteToolbar.onToolbarShowTimeout(); - mToolbarCache.remove(callingUid); - } - } - - private RemoteSelectionToolbar getRemoteSelectionToolbarByTokenLocked(long widgetToken) { - for (int i = 0; i < mToolbarCache.size(); i++) { - Pair<Long, RemoteSelectionToolbar> toolbarPair = mToolbarCache.valueAt(i); - if (toolbarPair.first == widgetToken) { - return toolbarPair.second; - } - } - return null; - } - - private void removeRemoteSelectionToolbarByTokenLocked(long widgetToken) { - for (int i = 0; i < mToolbarCache.size(); i++) { - Pair<Long, RemoteSelectionToolbar> toolbarPair = mToolbarCache.valueAt(i); - if (toolbarPair.first == widgetToken) { - mToolbarCache.remove(mToolbarCache.keyAt(i)); - return; - } - } - } - - @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - int size = mToolbarCache.size(); - pw.print("number selectionToolbar: "); pw.println(size); - String pfx = " "; - for (int i = 0; i < size; i++) { - pw.print("#"); pw.println(i); - int callingUid = mToolbarCache.keyAt(i); - pw.print(pfx); pw.print("callingUid: "); pw.println(callingUid); - Pair<Long, RemoteSelectionToolbar> toolbarPair = mToolbarCache.valueAt(i); - RemoteSelectionToolbar selectionToolbar = toolbarPair.second; - pw.print(pfx); pw.print("selectionToolbar: "); - selectionToolbar.dump(pfx, pw); - pw.println(); - } - } -} - diff --git a/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java b/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java deleted file mode 100644 index adc9251d89be..000000000000 --- a/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java +++ /dev/null @@ -1,89 +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.service.selectiontoolbar; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.Rect; -import android.os.IBinder; -import android.util.Log; -import android.view.MotionEvent; -import android.widget.LinearLayout; - -import java.io.PrintWriter; - -/** - * This class is the root view for the selection toolbar. It is responsible for - * detecting the click on the item and to also transfer input focus to the application. - * - * @hide - */ -@SuppressLint("ViewConstructor") -public class FloatingToolbarRoot extends LinearLayout { - - private static final boolean DEBUG = false; - private static final String TAG = "FloatingToolbarRoot"; - - private final IBinder mTargetInputToken; - private final SelectionToolbarRenderService.TransferTouchListener mTransferTouchListener; - private final Rect mContentRect = new Rect(); - - private int mLastDownX = -1; - private int mLastDownY = -1; - - public FloatingToolbarRoot(Context context, IBinder targetInputToken, - SelectionToolbarRenderService.TransferTouchListener transferTouchListener) { - super(context); - mTargetInputToken = targetInputToken; - mTransferTouchListener = transferTouchListener; - setFocusable(false); - } - - /** - * Sets the Rect that shows the selection toolbar content. - */ - public void setContentRect(Rect contentRect) { - mContentRect.set(contentRect); - } - - @Override - @SuppressLint("ClickableViewAccessibility") - public boolean dispatchTouchEvent(MotionEvent event) { - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - mLastDownX = (int) event.getX(); - mLastDownY = (int) event.getY(); - if (DEBUG) { - Log.d(TAG, "downX=" + mLastDownX + " downY=" + mLastDownY); - } - // TODO(b/215497659): Check FLAG_WINDOW_IS_PARTIALLY_OBSCURED - if (!mContentRect.contains(mLastDownX, mLastDownY)) { - if (DEBUG) { - Log.d(TAG, "Transfer touch focus to application."); - } - mTransferTouchListener.onTransferTouch(getViewRootImpl().getInputToken(), - mTargetInputToken); - } - } - return super.dispatchTouchEvent(event); - } - - void dump(String prefix, PrintWriter pw) { - pw.print(prefix); pw.println("FloatingToolbarRoot:"); - pw.print(prefix + " "); pw.print("last down X: "); pw.println(mLastDownX); - pw.print(prefix + " "); pw.print("last down Y: "); pw.println(mLastDownY); - } -} diff --git a/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderService.aidl b/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderService.aidl deleted file mode 100644 index 79281b8b361d..000000000000 --- a/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderService.aidl +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2021 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.service.selectiontoolbar; - -import android.view.selectiontoolbar.ISelectionToolbarCallback; -import android.view.selectiontoolbar.ShowInfo; - -/** - * The service to render the selection toolbar menus. - * - * @hide - */ -oneway interface ISelectionToolbarRenderService { - void onConnected(in IBinder callback); - void onShow(int callingUid, in ShowInfo showInfo, in ISelectionToolbarCallback callback); - void onHide(long widgetToken); - void onDismiss(int callingUid, long widgetToken); -} diff --git a/core/java/android/service/selectiontoolbar/OWNERS b/core/java/android/service/selectiontoolbar/OWNERS deleted file mode 100644 index 5500b92868dd..000000000000 --- a/core/java/android/service/selectiontoolbar/OWNERS +++ /dev/null @@ -1,10 +0,0 @@ -# Bug component: 709498 - -augale@google.com -joannechung@google.com -licha@google.com -lpeter@google.com -svetoslavganov@google.com -toki@google.com -tonymak@google.com -tymtsai@google.com
\ No newline at end of file diff --git a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java deleted file mode 100644 index 59e3a5e70376..000000000000 --- a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java +++ /dev/null @@ -1,1398 +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.service.selectiontoolbar; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.content.Context; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.drawable.AnimatedVectorDrawable; -import android.graphics.drawable.Drawable; -import android.os.IBinder; -import android.text.TextUtils; -import android.util.Log; -import android.util.Size; -import android.view.ContextThemeWrapper; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.SurfaceControlViewHost; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.view.animation.Animation; -import android.view.animation.AnimationSet; -import android.view.animation.AnimationUtils; -import android.view.animation.Interpolator; -import android.view.animation.Transformation; -import android.view.selectiontoolbar.ShowInfo; -import android.view.selectiontoolbar.ToolbarMenuItem; -import android.view.selectiontoolbar.WidgetInfo; -import android.widget.ArrayAdapter; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.TextView; - -import com.android.internal.R; -import com.android.internal.util.Preconditions; -import com.android.internal.widget.floatingtoolbar.FloatingToolbar; - -import java.io.PrintWriter; -import java.util.LinkedList; -import java.util.List; -import java.util.Objects; - -/** - * This class is responsible for rendering/animation of the selection toolbar in the remote - * system process. It holds 2 panels (i.e. main panel and overflow panel) and an overflow - * button to transition between panels. - * - * @hide - */ -// TODO(b/215497659): share code with LocalFloatingToolbarPopup -final class RemoteSelectionToolbar { - private static final String TAG = "RemoteSelectionToolbar"; - - /* Minimum and maximum number of items allowed in the overflow. */ - private static final int MIN_OVERFLOW_SIZE = 2; - private static final int MAX_OVERFLOW_SIZE = 4; - - private final Context mContext; - - /* Margins between the popup window and its content. */ - private final int mMarginHorizontal; - private final int mMarginVertical; - - /* View components */ - private final ViewGroup mContentContainer; // holds all contents. - private final ViewGroup mMainPanel; // holds menu items that are initially displayed. - // holds menu items hidden in the overflow. - private final OverflowPanel mOverflowPanel; - private final ImageButton mOverflowButton; // opens/closes the overflow. - /* overflow button drawables. */ - private final Drawable mArrow; - private final Drawable mOverflow; - private final AnimatedVectorDrawable mToArrow; - private final AnimatedVectorDrawable mToOverflow; - - private final OverflowPanelViewHelper mOverflowPanelViewHelper; - - /* Animation interpolators. */ - private final Interpolator mLogAccelerateInterpolator; - private final Interpolator mFastOutSlowInInterpolator; - private final Interpolator mLinearOutSlowInInterpolator; - private final Interpolator mFastOutLinearInInterpolator; - - /* Animations. */ - private final AnimatorSet mShowAnimation; - private final AnimatorSet mDismissAnimation; - private final AnimatorSet mHideAnimation; - private final AnimationSet mOpenOverflowAnimation; - private final AnimationSet mCloseOverflowAnimation; - private final Animation.AnimationListener mOverflowAnimationListener; - - private final Rect mViewPortOnScreen = new Rect(); // portion of screen we can draw in. - - private final int mLineHeight; - private final int mIconTextSpacing; - - private final long mSelectionToolbarToken; - private IBinder mHostInputToken; - private final SelectionToolbarRenderService.RemoteCallbackWrapper mCallbackWrapper; - private final SelectionToolbarRenderService.TransferTouchListener mTransferTouchListener; - private int mPopupWidth; - private int mPopupHeight; - // Coordinates to show the toolbar relative to the specified view port - private final Point mRelativeCoordsForToolbar = new Point(); - private List<ToolbarMenuItem> mMenuItems; - private SurfaceControlViewHost mSurfaceControlViewHost; - private SurfaceControlViewHost.SurfacePackage mSurfacePackage; - - /** - * @see OverflowPanelViewHelper#preparePopupContent(). - */ - private final Runnable mPreparePopupContentRTLHelper = new Runnable() { - @Override - public void run() { - setPanelsStatesAtRestingPosition(); - mContentContainer.setAlpha(1); - } - }; - - private boolean mDismissed = true; // tracks whether this popup is dismissed or dismissing. - private boolean mHidden; // tracks whether this popup is hidden or hiding. - - /* Calculated sizes for panels and overflow button. */ - private final Size mOverflowButtonSize; - private Size mOverflowPanelSize; // Should be null when there is no overflow. - private Size mMainPanelSize; - - /* Menu items and click listeners */ - private final View.OnClickListener mMenuItemButtonOnClickListener; - - private boolean mOpenOverflowUpwards; // Whether the overflow opens upwards or downwards. - private boolean mIsOverflowOpen; - - private int mTransitionDurationScale; // Used to scale the toolbar transition duration. - - private final Rect mPreviousContentRect = new Rect(); - - private final Rect mTempContentRect = new Rect(); - private final Rect mTempContentRectForRoot = new Rect(); - private final int[] mTempCoords = new int[2]; - - RemoteSelectionToolbar(Context context, long selectionToolbarToken, ShowInfo showInfo, - SelectionToolbarRenderService.RemoteCallbackWrapper callbackWrapper, - SelectionToolbarRenderService.TransferTouchListener transferTouchListener) { - mContext = applyDefaultTheme(context, showInfo.isIsLightTheme()); - mSelectionToolbarToken = selectionToolbarToken; - mCallbackWrapper = callbackWrapper; - mTransferTouchListener = transferTouchListener; - mHostInputToken = showInfo.getHostInputToken(); - mContentContainer = createContentContainer(mContext); - mMarginHorizontal = mContext.getResources() - .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin); - mMarginVertical = mContext.getResources() - .getDimensionPixelSize(R.dimen.floating_toolbar_vertical_margin); - mLineHeight = mContext.getResources() - .getDimensionPixelSize(R.dimen.floating_toolbar_height); - mIconTextSpacing = mContext.getResources() - .getDimensionPixelSize(R.dimen.floating_toolbar_icon_text_spacing); - - // Interpolators - mLogAccelerateInterpolator = new LogAccelerateInterpolator(); - mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator( - mContext, android.R.interpolator.fast_out_slow_in); - mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator( - mContext, android.R.interpolator.linear_out_slow_in); - mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator( - mContext, android.R.interpolator.fast_out_linear_in); - - // Drawables. Needed for views. - mArrow = mContext.getResources() - .getDrawable(R.drawable.ft_avd_tooverflow, mContext.getTheme()); - mArrow.setAutoMirrored(true); - mOverflow = mContext.getResources() - .getDrawable(R.drawable.ft_avd_toarrow, mContext.getTheme()); - mOverflow.setAutoMirrored(true); - mToArrow = (AnimatedVectorDrawable) mContext.getResources() - .getDrawable(R.drawable.ft_avd_toarrow_animation, mContext.getTheme()); - mToArrow.setAutoMirrored(true); - mToOverflow = (AnimatedVectorDrawable) mContext.getResources() - .getDrawable(R.drawable.ft_avd_tooverflow_animation, mContext.getTheme()); - mToOverflow.setAutoMirrored(true); - - // Views - mOverflowButton = createOverflowButton(); - mOverflowButtonSize = measure(mOverflowButton); - mMainPanel = createMainPanel(); - mOverflowPanelViewHelper = new OverflowPanelViewHelper(mContext, mIconTextSpacing); - mOverflowPanel = createOverflowPanel(); - - // Animation. Need views. - mOverflowAnimationListener = createOverflowAnimationListener(); - mOpenOverflowAnimation = new AnimationSet(true); - mOpenOverflowAnimation.setAnimationListener(mOverflowAnimationListener); - mCloseOverflowAnimation = new AnimationSet(true); - mCloseOverflowAnimation.setAnimationListener(mOverflowAnimationListener); - mShowAnimation = createEnterAnimation(mContentContainer, - new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - updateFloatingToolbarRootContentRect(); - } - }); - mDismissAnimation = createExitAnimation( - mContentContainer, - 150, // startDelay - new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - // TODO(b/215497659): should dismiss window after animation - mContentContainer.removeAllViews(); - mSurfaceControlViewHost.release(); - mSurfaceControlViewHost = null; - mSurfacePackage = null; - } - }); - mHideAnimation = createExitAnimation( - mContentContainer, - 0, // startDelay - null); // TODO(b/215497659): should handle hide after animation - mMenuItemButtonOnClickListener = v -> { - Object tag = v.getTag(); - if (!(tag instanceof ToolbarMenuItem)) { - return; - } - mCallbackWrapper.onMenuItemClicked((ToolbarMenuItem) tag); - }; - } - - private void updateFloatingToolbarRootContentRect() { - if (mSurfaceControlViewHost == null) { - return; - } - final FloatingToolbarRoot root = (FloatingToolbarRoot) mSurfaceControlViewHost.getView(); - mContentContainer.getLocationOnScreen(mTempCoords); - int contentLeft = mTempCoords[0]; - int contentTop = mTempCoords[1]; - mTempContentRectForRoot.set(contentLeft, contentTop, - contentLeft + mContentContainer.getWidth(), - contentTop + mContentContainer.getHeight()); - root.setContentRect(mTempContentRectForRoot); - } - - private WidgetInfo createWidgetInfo() { - mTempContentRect.set(mRelativeCoordsForToolbar.x, mRelativeCoordsForToolbar.y, - mRelativeCoordsForToolbar.x + mPopupWidth, - mRelativeCoordsForToolbar.y + mPopupHeight); - return new WidgetInfo(mSelectionToolbarToken, mTempContentRect, getSurfacePackage()); - } - - private SurfaceControlViewHost.SurfacePackage getSurfacePackage() { - if (mSurfaceControlViewHost == null) { - final FloatingToolbarRoot contentHolder = new FloatingToolbarRoot(mContext, - mHostInputToken, mTransferTouchListener); - contentHolder.addView(mContentContainer); - mSurfaceControlViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), - mHostInputToken, "RemoteSelectionToolbar"); - mSurfaceControlViewHost.setView(contentHolder, mPopupWidth, mPopupHeight); - } - if (mSurfacePackage == null) { - mSurfacePackage = mSurfaceControlViewHost.getSurfacePackage(); - } - return mSurfacePackage; - } - - private void layoutMenuItems( - List<ToolbarMenuItem> menuItems, - int suggestedWidth) { - cancelOverflowAnimations(); - clearPanels(); - - menuItems = layoutMainPanelItems(menuItems, getAdjustedToolbarWidth(suggestedWidth)); - if (!menuItems.isEmpty()) { - // Add remaining items to the overflow. - layoutOverflowPanelItems(menuItems); - } - updatePopupSize(); - } - - public void onToolbarShowTimeout() { - mCallbackWrapper.onToolbarShowTimeout(); - } - - /** - * Show the specified selection toolbar. - */ - public void show(ShowInfo showInfo) { - debugLog("show() for " + showInfo); - - mMenuItems = showInfo.getMenuItems(); - mViewPortOnScreen.set(showInfo.getViewPortOnScreen()); - - debugLog("show(): layoutRequired=" + showInfo.isLayoutRequired()); - if (showInfo.isLayoutRequired()) { - layoutMenuItems(mMenuItems, showInfo.getSuggestedWidth()); - } - Rect contentRect = showInfo.getContentRect(); - if (!isShowing()) { - show(contentRect); - } else if (!mPreviousContentRect.equals(contentRect)) { - updateCoordinates(contentRect); - } - mPreviousContentRect.set(contentRect); - } - - private void show(Rect contentRectOnScreen) { - Objects.requireNonNull(contentRectOnScreen); - - mHidden = false; - mDismissed = false; - cancelDismissAndHideAnimations(); - cancelOverflowAnimations(); - refreshCoordinatesAndOverflowDirection(contentRectOnScreen); - preparePopupContent(); - mCallbackWrapper.onShown(createWidgetInfo()); - // TODO(b/215681595): Use Choreographer to coordinate for show between different thread - mShowAnimation.start(); - } - - /** - * Dismiss the specified selection toolbar. - */ - public void dismiss(long floatingToolbarToken) { - debugLog("dismiss for " + floatingToolbarToken); - if (mDismissed) { - return; - } - mHidden = false; - mDismissed = true; - - mHideAnimation.cancel(); - mDismissAnimation.start(); - } - - /** - * Hide the specified selection toolbar. - */ - public void hide(long floatingToolbarToken) { - debugLog("hide for " + floatingToolbarToken); - if (!isShowing()) { - return; - } - - mHidden = true; - mHideAnimation.start(); - } - - public boolean isShowing() { - return !mDismissed && !mHidden; - } - - private void updateCoordinates(Rect contentRectOnScreen) { - Objects.requireNonNull(contentRectOnScreen); - - if (!isShowing()) { - return; - } - cancelOverflowAnimations(); - refreshCoordinatesAndOverflowDirection(contentRectOnScreen); - preparePopupContent(); - WidgetInfo widgetInfo = createWidgetInfo(); - mSurfaceControlViewHost.relayout(mPopupWidth, mPopupHeight); - mCallbackWrapper.onWidgetUpdated(widgetInfo); - } - - private void refreshCoordinatesAndOverflowDirection(Rect contentRectOnScreen) { - // Initialize x ensuring that the toolbar isn't rendered behind the nav bar in - // landscape. - final int x = Math.min( - contentRectOnScreen.centerX() - mPopupWidth / 2, - mViewPortOnScreen.right - mPopupWidth); - - final int y; - - final int availableHeightAboveContent = - contentRectOnScreen.top - mViewPortOnScreen.top; - final int availableHeightBelowContent = - mViewPortOnScreen.bottom - contentRectOnScreen.bottom; - - final int margin = 2 * mMarginVertical; - final int toolbarHeightWithVerticalMargin = mLineHeight + margin; - - if (!hasOverflow()) { - if (availableHeightAboveContent >= toolbarHeightWithVerticalMargin) { - // There is enough space at the top of the content. - y = contentRectOnScreen.top - toolbarHeightWithVerticalMargin; - } else if (availableHeightBelowContent >= toolbarHeightWithVerticalMargin) { - // There is enough space at the bottom of the content. - y = contentRectOnScreen.bottom; - } else if (availableHeightBelowContent >= mLineHeight) { - // Just enough space to fit the toolbar with no vertical margins. - y = contentRectOnScreen.bottom - mMarginVertical; - } else { - // Not enough space. Prefer to position as high as possible. - y = Math.max( - mViewPortOnScreen.top, - contentRectOnScreen.top - toolbarHeightWithVerticalMargin); - } - } else { - // Has an overflow. - final int minimumOverflowHeightWithMargin = - calculateOverflowHeight(MIN_OVERFLOW_SIZE) + margin; - final int availableHeightThroughContentDown = - mViewPortOnScreen.bottom - contentRectOnScreen.top - + toolbarHeightWithVerticalMargin; - final int availableHeightThroughContentUp = - contentRectOnScreen.bottom - mViewPortOnScreen.top - + toolbarHeightWithVerticalMargin; - - if (availableHeightAboveContent >= minimumOverflowHeightWithMargin) { - // There is enough space at the top of the content rect for the overflow. - // Position above and open upwards. - updateOverflowHeight(availableHeightAboveContent - margin); - y = contentRectOnScreen.top - mPopupHeight; - mOpenOverflowUpwards = true; - } else if (availableHeightAboveContent >= toolbarHeightWithVerticalMargin - && availableHeightThroughContentDown >= minimumOverflowHeightWithMargin) { - // There is enough space at the top of the content rect for the main panel - // but not the overflow. - // Position above but open downwards. - updateOverflowHeight(availableHeightThroughContentDown - margin); - y = contentRectOnScreen.top - toolbarHeightWithVerticalMargin; - mOpenOverflowUpwards = false; - } else if (availableHeightBelowContent >= minimumOverflowHeightWithMargin) { - // There is enough space at the bottom of the content rect for the overflow. - // Position below and open downwards. - updateOverflowHeight(availableHeightBelowContent - margin); - y = contentRectOnScreen.bottom; - mOpenOverflowUpwards = false; - } else if (availableHeightBelowContent >= toolbarHeightWithVerticalMargin - && mViewPortOnScreen.height() >= minimumOverflowHeightWithMargin) { - // There is enough space at the bottom of the content rect for the main panel - // but not the overflow. - // Position below but open upwards. - updateOverflowHeight(availableHeightThroughContentUp - margin); - y = contentRectOnScreen.bottom + toolbarHeightWithVerticalMargin - - mPopupHeight; - mOpenOverflowUpwards = true; - } else { - // Not enough space. - // Position at the top of the view port and open downwards. - updateOverflowHeight(mViewPortOnScreen.height() - margin); - y = mViewPortOnScreen.top; - mOpenOverflowUpwards = false; - } - } - mRelativeCoordsForToolbar.set(x, y); - } - - private void cancelDismissAndHideAnimations() { - mDismissAnimation.cancel(); - mHideAnimation.cancel(); - } - - private void cancelOverflowAnimations() { - mContentContainer.clearAnimation(); - mMainPanel.animate().cancel(); - mOverflowPanel.animate().cancel(); - mToArrow.stop(); - mToOverflow.stop(); - } - - private void openOverflow() { - final int targetWidth = mOverflowPanelSize.getWidth(); - final int targetHeight = mOverflowPanelSize.getHeight(); - final int startWidth = mContentContainer.getWidth(); - final int startHeight = mContentContainer.getHeight(); - final float startY = mContentContainer.getY(); - final float left = mContentContainer.getX(); - final float right = left + mContentContainer.getWidth(); - Animation widthAnimation = new Animation() { - @Override - protected void applyTransformation(float interpolatedTime, Transformation t) { - int deltaWidth = (int) (interpolatedTime * (targetWidth - startWidth)); - setWidth(mContentContainer, startWidth + deltaWidth); - if (isInRTLMode()) { - mContentContainer.setX(left); - - // Lock the panels in place. - mMainPanel.setX(0); - mOverflowPanel.setX(0); - } else { - mContentContainer.setX(right - mContentContainer.getWidth()); - - // Offset the panels' positions so they look like they're locked in place - // on the screen. - mMainPanel.setX(mContentContainer.getWidth() - startWidth); - mOverflowPanel.setX(mContentContainer.getWidth() - targetWidth); - } - } - }; - Animation heightAnimation = new Animation() { - @Override - protected void applyTransformation(float interpolatedTime, Transformation t) { - int deltaHeight = (int) (interpolatedTime * (targetHeight - startHeight)); - setHeight(mContentContainer, startHeight + deltaHeight); - if (mOpenOverflowUpwards) { - mContentContainer.setY( - startY - (mContentContainer.getHeight() - startHeight)); - positionContentYCoordinatesIfOpeningOverflowUpwards(); - } - } - }; - final float overflowButtonStartX = mOverflowButton.getX(); - final float overflowButtonTargetX = - isInRTLMode() ? overflowButtonStartX + targetWidth - mOverflowButton.getWidth() - : overflowButtonStartX - targetWidth + mOverflowButton.getWidth(); - Animation overflowButtonAnimation = new Animation() { - @Override - protected void applyTransformation(float interpolatedTime, Transformation t) { - float overflowButtonX = overflowButtonStartX - + interpolatedTime * (overflowButtonTargetX - overflowButtonStartX); - float deltaContainerWidth = - isInRTLMode() ? 0 : mContentContainer.getWidth() - startWidth; - float actualOverflowButtonX = overflowButtonX + deltaContainerWidth; - mOverflowButton.setX(actualOverflowButtonX); - updateFloatingToolbarRootContentRect(); - } - }; - widthAnimation.setInterpolator(mLogAccelerateInterpolator); - widthAnimation.setDuration(getAnimationDuration()); - heightAnimation.setInterpolator(mFastOutSlowInInterpolator); - heightAnimation.setDuration(getAnimationDuration()); - overflowButtonAnimation.setInterpolator(mFastOutSlowInInterpolator); - overflowButtonAnimation.setDuration(getAnimationDuration()); - mOpenOverflowAnimation.getAnimations().clear(); - mOpenOverflowAnimation.addAnimation(widthAnimation); - mOpenOverflowAnimation.addAnimation(heightAnimation); - mOpenOverflowAnimation.addAnimation(overflowButtonAnimation); - mContentContainer.startAnimation(mOpenOverflowAnimation); - mIsOverflowOpen = true; - mMainPanel.animate() - .alpha(0).withLayer() - .setInterpolator(mLinearOutSlowInInterpolator) - .setDuration(250) - .start(); - mOverflowPanel.setAlpha(1); // fadeIn in 0ms. - } - - private void closeOverflow() { - final int targetWidth = mMainPanelSize.getWidth(); - final int startWidth = mContentContainer.getWidth(); - final float left = mContentContainer.getX(); - final float right = left + mContentContainer.getWidth(); - Animation widthAnimation = new Animation() { - @Override - protected void applyTransformation(float interpolatedTime, Transformation t) { - int deltaWidth = (int) (interpolatedTime * (targetWidth - startWidth)); - setWidth(mContentContainer, startWidth + deltaWidth); - if (isInRTLMode()) { - mContentContainer.setX(left); - - // Lock the panels in place. - mMainPanel.setX(0); - mOverflowPanel.setX(0); - } else { - mContentContainer.setX(right - mContentContainer.getWidth()); - - // Offset the panels' positions so they look like they're locked in place - // on the screen. - mMainPanel.setX(mContentContainer.getWidth() - targetWidth); - mOverflowPanel.setX(mContentContainer.getWidth() - startWidth); - } - } - }; - final int targetHeight = mMainPanelSize.getHeight(); - final int startHeight = mContentContainer.getHeight(); - final float bottom = mContentContainer.getY() + mContentContainer.getHeight(); - Animation heightAnimation = new Animation() { - @Override - protected void applyTransformation(float interpolatedTime, Transformation t) { - int deltaHeight = (int) (interpolatedTime * (targetHeight - startHeight)); - setHeight(mContentContainer, startHeight + deltaHeight); - if (mOpenOverflowUpwards) { - mContentContainer.setY(bottom - mContentContainer.getHeight()); - positionContentYCoordinatesIfOpeningOverflowUpwards(); - } - } - }; - final float overflowButtonStartX = mOverflowButton.getX(); - final float overflowButtonTargetX = - isInRTLMode() ? overflowButtonStartX - startWidth + mOverflowButton.getWidth() - : overflowButtonStartX + startWidth - mOverflowButton.getWidth(); - Animation overflowButtonAnimation = new Animation() { - @Override - protected void applyTransformation(float interpolatedTime, Transformation t) { - float overflowButtonX = overflowButtonStartX - + interpolatedTime * (overflowButtonTargetX - overflowButtonStartX); - float deltaContainerWidth = - isInRTLMode() ? 0 : mContentContainer.getWidth() - startWidth; - float actualOverflowButtonX = overflowButtonX + deltaContainerWidth; - mOverflowButton.setX(actualOverflowButtonX); - updateFloatingToolbarRootContentRect(); - } - }; - widthAnimation.setInterpolator(mFastOutSlowInInterpolator); - widthAnimation.setDuration(getAnimationDuration()); - heightAnimation.setInterpolator(mLogAccelerateInterpolator); - heightAnimation.setDuration(getAnimationDuration()); - overflowButtonAnimation.setInterpolator(mFastOutSlowInInterpolator); - overflowButtonAnimation.setDuration(getAnimationDuration()); - mCloseOverflowAnimation.getAnimations().clear(); - mCloseOverflowAnimation.addAnimation(widthAnimation); - mCloseOverflowAnimation.addAnimation(heightAnimation); - mCloseOverflowAnimation.addAnimation(overflowButtonAnimation); - mContentContainer.startAnimation(mCloseOverflowAnimation); - mIsOverflowOpen = false; - mMainPanel.animate() - .alpha(1).withLayer() - .setInterpolator(mFastOutLinearInInterpolator) - .setDuration(100) - .start(); - mOverflowPanel.animate() - .alpha(0).withLayer() - .setInterpolator(mLinearOutSlowInInterpolator) - .setDuration(150) - .start(); - } - - /** - * Defines the position of the floating toolbar popup panels when transition animation has - * stopped. - */ - private void setPanelsStatesAtRestingPosition() { - mOverflowButton.setEnabled(true); - mOverflowPanel.awakenScrollBars(); - - if (mIsOverflowOpen) { - // Set open state. - final Size containerSize = mOverflowPanelSize; - setSize(mContentContainer, containerSize); - mMainPanel.setAlpha(0); - mMainPanel.setVisibility(View.INVISIBLE); - mOverflowPanel.setAlpha(1); - mOverflowPanel.setVisibility(View.VISIBLE); - mOverflowButton.setImageDrawable(mArrow); - mOverflowButton.setContentDescription(mContext.getString( - R.string.floating_toolbar_close_overflow_description)); - - // Update x-coordinates depending on RTL state. - if (isInRTLMode()) { - mContentContainer.setX(mMarginHorizontal); // align left - mMainPanel.setX(0); // align left - mOverflowButton.setX(// align right - containerSize.getWidth() - mOverflowButtonSize.getWidth()); - mOverflowPanel.setX(0); // align left - } else { - mContentContainer.setX(// align right - mPopupWidth - containerSize.getWidth() - mMarginHorizontal); - mMainPanel.setX(-mContentContainer.getX()); // align right - mOverflowButton.setX(0); // align left - mOverflowPanel.setX(0); // align left - } - - // Update y-coordinates depending on overflow's open direction. - if (mOpenOverflowUpwards) { - mContentContainer.setY(mMarginVertical); // align top - mMainPanel.setY(// align bottom - containerSize.getHeight() - mContentContainer.getHeight()); - mOverflowButton.setY(// align bottom - containerSize.getHeight() - mOverflowButtonSize.getHeight()); - mOverflowPanel.setY(0); // align top - } else { - // opens downwards. - mContentContainer.setY(mMarginVertical); // align top - mMainPanel.setY(0); // align top - mOverflowButton.setY(0); // align top - mOverflowPanel.setY(mOverflowButtonSize.getHeight()); // align bottom - } - } else { - // Overflow not open. Set closed state. - final Size containerSize = mMainPanelSize; - setSize(mContentContainer, containerSize); - mMainPanel.setAlpha(1); - mMainPanel.setVisibility(View.VISIBLE); - mOverflowPanel.setAlpha(0); - mOverflowPanel.setVisibility(View.INVISIBLE); - mOverflowButton.setImageDrawable(mOverflow); - mOverflowButton.setContentDescription(mContext.getString( - R.string.floating_toolbar_open_overflow_description)); - - if (hasOverflow()) { - // Update x-coordinates depending on RTL state. - if (isInRTLMode()) { - mContentContainer.setX(mMarginHorizontal); // align left - mMainPanel.setX(0); // align left - mOverflowButton.setX(0); // align left - mOverflowPanel.setX(0); // align left - } else { - mContentContainer.setX(// align right - mPopupWidth - containerSize.getWidth() - mMarginHorizontal); - mMainPanel.setX(0); // align left - mOverflowButton.setX(// align right - containerSize.getWidth() - mOverflowButtonSize.getWidth()); - mOverflowPanel.setX(// align right - containerSize.getWidth() - mOverflowPanelSize.getWidth()); - } - - // Update y-coordinates depending on overflow's open direction. - if (mOpenOverflowUpwards) { - mContentContainer.setY(// align bottom - mMarginVertical + mOverflowPanelSize.getHeight() - - containerSize.getHeight()); - mMainPanel.setY(0); // align top - mOverflowButton.setY(0); // align top - mOverflowPanel.setY(// align bottom - containerSize.getHeight() - mOverflowPanelSize.getHeight()); - } else { - // opens downwards. - mContentContainer.setY(mMarginVertical); // align top - mMainPanel.setY(0); // align top - mOverflowButton.setY(0); // align top - mOverflowPanel.setY(mOverflowButtonSize.getHeight()); // align bottom - } - } else { - // No overflow. - mContentContainer.setX(mMarginHorizontal); // align left - mContentContainer.setY(mMarginVertical); // align top - mMainPanel.setX(0); // align left - mMainPanel.setY(0); // align top - } - } - } - - private void updateOverflowHeight(int suggestedHeight) { - if (hasOverflow()) { - final int maxItemSize = - (suggestedHeight - mOverflowButtonSize.getHeight()) / mLineHeight; - final int newHeight = calculateOverflowHeight(maxItemSize); - if (mOverflowPanelSize.getHeight() != newHeight) { - mOverflowPanelSize = new Size(mOverflowPanelSize.getWidth(), newHeight); - } - setSize(mOverflowPanel, mOverflowPanelSize); - if (mIsOverflowOpen) { - setSize(mContentContainer, mOverflowPanelSize); - if (mOpenOverflowUpwards) { - final int deltaHeight = mOverflowPanelSize.getHeight() - newHeight; - mContentContainer.setY(mContentContainer.getY() + deltaHeight); - mOverflowButton.setY(mOverflowButton.getY() - deltaHeight); - } - } else { - setSize(mContentContainer, mMainPanelSize); - } - updatePopupSize(); - } - } - - private void updatePopupSize() { - int width = 0; - int height = 0; - if (mMainPanelSize != null) { - width = Math.max(width, mMainPanelSize.getWidth()); - height = Math.max(height, mMainPanelSize.getHeight()); - } - if (mOverflowPanelSize != null) { - width = Math.max(width, mOverflowPanelSize.getWidth()); - height = Math.max(height, mOverflowPanelSize.getHeight()); - } - - mPopupWidth = width + mMarginHorizontal * 2; - mPopupHeight = height + mMarginVertical * 2; - maybeComputeTransitionDurationScale(); - } - - private int getAdjustedToolbarWidth(int suggestedWidth) { - int width = suggestedWidth; - int maximumWidth = mViewPortOnScreen.width() - 2 * mContext.getResources() - .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin); - if (width <= 0) { - width = mContext.getResources() - .getDimensionPixelSize(R.dimen.floating_toolbar_preferred_width); - } - return Math.min(width, maximumWidth); - } - - private boolean isInRTLMode() { - return mContext.getApplicationInfo().hasRtlSupport() - && mContext.getResources().getConfiguration().getLayoutDirection() - == View.LAYOUT_DIRECTION_RTL; - } - - private boolean hasOverflow() { - return mOverflowPanelSize != null; - } - - /** - * Fits as many menu items in the main panel and returns a list of the menu items that - * were not fit in. - * - * @return The menu items that are not included in this main panel. - */ - private List<ToolbarMenuItem> layoutMainPanelItems(List<ToolbarMenuItem> menuItems, - int toolbarWidth) { - final LinkedList<ToolbarMenuItem> remainingMenuItems = new LinkedList<>(); - // add the overflow menu items to the end of the remainingMenuItems list. - final LinkedList<ToolbarMenuItem> overflowMenuItems = new LinkedList(); - for (ToolbarMenuItem menuItem : menuItems) { - if (menuItem.getItemId() != android.R.id.textAssist - && menuItem.getPriority() == ToolbarMenuItem.PRIORITY_OVERFLOW) { - overflowMenuItems.add(menuItem); - } else { - remainingMenuItems.add(menuItem); - } - } - remainingMenuItems.addAll(overflowMenuItems); - - mMainPanel.removeAllViews(); - mMainPanel.setPaddingRelative(0, 0, 0, 0); - - int availableWidth = toolbarWidth; - boolean isFirstItem = true; - while (!remainingMenuItems.isEmpty()) { - ToolbarMenuItem menuItem = remainingMenuItems.peek(); - // if this is the first item, regardless of requiresOverflow(), it should be - // displayed on the main panel. Otherwise all items including this one will be - // overflow items, and should be displayed in overflow panel. - if (!isFirstItem && menuItem.getPriority() == ToolbarMenuItem.PRIORITY_OVERFLOW) { - break; - } - final boolean showIcon = isFirstItem && menuItem.getItemId() == R.id.textAssist; - final View menuItemButton = createMenuItemButton( - mContext, menuItem, mIconTextSpacing, showIcon); - if (!showIcon && menuItemButton instanceof LinearLayout) { - ((LinearLayout) menuItemButton).setGravity(Gravity.CENTER); - } - // Adding additional start padding for the first button to even out button spacing. - if (isFirstItem) { - menuItemButton.setPaddingRelative( - (int) (1.5 * menuItemButton.getPaddingStart()), - menuItemButton.getPaddingTop(), - menuItemButton.getPaddingEnd(), - menuItemButton.getPaddingBottom()); - } - // Adding additional end padding for the last button to even out button spacing. - boolean isLastItem = remainingMenuItems.size() == 1; - if (isLastItem) { - menuItemButton.setPaddingRelative( - menuItemButton.getPaddingStart(), - menuItemButton.getPaddingTop(), - (int) (1.5 * menuItemButton.getPaddingEnd()), - menuItemButton.getPaddingBottom()); - } - menuItemButton.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); - final int menuItemButtonWidth = Math.min( - menuItemButton.getMeasuredWidth(), toolbarWidth); - // Check if we can fit an item while reserving space for the overflowButton. - final boolean canFitWithOverflow = - menuItemButtonWidth <= availableWidth - mOverflowButtonSize.getWidth(); - final boolean canFitNoOverflow = - isLastItem && menuItemButtonWidth <= availableWidth; - if (canFitWithOverflow || canFitNoOverflow) { - menuItemButton.setTag(menuItem); - menuItemButton.setOnClickListener(mMenuItemButtonOnClickListener); - // Set tooltips for main panel items, but not overflow items (b/35726766). - menuItemButton.setTooltipText(menuItem.getTooltipText()); - mMainPanel.addView(menuItemButton); - final ViewGroup.LayoutParams params = menuItemButton.getLayoutParams(); - params.width = menuItemButtonWidth; - menuItemButton.setLayoutParams(params); - availableWidth -= menuItemButtonWidth; - remainingMenuItems.pop(); - } else { - break; - } - isFirstItem = false; - } - if (!remainingMenuItems.isEmpty()) { - // Reserve space for overflowButton. - mMainPanel.setPaddingRelative(0, 0, mOverflowButtonSize.getWidth(), 0); - } - mMainPanelSize = measure(mMainPanel); - - return remainingMenuItems; - } - - private void layoutOverflowPanelItems(List<ToolbarMenuItem> menuItems) { - ArrayAdapter<ToolbarMenuItem> overflowPanelAdapter = - (ArrayAdapter<ToolbarMenuItem>) mOverflowPanel.getAdapter(); - overflowPanelAdapter.clear(); - final int size = menuItems.size(); - for (int i = 0; i < size; i++) { - overflowPanelAdapter.add(menuItems.get(i)); - } - mOverflowPanel.setAdapter(overflowPanelAdapter); - if (mOpenOverflowUpwards) { - mOverflowPanel.setY(0); - } else { - mOverflowPanel.setY(mOverflowButtonSize.getHeight()); - } - int width = Math.max(getOverflowWidth(), mOverflowButtonSize.getWidth()); - int height = calculateOverflowHeight(MAX_OVERFLOW_SIZE); - mOverflowPanelSize = new Size(width, height); - setSize(mOverflowPanel, mOverflowPanelSize); - } - - /** - * Resets the content container and appropriately position it's panels. - */ - private void preparePopupContent() { - mContentContainer.removeAllViews(); - // Add views in the specified order so they stack up as expected. - // Order: overflowPanel, mainPanel, overflowButton. - if (hasOverflow()) { - mContentContainer.addView(mOverflowPanel); - } - mContentContainer.addView(mMainPanel); - if (hasOverflow()) { - mContentContainer.addView(mOverflowButton); - } - setPanelsStatesAtRestingPosition(); - - // The positioning of contents in RTL is wrong when the view is first rendered. - // Hide the view and post a runnable to recalculate positions and render the view. - // TODO: Investigate why this happens and fix. - if (isInRTLMode()) { - mContentContainer.setAlpha(0); - mContentContainer.post(mPreparePopupContentRTLHelper); - } - } - - /** - * Clears out the panels and their container. Resets their calculated sizes. - */ - private void clearPanels() { - mIsOverflowOpen = false; - mMainPanelSize = null; - mMainPanel.removeAllViews(); - mOverflowPanelSize = null; - ArrayAdapter<ToolbarMenuItem> overflowPanelAdapter = - (ArrayAdapter<ToolbarMenuItem>) mOverflowPanel.getAdapter(); - overflowPanelAdapter.clear(); - mOverflowPanel.setAdapter(overflowPanelAdapter); - mContentContainer.removeAllViews(); - } - - private void positionContentYCoordinatesIfOpeningOverflowUpwards() { - if (mOpenOverflowUpwards) { - mMainPanel.setY(mContentContainer.getHeight() - mMainPanelSize.getHeight()); - mOverflowButton.setY(mContentContainer.getHeight() - mOverflowButton.getHeight()); - mOverflowPanel.setY(mContentContainer.getHeight() - mOverflowPanelSize.getHeight()); - } - } - - private int getOverflowWidth() { - int overflowWidth = 0; - final int count = mOverflowPanel.getAdapter().getCount(); - for (int i = 0; i < count; i++) { - ToolbarMenuItem menuItem = (ToolbarMenuItem) mOverflowPanel.getAdapter().getItem(i); - overflowWidth = - Math.max(mOverflowPanelViewHelper.calculateWidth(menuItem), overflowWidth); - } - return overflowWidth; - } - - private int calculateOverflowHeight(int maxItemSize) { - // Maximum of 4 items, minimum of 2 if the overflow has to scroll. - int actualSize = Math.min( - MAX_OVERFLOW_SIZE, - Math.min( - Math.max(MIN_OVERFLOW_SIZE, maxItemSize), - mOverflowPanel.getCount())); - int extension = 0; - if (actualSize < mOverflowPanel.getCount()) { - // The overflow will require scrolling to get to all the items. - // Extend the height so that part of the hidden items is displayed. - extension = (int) (mLineHeight * 0.5f); - } - return actualSize * mLineHeight - + mOverflowButtonSize.getHeight() - + extension; - } - - /** - * NOTE: Use only in android.view.animation.* animations. Do not use in android.animation.* - * animations. See comment about this in the code. - */ - private int getAnimationDuration() { - if (mTransitionDurationScale < 150) { - // For smaller transition, decrease the time. - return 200; - } else if (mTransitionDurationScale > 300) { - // For bigger transition, increase the time. - return 300; - } - - // Scale the animation duration with getDurationScale(). This allows - // android.view.animation.* animations to scale just like android.animation.* animations - // when animator duration scale is adjusted in "Developer Options". - // For this reason, do not use this method for android.animation.* animations. - return (int) (250 * ValueAnimator.getDurationScale()); - } - - private void maybeComputeTransitionDurationScale() { - if (mMainPanelSize != null && mOverflowPanelSize != null) { - int w = mMainPanelSize.getWidth() - mOverflowPanelSize.getWidth(); - int h = mOverflowPanelSize.getHeight() - mMainPanelSize.getHeight(); - mTransitionDurationScale = (int) (Math.sqrt(w * w + h * h) - / mContentContainer.getContext().getResources().getDisplayMetrics().density); - } - } - - private ViewGroup createMainPanel() { - return new LinearLayout(mContext) { - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (isOverflowAnimating()) { - // Update widthMeasureSpec to make sure that this view is not clipped - // as we offset its coordinates with respect to its parent. - widthMeasureSpec = MeasureSpec.makeMeasureSpec( - mMainPanelSize.getWidth(), - MeasureSpec.EXACTLY); - } - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - // Intercept the touch event while the overflow is animating. - return isOverflowAnimating(); - } - }; - } - - private ImageButton createOverflowButton() { - final ImageButton overflowButton = (ImageButton) LayoutInflater.from(mContext) - .inflate(R.layout.floating_popup_overflow_button, null); - overflowButton.setImageDrawable(mOverflow); - overflowButton.setOnClickListener(v -> { - if (isShowing()) { - preparePopupContent(); - WidgetInfo widgetInfo = createWidgetInfo(); - mSurfaceControlViewHost.relayout(mPopupWidth, mPopupHeight); - mCallbackWrapper.onWidgetUpdated(widgetInfo); - } - if (mIsOverflowOpen) { - overflowButton.setImageDrawable(mToOverflow); - mToOverflow.start(); - closeOverflow(); - } else { - overflowButton.setImageDrawable(mToArrow); - mToArrow.start(); - openOverflow(); - } - }); - return overflowButton; - } - - private OverflowPanel createOverflowPanel() { - final OverflowPanel overflowPanel = new OverflowPanel(this); - overflowPanel.setLayoutParams(new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - overflowPanel.setDivider(null); - overflowPanel.setDividerHeight(0); - - final ArrayAdapter adapter = - new ArrayAdapter<ToolbarMenuItem>(mContext, 0) { - @Override - public View getView(int position, View convertView, ViewGroup parent) { - return mOverflowPanelViewHelper.getView( - getItem(position), mOverflowPanelSize.getWidth(), convertView); - } - }; - overflowPanel.setAdapter(adapter); - overflowPanel.setOnItemClickListener((parent, view, position, id) -> { - ToolbarMenuItem menuItem = - (ToolbarMenuItem) overflowPanel.getAdapter().getItem(position); - mCallbackWrapper.onMenuItemClicked(menuItem); - }); - return overflowPanel; - } - - private boolean isOverflowAnimating() { - final boolean overflowOpening = mOpenOverflowAnimation.hasStarted() - && !mOpenOverflowAnimation.hasEnded(); - final boolean overflowClosing = mCloseOverflowAnimation.hasStarted() - && !mCloseOverflowAnimation.hasEnded(); - return overflowOpening || overflowClosing; - } - - private Animation.AnimationListener createOverflowAnimationListener() { - return new Animation.AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - // Disable the overflow button while it's animating. - // It will be re-enabled when the animation stops. - mOverflowButton.setEnabled(false); - // Ensure both panels have visibility turned on when the overflow animation - // starts. - mMainPanel.setVisibility(View.VISIBLE); - mOverflowPanel.setVisibility(View.VISIBLE); - } - - @Override - public void onAnimationEnd(Animation animation) { - // Posting this because it seems like this is called before the animation - // actually ends. - mContentContainer.post(() -> { - setPanelsStatesAtRestingPosition(); - }); - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - }; - } - - private static Size measure(View view) { - Preconditions.checkState(view.getParent() == null); - view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); - return new Size(view.getMeasuredWidth(), view.getMeasuredHeight()); - } - - private static void setSize(View view, int width, int height) { - view.setMinimumWidth(width); - view.setMinimumHeight(height); - ViewGroup.LayoutParams params = view.getLayoutParams(); - params = (params == null) ? new ViewGroup.LayoutParams(0, 0) : params; - params.width = width; - params.height = height; - view.setLayoutParams(params); - } - - private static void setSize(View view, Size size) { - setSize(view, size.getWidth(), size.getHeight()); - } - - private static void setWidth(View view, int width) { - ViewGroup.LayoutParams params = view.getLayoutParams(); - setSize(view, width, params.height); - } - - private static void setHeight(View view, int height) { - ViewGroup.LayoutParams params = view.getLayoutParams(); - setSize(view, params.width, height); - } - - /** - * A custom ListView for the overflow panel. - */ - private static final class OverflowPanel extends ListView { - - private final RemoteSelectionToolbar mPopup; - - OverflowPanel(RemoteSelectionToolbar popup) { - super(Objects.requireNonNull(popup).mContext); - this.mPopup = popup; - setScrollBarDefaultDelayBeforeFade(ViewConfiguration.getScrollDefaultDelay() * 3); - setScrollIndicators(View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - // Update heightMeasureSpec to make sure that this view is not clipped - // as we offset it's coordinates with respect to its parent. - int height = mPopup.mOverflowPanelSize.getHeight() - - mPopup.mOverflowButtonSize.getHeight(); - heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - if (mPopup.isOverflowAnimating()) { - // Eat the touch event. - return true; - } - return super.dispatchTouchEvent(ev); - } - - @Override - protected boolean awakenScrollBars() { - return super.awakenScrollBars(); - } - } - - /** - * A custom interpolator used for various floating toolbar animations. - */ - private static final class LogAccelerateInterpolator implements Interpolator { - - private static final int BASE = 100; - private static final float LOGS_SCALE = 1f / computeLog(1, BASE); - - private static float computeLog(float t, int base) { - return (float) (1 - Math.pow(base, -t)); - } - - @Override - public float getInterpolation(float t) { - return 1 - computeLog(1 - t, BASE) * LOGS_SCALE; - } - } - - /** - * A helper for generating views for the overflow panel. - */ - private static final class OverflowPanelViewHelper { - private final Context mContext; - private final View mCalculator; - private final int mIconTextSpacing; - private final int mSidePadding; - - OverflowPanelViewHelper(Context context, int iconTextSpacing) { - mContext = Objects.requireNonNull(context); - mIconTextSpacing = iconTextSpacing; - mSidePadding = context.getResources() - .getDimensionPixelSize(R.dimen.floating_toolbar_overflow_side_padding); - mCalculator = createMenuButton(null); - } - - public View getView(ToolbarMenuItem menuItem, int minimumWidth, View convertView) { - Objects.requireNonNull(menuItem); - if (convertView != null) { - updateMenuItemButton( - convertView, menuItem, mIconTextSpacing, shouldShowIcon(menuItem)); - } else { - convertView = createMenuButton(menuItem); - } - convertView.setMinimumWidth(minimumWidth); - return convertView; - } - - public int calculateWidth(ToolbarMenuItem menuItem) { - updateMenuItemButton( - mCalculator, menuItem, mIconTextSpacing, shouldShowIcon(menuItem)); - mCalculator.measure( - View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); - return mCalculator.getMeasuredWidth(); - } - - private View createMenuButton(ToolbarMenuItem menuItem) { - View button = createMenuItemButton( - mContext, menuItem, mIconTextSpacing, shouldShowIcon(menuItem)); - button.setPadding(mSidePadding, 0, mSidePadding, 0); - return button; - } - - private boolean shouldShowIcon(ToolbarMenuItem menuItem) { - if (menuItem != null) { - return menuItem.getGroupId() == android.R.id.textAssist; - } - return false; - } - } - - /** - * Creates and returns a menu button for the specified menu item. - */ - private static View createMenuItemButton( - Context context, ToolbarMenuItem menuItem, int iconTextSpacing, boolean showIcon) { - final View menuItemButton = LayoutInflater.from(context) - .inflate(R.layout.floating_popup_menu_button, null); - if (menuItem != null) { - updateMenuItemButton(menuItemButton, menuItem, iconTextSpacing, showIcon); - } - return menuItemButton; - } - - /** - * Updates the specified menu item button with the specified menu item data. - */ - private static void updateMenuItemButton(View menuItemButton, ToolbarMenuItem menuItem, - int iconTextSpacing, boolean showIcon) { - final TextView buttonText = menuItemButton.findViewById( - R.id.floating_toolbar_menu_item_text); - buttonText.setEllipsize(null); - if (TextUtils.isEmpty(menuItem.getTitle())) { - buttonText.setVisibility(View.GONE); - } else { - buttonText.setVisibility(View.VISIBLE); - buttonText.setText(menuItem.getTitle()); - } - final ImageView buttonIcon = menuItemButton.findViewById( - R.id.floating_toolbar_menu_item_image); - if (menuItem.getIcon() == null || !showIcon) { - buttonIcon.setVisibility(View.GONE); - buttonText.setPaddingRelative(0, 0, 0, 0); - } else { - buttonIcon.setVisibility(View.VISIBLE); - buttonIcon.setImageDrawable( - menuItem.getIcon().loadDrawable(menuItemButton.getContext())); - buttonText.setPaddingRelative(iconTextSpacing, 0, 0, 0); - } - final CharSequence contentDescription = menuItem.getContentDescription(); - if (TextUtils.isEmpty(contentDescription)) { - menuItemButton.setContentDescription(menuItem.getTitle()); - } else { - menuItemButton.setContentDescription(contentDescription); - } - } - - private static ViewGroup createContentContainer(Context context) { - ViewGroup contentContainer = (ViewGroup) LayoutInflater.from(context) - .inflate(R.layout.floating_popup_container, null); - contentContainer.setLayoutParams(new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - contentContainer.setTag(FloatingToolbar.FLOATING_TOOLBAR_TAG); - contentContainer.setClipToOutline(true); - return contentContainer; - } - - /** - * Creates an "appear" animation for the specified view. - * - * @param view The view to animate - */ - private static AnimatorSet createEnterAnimation(View view, Animator.AnimatorListener listener) { - AnimatorSet animation = new AnimatorSet(); - animation.playTogether( - ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1).setDuration(150)); - animation.addListener(listener); - return animation; - } - - /** - * Creates a "disappear" animation for the specified view. - * - * @param view The view to animate - * @param startDelay The start delay of the animation - * @param listener The animation listener - */ - private static AnimatorSet createExitAnimation( - View view, int startDelay, Animator.AnimatorListener listener) { - AnimatorSet animation = new AnimatorSet(); - animation.playTogether( - ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0).setDuration(100)); - animation.setStartDelay(startDelay); - if (listener != null) { - animation.addListener(listener); - } - return animation; - } - - /** - * Returns a re-themed context with controlled look and feel for views. - */ - private static Context applyDefaultTheme(Context originalContext, boolean isLightTheme) { - int themeId = - isLightTheme ? R.style.Theme_DeviceDefault_Light : R.style.Theme_DeviceDefault; - return new ContextThemeWrapper(originalContext, themeId); - } - - private static void debugLog(String message) { - if (Log.isLoggable(FloatingToolbar.FLOATING_TOOLBAR_TAG, Log.DEBUG)) { - Log.v(TAG, message); - } - } - - void dump(String prefix, PrintWriter pw) { - pw.print(prefix); pw.print("toolbar token: "); pw.println(mSelectionToolbarToken); - pw.print(prefix); pw.print("dismissed: "); pw.println(mDismissed); - pw.print(prefix); pw.print("hidden: "); pw.println(mHidden); - pw.print(prefix); pw.print("popup width: "); pw.println(mPopupWidth); - pw.print(prefix); pw.print("popup height: "); pw.println(mPopupHeight); - pw.print(prefix); pw.print("relative coords: "); pw.println(mRelativeCoordsForToolbar); - pw.print(prefix); pw.print("main panel size: "); pw.println(mMainPanelSize); - boolean hasOverflow = hasOverflow(); - pw.print(prefix); pw.print("has overflow: "); pw.println(hasOverflow); - if (hasOverflow) { - pw.print(prefix); pw.print("overflow open: "); pw.println(mIsOverflowOpen); - pw.print(prefix); pw.print("overflow size: "); pw.println(mOverflowPanelSize); - } - if (mSurfaceControlViewHost != null) { - FloatingToolbarRoot root = (FloatingToolbarRoot) mSurfaceControlViewHost.getView(); - root.dump(prefix, pw); - } - if (mMenuItems != null) { - int menuItemSize = mMenuItems.size(); - pw.print(prefix); pw.print("number menu items: "); pw.println(menuItemSize); - for (int i = 0; i < menuItemSize; i++) { - pw.print(prefix); pw.print("#"); pw.println(i); - pw.print(prefix + " "); pw.println(mMenuItems.get(i)); - } - } - } -} diff --git a/core/java/android/service/selectiontoolbar/SelectionToolbarRenderCallback.java b/core/java/android/service/selectiontoolbar/SelectionToolbarRenderCallback.java deleted file mode 100644 index a10b6a8ac8cb..000000000000 --- a/core/java/android/service/selectiontoolbar/SelectionToolbarRenderCallback.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2021 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.service.selectiontoolbar; - -import android.view.selectiontoolbar.ToolbarMenuItem; -import android.view.selectiontoolbar.WidgetInfo; - -/** - * The callback that the render service uses to communicate with the host of the selection toolbar - * container. - * - * @hide - */ -public interface SelectionToolbarRenderCallback { - /** - * The selection toolbar is shown. - */ - void onShown(WidgetInfo widgetInfo); - /** - * The selection toolbar has changed. - */ - void onWidgetUpdated(WidgetInfo info); - /** - * The menu item on the selection toolbar has been clicked. - */ - void onMenuItemClicked(ToolbarMenuItem item); - /** - * The toolbar doesn't be dismissed after showing on a given timeout. - */ - void onToolbarShowTimeout(); - /** - * The error occurred when operating on the selection toolbar. - */ - void onError(int errorCode); -} diff --git a/core/java/android/service/selectiontoolbar/SelectionToolbarRenderService.java b/core/java/android/service/selectiontoolbar/SelectionToolbarRenderService.java deleted file mode 100644 index f33feaec6dde..000000000000 --- a/core/java/android/service/selectiontoolbar/SelectionToolbarRenderService.java +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright (C) 2021 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.service.selectiontoolbar; - -import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; - -import android.annotation.CallSuper; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.Service; -import android.content.Intent; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.RemoteException; -import android.util.Log; -import android.util.Pair; -import android.util.SparseArray; -import android.view.selectiontoolbar.ISelectionToolbarCallback; -import android.view.selectiontoolbar.ShowInfo; -import android.view.selectiontoolbar.ToolbarMenuItem; -import android.view.selectiontoolbar.WidgetInfo; - -/** - * Service for rendering selection toolbar. - * - * @hide - */ -public abstract class SelectionToolbarRenderService extends Service { - - private static final String TAG = "SelectionToolbarRenderService"; - - // TODO(b/215497659): read from DeviceConfig - // The timeout to clean the cache if the client forgot to call dismiss() - private static final int CACHE_CLEAN_AFTER_SHOW_TIMEOUT_IN_MS = 10 * 60 * 1000; // 10 minutes - - /** - * The {@link Intent} that must be declared as handled by the service. - * - * <p>To be supported, the service must also require the - * {@link android.Manifest.permission#BIND_SELECTION_TOOLBAR_RENDER_SERVICE} permission so - * that other applications can not abuse it. - */ - public static final String SERVICE_INTERFACE = - "android.service.selectiontoolbar.SelectionToolbarRenderService"; - - private Handler mHandler; - private ISelectionToolbarRenderServiceCallback mServiceCallback; - - private final SparseArray<Pair<RemoteCallbackWrapper, CleanCacheRunnable>> mCache = - new SparseArray<>(); - - /** - * Binder to receive calls from system server. - */ - private final ISelectionToolbarRenderService mInterface = - new ISelectionToolbarRenderService.Stub() { - - @Override - public void onShow(int callingUid, ShowInfo showInfo, ISelectionToolbarCallback callback) { - if (mCache.indexOfKey(callingUid) < 0) { - mCache.put(callingUid, new Pair<>(new RemoteCallbackWrapper(callback), - new CleanCacheRunnable(callingUid))); - } - Pair<RemoteCallbackWrapper, CleanCacheRunnable> toolbarPair = mCache.get(callingUid); - CleanCacheRunnable cleanRunnable = toolbarPair.second; - mHandler.removeCallbacks(cleanRunnable); - mHandler.sendMessage(obtainMessage(SelectionToolbarRenderService::onShow, - SelectionToolbarRenderService.this, callingUid, showInfo, - toolbarPair.first)); - mHandler.postDelayed(cleanRunnable, CACHE_CLEAN_AFTER_SHOW_TIMEOUT_IN_MS); - } - - @Override - public void onHide(long widgetToken) { - mHandler.sendMessage(obtainMessage(SelectionToolbarRenderService::onHide, - SelectionToolbarRenderService.this, widgetToken)); - } - - @Override - public void onDismiss(int callingUid, long widgetToken) { - mHandler.sendMessage(obtainMessage(SelectionToolbarRenderService::onDismiss, - SelectionToolbarRenderService.this, widgetToken)); - Pair<RemoteCallbackWrapper, CleanCacheRunnable> toolbarPair = mCache.get(callingUid); - if (toolbarPair != null) { - mHandler.removeCallbacks(toolbarPair.second); - mCache.remove(callingUid); - } - } - - @Override - public void onConnected(IBinder callback) { - mHandler.sendMessage(obtainMessage(SelectionToolbarRenderService::handleOnConnected, - SelectionToolbarRenderService.this, callback)); - } - }; - - @CallSuper - @Override - public void onCreate() { - super.onCreate(); - mHandler = new Handler(Looper.getMainLooper(), null, true); - } - - @Override - @Nullable - public final IBinder onBind(@NonNull Intent intent) { - if (SERVICE_INTERFACE.equals(intent.getAction())) { - return mInterface.asBinder(); - } - Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent); - return null; - } - - private void handleOnConnected(@NonNull IBinder callback) { - mServiceCallback = ISelectionToolbarRenderServiceCallback.Stub.asInterface(callback); - } - - protected void transferTouch(@NonNull IBinder source, @NonNull IBinder target) { - final ISelectionToolbarRenderServiceCallback callback = mServiceCallback; - if (callback == null) { - Log.e(TAG, "transferTouch(): no server callback"); - return; - } - try { - callback.transferTouch(source, target); - } catch (RemoteException e) { - // no-op - } - } - - /** - * Called when showing the selection toolbar. - */ - public abstract void onShow(int callingUid, ShowInfo showInfo, - RemoteCallbackWrapper callbackWrapper); - - /** - * Called when hiding the selection toolbar. - */ - public abstract void onHide(long widgetToken); - - - /** - * Called when dismissing the selection toolbar. - */ - public abstract void onDismiss(long widgetToken); - - /** - * Called when showing the selection toolbar for a specific timeout. This avoids the client - * forgot to call dismiss to clean the state. - */ - public void onToolbarShowTimeout(int callingUid) { - // no-op - } - - /** - * Callback to notify the client toolbar events. - */ - public static final class RemoteCallbackWrapper implements SelectionToolbarRenderCallback { - - private final ISelectionToolbarCallback mRemoteCallback; - - RemoteCallbackWrapper(ISelectionToolbarCallback remoteCallback) { - // TODO(b/215497659): handle if the binder dies. - mRemoteCallback = remoteCallback; - } - - @Override - public void onShown(WidgetInfo widgetInfo) { - try { - mRemoteCallback.onShown(widgetInfo); - } catch (RemoteException e) { - // no-op - } - } - - @Override - public void onToolbarShowTimeout() { - try { - mRemoteCallback.onToolbarShowTimeout(); - } catch (RemoteException e) { - // no-op - } - } - - @Override - public void onWidgetUpdated(WidgetInfo widgetInfo) { - try { - mRemoteCallback.onWidgetUpdated(widgetInfo); - } catch (RemoteException e) { - // no-op - } - } - - @Override - public void onMenuItemClicked(ToolbarMenuItem item) { - try { - mRemoteCallback.onMenuItemClicked(item); - } catch (RemoteException e) { - // no-op - } - } - - @Override - public void onError(int errorCode) { - try { - mRemoteCallback.onError(errorCode); - } catch (RemoteException e) { - // no-op - } - } - } - - private class CleanCacheRunnable implements Runnable { - - int mCleanUid; - - CleanCacheRunnable(int cleanUid) { - mCleanUid = cleanUid; - } - - @Override - public void run() { - Pair<RemoteCallbackWrapper, CleanCacheRunnable> toolbarPair = mCache.get(mCleanUid); - if (toolbarPair != null) { - Log.w(TAG, "CleanCacheRunnable: remove " + mCleanUid + " from cache."); - mCache.remove(mCleanUid); - onToolbarShowTimeout(mCleanUid); - } - } - } - - /** - * A listener to notify the service to the transfer touch focus. - */ - public interface TransferTouchListener { - /** - * Notify the service to transfer the touch focus. - */ - void onTransferTouch(IBinder source, IBinder target); - } -} diff --git a/core/java/android/util/apk/TEST_MAPPING b/core/java/android/util/apk/TEST_MAPPING index a3a86fdb4b82..7668eec474ab 100644 --- a/core/java/android/util/apk/TEST_MAPPING +++ b/core/java/android/util/apk/TEST_MAPPING @@ -7,7 +7,9 @@ "include-filter": "android.util.apk.SourceStampVerifierTest" } ] - }, + } + ], + "presubmit-large": [ { "name": "CtsPackageManagerTestCases", "options": [ diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 4b96d74b5687..0b2b6ce33666 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -375,6 +375,14 @@ public final class Display { public static final int FLAG_REAR = 1 << 13; /** + * Display flag: Indicates that the orientation of this display is not fixed and is coupled to + * the orientation of its content. + * + * @hide + */ + public static final int FLAG_ROTATES_WITH_CONTENT = 1 << 14; + + /** * Display flag: Indicates that the contents of the display should not be scaled * to fit the physical screen dimensions. Used for development only to emulate * devices with smaller physicals screens while preserving density. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 21ee0e783fbc..3aa610af60b0 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1810,6 +1810,9 @@ public final class ViewRootImpl implements ViewParent, // Request to update light center. mAttachInfo.mNeedsUpdateLightCenter = true; } + if ((changes & WindowManager.LayoutParams.COLOR_MODE_CHANGED) != 0) { + invalidate(); + } if (mWindowAttributes.packageName == null) { mWindowAttributes.packageName = mBasePackageName; } @@ -3792,6 +3795,11 @@ public final class ViewRootImpl implements ViewParent, boolean cancelDueToPreDrawListener = mAttachInfo.mTreeObserver.dispatchOnPreDraw(); boolean cancelAndRedraw = cancelDueToPreDrawListener || (cancelDraw && mDrewOnceForSync); + if (cancelAndRedraw) { + Log.d(mTag, "Cancelling draw." + + " cancelDueToPreDrawListener=" + cancelDueToPreDrawListener + + " cancelDueToSync=" + (cancelDraw && mDrewOnceForSync)); + } if (!cancelAndRedraw) { // A sync was already requested before the WMS requested sync. This means we need to // sync the buffer, regardless if WMS wants to sync the buffer. @@ -5561,6 +5569,7 @@ public final class ViewRootImpl implements ViewParent, if (desiredRatio != mDesiredHdrSdrRatio) { mDesiredHdrSdrRatio = desiredRatio; updateRenderHdrSdrRatio(); + invalidate(); if (mDesiredHdrSdrRatio < 1.01f) { mDisplay.unregisterHdrSdrRatioChangedListener(mHdrSdrRatioChangedListener); diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 7f8f61109287..9687a5395afd 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -1516,7 +1516,7 @@ public final class InputMethodManager { * @see #startStylusHandwriting(View) */ public boolean isStylusHandwritingAvailable() { - return isStylusHandwritingAvailableAsUser(UserHandle.myUserId()); + return isStylusHandwritingAvailableAsUser(UserHandle.of(UserHandle.myUserId())); } /** @@ -1527,14 +1527,17 @@ public final class InputMethodManager { * called and Stylus touch should continue as normal touch input.</p> * * <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when - * {@code userId} is different from the user id of the current process.</p> + * {@code user} is different from the user of the current process.</p> * * @see #startStylusHandwriting(View) - * @param userId user ID to query. + * @param user UserHandle to query. * @hide */ + @NonNull @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true) - public boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) { + @TestApi + @SuppressLint("UserHandle") + public boolean isStylusHandwritingAvailableAsUser(@NonNull UserHandle user) { final Context fallbackContext = ActivityThread.currentApplication(); if (fallbackContext == null) { return false; @@ -1551,7 +1554,7 @@ public final class InputMethodManager { } }; } - isAvailable = mStylusHandwritingAvailableCache.query(userId); + isAvailable = mStylusHandwritingAvailableCache.query(user.getIdentifier()); } return isAvailable; } @@ -1643,16 +1646,19 @@ public final class InputMethodManager { * Returns the list of enabled input methods for the specified user. * * <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when - * {@code userId} is different from the user id of the current process.</p> + * {@code user} is different from the user of the current process.</p> * - * @param userId user ID to query + * @param user UserHandle to query * @return {@link List} of {@link InputMethodInfo}. - * @see #getEnabledInputMethodSubtypeListAsUser(String, boolean, int) + * @see #getEnabledInputMethodSubtypeListAsUser(String, boolean, UserHandle) * @hide */ + @NonNull @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true) - public List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) { - return IInputMethodManagerGlobalInvoker.getEnabledInputMethodList(userId); + @TestApi + @SuppressLint("UserHandle") + public List<InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull UserHandle user) { + return IInputMethodManagerGlobalInvoker.getEnabledInputMethodList(user.getIdentifier()); } /** @@ -1681,7 +1687,7 @@ public final class InputMethodManager { * * @param imeId IME ID to be queried about. * @param allowsImplicitlyEnabledSubtypes {@code true} to include implicitly enabled subtypes. - * @param userId user ID to be queried about. + * @param user UserHandle to be queried about. * {@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required if this is * different from the calling process user ID. * @return {@link List} of {@link InputMethodSubtype}. @@ -1690,10 +1696,14 @@ public final class InputMethodManager { */ @NonNull @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true) + @TestApi + @SuppressLint("UserHandle") public List<InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser( - @NonNull String imeId, boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) { + @NonNull String imeId, boolean allowsImplicitlyEnabledSubtypes, + @NonNull UserHandle user) { return IInputMethodManagerGlobalInvoker.getEnabledInputMethodSubtypeList( - Objects.requireNonNull(imeId), allowsImplicitlyEnabledSubtypes, userId); + Objects.requireNonNull(imeId), allowsImplicitlyEnabledSubtypes, + user.getIdentifier()); } /** diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java index 987ac2e90240..d588c487844b 100644 --- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java +++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java @@ -809,11 +809,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { return; // cancelled } InputConnection ic = getInputConnection(); - // Note we do NOT check isActive() here, because this is safe - // for an IME to call at any time, and we need to allow it - // through to clean up our state after the IME has switched to - // another client. - if (ic == null) { + if (ic == null || !isActive()) { Log.w(TAG, "finishComposingTextFromImm on inactive InputConnection"); return; } @@ -837,11 +833,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { return; // cancelled } InputConnection ic = getInputConnection(); - // Note we do NOT check isActive() here, because this is safe - // for an IME to call at any time, and we need to allow it - // through to clean up our state after the IME has switched to - // another client. - if (ic == null) { + if (ic == null && !isActive()) { Log.w(TAG, "finishComposingText on inactive InputConnection"); return; } diff --git a/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl b/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl deleted file mode 100644 index aaeb12012f68..000000000000 --- a/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2021 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.view.selectiontoolbar; - -import android.view.selectiontoolbar.ToolbarMenuItem; -import android.view.selectiontoolbar.WidgetInfo; - -/** - * Binder interface to notify the selection toolbar events from one process to the other. - * @hide - */ -oneway interface ISelectionToolbarCallback { - void onShown(in WidgetInfo info); - void onWidgetUpdated(in WidgetInfo info); - void onToolbarShowTimeout(); - void onMenuItemClicked(in ToolbarMenuItem item); - void onError(int errorCode); -} diff --git a/core/java/android/view/selectiontoolbar/ISelectionToolbarManager.aidl b/core/java/android/view/selectiontoolbar/ISelectionToolbarManager.aidl deleted file mode 100644 index 4a647ada1d6c..000000000000 --- a/core/java/android/view/selectiontoolbar/ISelectionToolbarManager.aidl +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2021 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.view.selectiontoolbar; - -import android.view.selectiontoolbar.ISelectionToolbarCallback; -import android.view.selectiontoolbar.ShowInfo; - -/** - * Mediator between apps and selection toolbar service implementation. - * - * @hide - */ -oneway interface ISelectionToolbarManager { - void showToolbar(in ShowInfo showInfo, in ISelectionToolbarCallback callback, int userId); - void hideToolbar(long widgetToken, int userId); - void dismissToolbar(long widgetToken, int userId); -}
\ No newline at end of file diff --git a/core/java/android/view/selectiontoolbar/OWNERS b/core/java/android/view/selectiontoolbar/OWNERS deleted file mode 100644 index 5500b92868dd..000000000000 --- a/core/java/android/view/selectiontoolbar/OWNERS +++ /dev/null @@ -1,10 +0,0 @@ -# Bug component: 709498 - -augale@google.com -joannechung@google.com -licha@google.com -lpeter@google.com -svetoslavganov@google.com -toki@google.com -tonymak@google.com -tymtsai@google.com
\ No newline at end of file diff --git a/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java b/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java deleted file mode 100644 index 6de031628768..000000000000 --- a/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2021 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.view.selectiontoolbar; - -import android.annotation.NonNull; -import android.annotation.SystemService; -import android.content.Context; -import android.os.RemoteException; -import android.provider.DeviceConfig; - -import java.util.Objects; - -/** - * The {@link SelectionToolbarManager} class provides ways for apps to control the - * selection toolbar. - * - * @hide - */ -@SystemService(Context.SELECTION_TOOLBAR_SERVICE) -public final class SelectionToolbarManager { - - private static final String TAG = "SelectionToolbar"; - - /** - * The tag which uses for enabling debug log dump. To enable it, we can use command "adb shell - * setprop log.tag.UiTranslation DEBUG". - */ - public static final String LOG_TAG = "SelectionToolbar"; - - /** - * Whether system selection toolbar is enabled. - */ - private static final String REMOTE_SELECTION_TOOLBAR_ENABLED = - "remote_selection_toolbar_enabled"; - - /** - * Used to mark a toolbar that has no toolbar token id. - */ - public static final long NO_TOOLBAR_ID = 0; - - /** - * The error code that do not allow to create multiple toolbar. - */ - public static final int ERROR_DO_NOT_ALLOW_MULTIPLE_TOOL_BAR = 1; - - @NonNull - private final Context mContext; - private final ISelectionToolbarManager mService; - - public SelectionToolbarManager(@NonNull Context context, - @NonNull ISelectionToolbarManager service) { - mContext = Objects.requireNonNull(context); - mService = service; - } - - /** - * Request to show selection toolbar for a given View. - */ - public void showToolbar(@NonNull ShowInfo showInfo, - @NonNull ISelectionToolbarCallback callback) { - try { - Objects.requireNonNull(showInfo); - Objects.requireNonNull(callback); - mService.showToolbar(showInfo, callback, mContext.getUserId()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Request to hide selection toolbar. - */ - public void hideToolbar(long widgetToken) { - try { - mService.hideToolbar(widgetToken, mContext.getUserId()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Dismiss to dismiss selection toolbar. - */ - public void dismissToolbar(long widgetToken) { - try { - mService.dismissToolbar(widgetToken, mContext.getUserId()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - private boolean isRemoteSelectionToolbarEnabled() { - return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SELECTION_TOOLBAR, - REMOTE_SELECTION_TOOLBAR_ENABLED, false); - } - - /** - * Returns {@code true} if remote render selection toolbar enabled, otherwise - * returns {@code false}. - */ - public static boolean isRemoteSelectionToolbarEnabled(Context context) { - SelectionToolbarManager manager = context.getSystemService(SelectionToolbarManager.class); - if (manager != null) { - return manager.isRemoteSelectionToolbarEnabled(); - } - return false; - } -} diff --git a/core/java/android/view/selectiontoolbar/ShowInfo.java b/core/java/android/view/selectiontoolbar/ShowInfo.java deleted file mode 100644 index 28b4480d4967..000000000000 --- a/core/java/android/view/selectiontoolbar/ShowInfo.java +++ /dev/null @@ -1,361 +0,0 @@ -/* - * Copyright (C) 2021 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.view.selectiontoolbar; - -import android.annotation.NonNull; -import android.graphics.Rect; -import android.os.IBinder; -import android.os.Parcelable; - -import com.android.internal.util.DataClass; - -import java.util.List; - - -/** - * The class holds menu information for render service to render the selection toolbar. - * - * @hide - */ -@DataClass(genToString = true, genEqualsHashCode = true) -public final class ShowInfo implements Parcelable { - - /** - * The token that is used to identify the selection toolbar. This is initially set to 0 - * until a selection toolbar has been created for the showToolbar request. - */ - private final long mWidgetToken; - - /** - * If the toolbar menu items need to be re-layout. - */ - private final boolean mLayoutRequired; - - /** - * The menu items to be rendered in the selection toolbar. - */ - @NonNull - private final List<ToolbarMenuItem> mMenuItems; - - /** - * A rect specifying where the selection toolbar on the screen. - */ - @NonNull - private final Rect mContentRect; - - /** - * A recommended maximum suggested width of the selection toolbar. - */ - private final int mSuggestedWidth; - - /** - * The portion of the screen that is available to the selection toolbar. - */ - @NonNull - private final Rect mViewPortOnScreen; - - /** - * The host application's input token, this allows the remote render service to transfer - * the touch focus to the host application. - */ - @NonNull - private final IBinder mHostInputToken; - - /** - * If the host application uses light theme. - */ - private final boolean mIsLightTheme; - - - - // Code below generated by codegen v1.0.23. - // - // DO NOT MODIFY! - // CHECKSTYLE:OFF Generated code - // - // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/selectiontoolbar/ShowInfo.java - // - // To exclude the generated code from IntelliJ auto-formatting enable (one-time): - // Settings > Editor > Code Style > Formatter Control - //@formatter:off - - - /** - * Creates a new ShowInfo. - * - * @param widgetToken - * The token that is used to identify the selection toolbar. This is initially set to 0 - * until a selection toolbar has been created for the showToolbar request. - * @param layoutRequired - * If the toolbar menu items need to be re-layout. - * @param menuItems - * The menu items to be rendered in the selection toolbar. - * @param contentRect - * A rect specifying where the selection toolbar on the screen. - * @param suggestedWidth - * A recommended maximum suggested width of the selection toolbar. - * @param viewPortOnScreen - * The portion of the screen that is available to the selection toolbar. - * @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( - long widgetToken, - boolean layoutRequired, - @NonNull List<ToolbarMenuItem> menuItems, - @NonNull Rect contentRect, - int suggestedWidth, - @NonNull Rect viewPortOnScreen, - @NonNull IBinder hostInputToken, - boolean isLightTheme) { - this.mWidgetToken = widgetToken; - this.mLayoutRequired = layoutRequired; - this.mMenuItems = menuItems; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mMenuItems); - this.mContentRect = contentRect; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mContentRect); - this.mSuggestedWidth = suggestedWidth; - this.mViewPortOnScreen = viewPortOnScreen; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mViewPortOnScreen); - 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 - } - - /** - * The token that is used to identify the selection toolbar. This is initially set to 0 - * until a selection toolbar has been created for the showToolbar request. - */ - @DataClass.Generated.Member - public long getWidgetToken() { - return mWidgetToken; - } - - /** - * If the toolbar menu items need to be re-layout. - */ - @DataClass.Generated.Member - public boolean isLayoutRequired() { - return mLayoutRequired; - } - - /** - * The menu items to be rendered in the selection toolbar. - */ - @DataClass.Generated.Member - public @NonNull List<ToolbarMenuItem> getMenuItems() { - return mMenuItems; - } - - /** - * A rect specifying where the selection toolbar on the screen. - */ - @DataClass.Generated.Member - public @NonNull Rect getContentRect() { - return mContentRect; - } - - /** - * A recommended maximum suggested width of the selection toolbar. - */ - @DataClass.Generated.Member - public int getSuggestedWidth() { - return mSuggestedWidth; - } - - /** - * The portion of the screen that is available to the selection toolbar. - */ - @DataClass.Generated.Member - public @NonNull Rect getViewPortOnScreen() { - return mViewPortOnScreen; - } - - /** - * The host application's input token, this allows the remote render service to transfer - * the touch focus to the host application. - */ - @DataClass.Generated.Member - public @NonNull IBinder getHostInputToken() { - return mHostInputToken; - } - - /** - * If the host application uses light theme. - */ - @DataClass.Generated.Member - public boolean isIsLightTheme() { - return mIsLightTheme; - } - - @Override - @DataClass.Generated.Member - public String toString() { - // You can override field toString logic by defining methods like: - // String fieldNameToString() { ... } - - return "ShowInfo { " + - "widgetToken = " + mWidgetToken + ", " + - "layoutRequired = " + mLayoutRequired + ", " + - "menuItems = " + mMenuItems + ", " + - "contentRect = " + mContentRect + ", " + - "suggestedWidth = " + mSuggestedWidth + ", " + - "viewPortOnScreen = " + mViewPortOnScreen + ", " + - "hostInputToken = " + mHostInputToken + ", " + - "isLightTheme = " + mIsLightTheme + - " }"; - } - - @Override - @DataClass.Generated.Member - public boolean equals(@android.annotation.Nullable Object o) { - // You can override field equality logic by defining either of the methods like: - // boolean fieldNameEquals(ShowInfo other) { ... } - // boolean fieldNameEquals(FieldType otherValue) { ... } - - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - @SuppressWarnings("unchecked") - ShowInfo that = (ShowInfo) o; - //noinspection PointlessBooleanExpression - return true - && mWidgetToken == that.mWidgetToken - && mLayoutRequired == that.mLayoutRequired - && java.util.Objects.equals(mMenuItems, that.mMenuItems) - && java.util.Objects.equals(mContentRect, that.mContentRect) - && mSuggestedWidth == that.mSuggestedWidth - && java.util.Objects.equals(mViewPortOnScreen, that.mViewPortOnScreen) - && java.util.Objects.equals(mHostInputToken, that.mHostInputToken) - && mIsLightTheme == that.mIsLightTheme; - } - - @Override - @DataClass.Generated.Member - public int hashCode() { - // You can override field hashCode logic by defining methods like: - // int fieldNameHashCode() { ... } - - int _hash = 1; - _hash = 31 * _hash + Long.hashCode(mWidgetToken); - _hash = 31 * _hash + Boolean.hashCode(mLayoutRequired); - _hash = 31 * _hash + java.util.Objects.hashCode(mMenuItems); - _hash = 31 * _hash + java.util.Objects.hashCode(mContentRect); - _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; - } - - @Override - @DataClass.Generated.Member - public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { - // You can override field parcelling by defining methods like: - // void parcelFieldName(Parcel dest, int flags) { ... } - - int flg = 0; - if (mLayoutRequired) flg |= 0x2; - if (mIsLightTheme) flg |= 0x80; - dest.writeInt(flg); - dest.writeLong(mWidgetToken); - dest.writeParcelableList(mMenuItems, flags); - dest.writeTypedObject(mContentRect, flags); - dest.writeInt(mSuggestedWidth); - dest.writeTypedObject(mViewPortOnScreen, flags); - dest.writeStrongBinder(mHostInputToken); - } - - @Override - @DataClass.Generated.Member - public int describeContents() { return 0; } - - /** @hide */ - @SuppressWarnings({"unchecked", "RedundantCast"}) - @DataClass.Generated.Member - /* package-private */ ShowInfo(@NonNull android.os.Parcel in) { - // You can override field unparcelling by defining methods like: - // static FieldType unparcelFieldName(Parcel in) { ... } - - 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); - Rect contentRect = (Rect) in.readTypedObject(Rect.CREATOR); - int suggestedWidth = in.readInt(); - Rect viewPortOnScreen = (Rect) in.readTypedObject(Rect.CREATOR); - IBinder hostInputToken = (IBinder) in.readStrongBinder(); - - this.mWidgetToken = widgetToken; - this.mLayoutRequired = layoutRequired; - this.mMenuItems = menuItems; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mMenuItems); - this.mContentRect = contentRect; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mContentRect); - this.mSuggestedWidth = suggestedWidth; - this.mViewPortOnScreen = viewPortOnScreen; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mViewPortOnScreen); - 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 - } - - @DataClass.Generated.Member - public static final @NonNull Parcelable.Creator<ShowInfo> CREATOR - = new Parcelable.Creator<ShowInfo>() { - @Override - public ShowInfo[] newArray(int size) { - return new ShowInfo[size]; - } - - @Override - public ShowInfo createFromParcel(@NonNull android.os.Parcel in) { - return new ShowInfo(in); - } - }; - - @DataClass.Generated( - 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\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() {} - - - //@formatter:on - // End of generated code - -} diff --git a/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java b/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java deleted file mode 100644 index 89347c613310..000000000000 --- a/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java +++ /dev/null @@ -1,543 +0,0 @@ -/* - * Copyright (C) 2021 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.view.selectiontoolbar; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.graphics.drawable.Icon; -import android.os.Parcelable; -import android.view.MenuItem; - -import com.android.internal.util.DataClass; - -/** - * The menu item that is used to show the selection toolbar. - * - * @hide - */ -@DataClass(genBuilder = true, genToString = true, genEqualsHashCode = true) -public final class ToolbarMenuItem implements Parcelable { - - /** - * The priority of menu item is unknown. - */ - public static final int PRIORITY_UNKNOWN = 0; - - /** - * The priority of menu item is shown in primary selection toolbar. - */ - public static final int PRIORITY_PRIMARY = 1; - - /** - * The priority of menu item is shown in overflow selection toolbar. - */ - public static final int PRIORITY_OVERFLOW = 2; - - /** - * The id of the menu item. - * - * @see MenuItem#getItemId() - */ - private final int mItemId; - - /** - * The title of the menu item. - * - * @see MenuItem#getTitle() - */ - @NonNull - private final CharSequence mTitle; - - /** - * The content description of the menu item. - * - * @see MenuItem#getContentDescription() - */ - @Nullable - private final CharSequence mContentDescription; - - /** - * The group id of the menu item. - * - * @see MenuItem#getGroupId() - */ - private final int mGroupId; - - /** - * The icon id of the menu item. - * - * @see MenuItem#getIcon() - */ - @Nullable - private final Icon mIcon; - - /** - * The tooltip text of the menu item. - * - * @see MenuItem#getTooltipText() - */ - @Nullable - private final CharSequence mTooltipText; - - /** - * The priority of the menu item used to display the order of the menu item. - */ - private final int mPriority; - - /** - * Returns the priority from a given {@link MenuItem}. - */ - public static int getPriorityFromMenuItem(MenuItem menuItem) { - if (menuItem.requiresActionButton()) { - return PRIORITY_PRIMARY; - } else if (menuItem.requiresOverflow()) { - return PRIORITY_OVERFLOW; - } - return PRIORITY_UNKNOWN; - } - - - - - // Code below generated by codegen v1.0.23. - // - // DO NOT MODIFY! - // CHECKSTYLE:OFF Generated code - // - // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java - // - // To exclude the generated code from IntelliJ auto-formatting enable (one-time): - // Settings > Editor > Code Style > Formatter Control - //@formatter:off - - - @android.annotation.IntDef(prefix = "PRIORITY_", value = { - PRIORITY_UNKNOWN, - PRIORITY_PRIMARY, - PRIORITY_OVERFLOW - }) - @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) - @DataClass.Generated.Member - public @interface Priority {} - - @DataClass.Generated.Member - public static String priorityToString(@Priority int value) { - switch (value) { - case PRIORITY_UNKNOWN: - return "PRIORITY_UNKNOWN"; - case PRIORITY_PRIMARY: - return "PRIORITY_PRIMARY"; - case PRIORITY_OVERFLOW: - return "PRIORITY_OVERFLOW"; - default: return Integer.toHexString(value); - } - } - - @DataClass.Generated.Member - /* package-private */ ToolbarMenuItem( - int itemId, - @NonNull CharSequence title, - @Nullable CharSequence contentDescription, - int groupId, - @Nullable Icon icon, - @Nullable CharSequence tooltipText, - int priority) { - this.mItemId = itemId; - this.mTitle = title; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mTitle); - this.mContentDescription = contentDescription; - this.mGroupId = groupId; - this.mIcon = icon; - this.mTooltipText = tooltipText; - this.mPriority = priority; - - // onConstructed(); // You can define this method to get a callback - } - - /** - * The id of the menu item. - * - * @see MenuItem#getItemId() - */ - @DataClass.Generated.Member - public int getItemId() { - return mItemId; - } - - /** - * The title of the menu item. - * - * @see MenuItem#getTitle() - */ - @DataClass.Generated.Member - public @NonNull CharSequence getTitle() { - return mTitle; - } - - /** - * The content description of the menu item. - * - * @see MenuItem#getContentDescription() - */ - @DataClass.Generated.Member - public @Nullable CharSequence getContentDescription() { - return mContentDescription; - } - - /** - * The group id of the menu item. - * - * @see MenuItem#getGroupId() - */ - @DataClass.Generated.Member - public int getGroupId() { - return mGroupId; - } - - /** - * The icon id of the menu item. - * - * @see MenuItem#getIcon() - */ - @DataClass.Generated.Member - public @Nullable Icon getIcon() { - return mIcon; - } - - /** - * The tooltip text of the menu item. - * - * @see MenuItem#getTooltipText() - */ - @DataClass.Generated.Member - public @Nullable CharSequence getTooltipText() { - return mTooltipText; - } - - /** - * The priority of the menu item used to display the order of the menu item. - */ - @DataClass.Generated.Member - public int getPriority() { - return mPriority; - } - - @Override - @DataClass.Generated.Member - public String toString() { - // You can override field toString logic by defining methods like: - // String fieldNameToString() { ... } - - return "ToolbarMenuItem { " + - "itemId = " + mItemId + ", " + - "title = " + mTitle + ", " + - "contentDescription = " + mContentDescription + ", " + - "groupId = " + mGroupId + ", " + - "icon = " + mIcon + ", " + - "tooltipText = " + mTooltipText + ", " + - "priority = " + mPriority + - " }"; - } - - @Override - @DataClass.Generated.Member - public boolean equals(@Nullable Object o) { - // You can override field equality logic by defining either of the methods like: - // boolean fieldNameEquals(ToolbarMenuItem other) { ... } - // boolean fieldNameEquals(FieldType otherValue) { ... } - - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - @SuppressWarnings("unchecked") - ToolbarMenuItem that = (ToolbarMenuItem) o; - //noinspection PointlessBooleanExpression - return true - && mItemId == that.mItemId - && java.util.Objects.equals(mTitle, that.mTitle) - && java.util.Objects.equals(mContentDescription, that.mContentDescription) - && mGroupId == that.mGroupId - && java.util.Objects.equals(mIcon, that.mIcon) - && java.util.Objects.equals(mTooltipText, that.mTooltipText) - && mPriority == that.mPriority; - } - - @Override - @DataClass.Generated.Member - public int hashCode() { - // You can override field hashCode logic by defining methods like: - // int fieldNameHashCode() { ... } - - int _hash = 1; - _hash = 31 * _hash + mItemId; - _hash = 31 * _hash + java.util.Objects.hashCode(mTitle); - _hash = 31 * _hash + java.util.Objects.hashCode(mContentDescription); - _hash = 31 * _hash + mGroupId; - _hash = 31 * _hash + java.util.Objects.hashCode(mIcon); - _hash = 31 * _hash + java.util.Objects.hashCode(mTooltipText); - _hash = 31 * _hash + mPriority; - return _hash; - } - - @Override - @DataClass.Generated.Member - public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { - // You can override field parcelling by defining methods like: - // void parcelFieldName(Parcel dest, int flags) { ... } - - byte flg = 0; - if (mContentDescription != null) flg |= 0x4; - if (mIcon != null) flg |= 0x10; - if (mTooltipText != null) flg |= 0x20; - dest.writeByte(flg); - dest.writeInt(mItemId); - dest.writeCharSequence(mTitle); - if (mContentDescription != null) dest.writeCharSequence(mContentDescription); - dest.writeInt(mGroupId); - if (mIcon != null) dest.writeTypedObject(mIcon, flags); - if (mTooltipText != null) dest.writeCharSequence(mTooltipText); - dest.writeInt(mPriority); - } - - @Override - @DataClass.Generated.Member - public int describeContents() { return 0; } - - /** @hide */ - @SuppressWarnings({"unchecked", "RedundantCast"}) - @DataClass.Generated.Member - /* package-private */ ToolbarMenuItem(@NonNull android.os.Parcel in) { - // You can override field unparcelling by defining methods like: - // static FieldType unparcelFieldName(Parcel in) { ... } - - byte flg = in.readByte(); - int itemId = in.readInt(); - CharSequence title = (CharSequence) in.readCharSequence(); - CharSequence contentDescription = (flg & 0x4) == 0 ? null : (CharSequence) in.readCharSequence(); - int groupId = in.readInt(); - Icon icon = (flg & 0x10) == 0 ? null : (Icon) in.readTypedObject(Icon.CREATOR); - CharSequence tooltipText = (flg & 0x20) == 0 ? null : (CharSequence) in.readCharSequence(); - int priority = in.readInt(); - - this.mItemId = itemId; - this.mTitle = title; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mTitle); - this.mContentDescription = contentDescription; - this.mGroupId = groupId; - this.mIcon = icon; - this.mTooltipText = tooltipText; - this.mPriority = priority; - - // onConstructed(); // You can define this method to get a callback - } - - @DataClass.Generated.Member - public static final @NonNull Parcelable.Creator<ToolbarMenuItem> CREATOR - = new Parcelable.Creator<ToolbarMenuItem>() { - @Override - public ToolbarMenuItem[] newArray(int size) { - return new ToolbarMenuItem[size]; - } - - @Override - public ToolbarMenuItem createFromParcel(@NonNull android.os.Parcel in) { - return new ToolbarMenuItem(in); - } - }; - - /** - * A builder for {@link ToolbarMenuItem} - */ - @SuppressWarnings("WeakerAccess") - @DataClass.Generated.Member - public static final class Builder { - - private int mItemId; - private @NonNull CharSequence mTitle; - private @Nullable CharSequence mContentDescription; - private int mGroupId; - private @Nullable Icon mIcon; - private @Nullable CharSequence mTooltipText; - private int mPriority; - - private long mBuilderFieldsSet = 0L; - - /** - * Creates a new Builder. - * - * @param itemId - * The id of the menu item. - * @param title - * The title of the menu item. - * @param contentDescription - * The content description of the menu item. - * @param groupId - * The group id of the menu item. - * @param icon - * The icon id of the menu item. - * @param tooltipText - * The tooltip text of the menu item. - * @param priority - * The priority of the menu item used to display the order of the menu item. - */ - public Builder( - int itemId, - @NonNull CharSequence title, - @Nullable CharSequence contentDescription, - int groupId, - @Nullable Icon icon, - @Nullable CharSequence tooltipText, - int priority) { - mItemId = itemId; - mTitle = title; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mTitle); - mContentDescription = contentDescription; - mGroupId = groupId; - mIcon = icon; - mTooltipText = tooltipText; - mPriority = priority; - } - - /** - * The id of the menu item. - * - * @see MenuItem#getItemId() - */ - @DataClass.Generated.Member - public @NonNull Builder setItemId(int value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x1; - mItemId = value; - return this; - } - - /** - * The title of the menu item. - * - * @see MenuItem#getTitle() - */ - @DataClass.Generated.Member - public @NonNull Builder setTitle(@NonNull CharSequence value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x2; - mTitle = value; - return this; - } - - /** - * The content description of the menu item. - * - * @see MenuItem#getContentDescription() - */ - @DataClass.Generated.Member - public @NonNull Builder setContentDescription(@NonNull CharSequence value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x4; - mContentDescription = value; - return this; - } - - /** - * The group id of the menu item. - * - * @see MenuItem#getGroupId() - */ - @DataClass.Generated.Member - public @NonNull Builder setGroupId(int value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x8; - mGroupId = value; - return this; - } - - /** - * The icon id of the menu item. - * - * @see MenuItem#getIcon() - */ - @DataClass.Generated.Member - public @NonNull Builder setIcon(@NonNull Icon value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x10; - mIcon = value; - return this; - } - - /** - * The tooltip text of the menu item. - * - * @see MenuItem#getTooltipText() - */ - @DataClass.Generated.Member - public @NonNull Builder setTooltipText(@NonNull CharSequence value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x20; - mTooltipText = value; - return this; - } - - /** - * The priority of the menu item used to display the order of the menu item. - */ - @DataClass.Generated.Member - public @NonNull Builder setPriority(int value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x40; - mPriority = value; - return this; - } - - /** Builds the instance. This builder should not be touched after calling this! */ - public @NonNull ToolbarMenuItem build() { - checkNotUsed(); - mBuilderFieldsSet |= 0x80; // Mark builder used - - ToolbarMenuItem o = new ToolbarMenuItem( - mItemId, - mTitle, - mContentDescription, - mGroupId, - mIcon, - mTooltipText, - mPriority); - return o; - } - - private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x80) != 0) { - throw new IllegalStateException( - "This Builder should not be reused. Use a new Builder instance instead"); - } - } - } - - @DataClass.Generated( - time = 1643200806234L, - codegenVersion = "1.0.23", - sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java", - inputSignatures = "public static final int PRIORITY_UNKNOWN\npublic static final int PRIORITY_PRIMARY\npublic static final int PRIORITY_OVERFLOW\nprivate final int mItemId\nprivate final @android.annotation.NonNull java.lang.CharSequence mTitle\nprivate final @android.annotation.Nullable java.lang.CharSequence mContentDescription\nprivate final int mGroupId\nprivate final @android.annotation.Nullable android.graphics.drawable.Icon mIcon\nprivate final @android.annotation.Nullable java.lang.CharSequence mTooltipText\nprivate final int mPriority\npublic static int getPriorityFromMenuItem(android.view.MenuItem)\nclass ToolbarMenuItem extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genToString=true, genEqualsHashCode=true)") - @Deprecated - private void __metadata() {} - - - //@formatter:on - // End of generated code - -} diff --git a/core/java/android/view/selectiontoolbar/WidgetInfo.java b/core/java/android/view/selectiontoolbar/WidgetInfo.java deleted file mode 100644 index 5d0fd473c914..000000000000 --- a/core/java/android/view/selectiontoolbar/WidgetInfo.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright (C) 2021 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.view.selectiontoolbar; - -import android.annotation.NonNull; -import android.graphics.Rect; -import android.os.Parcel; -import android.os.Parcelable; -import android.view.SurfaceControlViewHost; - -import com.android.internal.util.DataClass; - -/** - * The class holds the rendered content and the related information from the render service to - * be used to show on the selection toolbar. - * - * @hide - */ -@DataClass(genToString = true, genEqualsHashCode = true) -public final class WidgetInfo implements Parcelable { - - /** - * The token that is used to identify the selection toolbar. - */ - private final long mWidgetToken; - - /** - * A Rect that defines the size and positioning of the remote view with respect to - * its host window. - */ - @NonNull - private final Rect mContentRect; - - /** - * The SurfacePackage pointing to the remote view. - */ - @NonNull - private final SurfaceControlViewHost.SurfacePackage mSurfacePackage; - - - - // Code below generated by codegen v1.0.23. - // - // DO NOT MODIFY! - // CHECKSTYLE:OFF Generated code - // - // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/selectiontoolbar/WidgetInfo.java - // - // To exclude the generated code from IntelliJ auto-formatting enable (one-time): - // Settings > Editor > Code Style > Formatter Control - //@formatter:off - - - /** - * Creates a new WidgetInfo. - * - * @param widgetToken - * The token that is used to identify the selection toolbar. - * @param contentRect - * A Rect that defines the size and positioning of the remote view with respect to - * its host window. - * @param surfacePackage - * The SurfacePackage pointing to the remote view. - */ - @DataClass.Generated.Member - public WidgetInfo( - long widgetToken, - @NonNull Rect contentRect, - @NonNull SurfaceControlViewHost.SurfacePackage surfacePackage) { - this.mWidgetToken = widgetToken; - this.mContentRect = contentRect; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mContentRect); - this.mSurfacePackage = surfacePackage; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mSurfacePackage); - - // onConstructed(); // You can define this method to get a callback - } - - /** - * The token that is used to identify the selection toolbar. - */ - @DataClass.Generated.Member - public long getWidgetToken() { - return mWidgetToken; - } - - /** - * A Rect that defines the size and positioning of the remote view with respect to - * its host window. - */ - @DataClass.Generated.Member - public @NonNull Rect getContentRect() { - return mContentRect; - } - - /** - * The SurfacePackage pointing to the remote view. - */ - @DataClass.Generated.Member - public @NonNull SurfaceControlViewHost.SurfacePackage getSurfacePackage() { - return mSurfacePackage; - } - - @Override - @DataClass.Generated.Member - public String toString() { - // You can override field toString logic by defining methods like: - // String fieldNameToString() { ... } - - return "WidgetInfo { " + - "widgetToken = " + mWidgetToken + ", " + - "contentRect = " + mContentRect + ", " + - "surfacePackage = " + mSurfacePackage + - " }"; - } - - @Override - @DataClass.Generated.Member - public boolean equals(@android.annotation.Nullable Object o) { - // You can override field equality logic by defining either of the methods like: - // boolean fieldNameEquals(WidgetInfo other) { ... } - // boolean fieldNameEquals(FieldType otherValue) { ... } - - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - @SuppressWarnings("unchecked") - WidgetInfo that = (WidgetInfo) o; - //noinspection PointlessBooleanExpression - return true - && mWidgetToken == that.mWidgetToken - && java.util.Objects.equals(mContentRect, that.mContentRect) - && java.util.Objects.equals(mSurfacePackage, that.mSurfacePackage); - } - - @Override - @DataClass.Generated.Member - public int hashCode() { - // You can override field hashCode logic by defining methods like: - // int fieldNameHashCode() { ... } - - int _hash = 1; - _hash = 31 * _hash + Long.hashCode(mWidgetToken); - _hash = 31 * _hash + java.util.Objects.hashCode(mContentRect); - _hash = 31 * _hash + java.util.Objects.hashCode(mSurfacePackage); - return _hash; - } - - @Override - @DataClass.Generated.Member - public void writeToParcel(@NonNull Parcel dest, int flags) { - // You can override field parcelling by defining methods like: - // void parcelFieldName(Parcel dest, int flags) { ... } - - dest.writeLong(mWidgetToken); - dest.writeTypedObject(mContentRect, flags); - dest.writeTypedObject(mSurfacePackage, flags); - } - - @Override - @DataClass.Generated.Member - public int describeContents() { return 0; } - - /** @hide */ - @SuppressWarnings({"unchecked", "RedundantCast"}) - @DataClass.Generated.Member - /* package-private */ WidgetInfo(@NonNull Parcel in) { - // You can override field unparcelling by defining methods like: - // static FieldType unparcelFieldName(Parcel in) { ... } - - long widgetToken = in.readLong(); - Rect contentRect = (Rect) in.readTypedObject(Rect.CREATOR); - SurfaceControlViewHost.SurfacePackage surfacePackage = (SurfaceControlViewHost.SurfacePackage) in.readTypedObject(SurfaceControlViewHost.SurfacePackage.CREATOR); - - this.mWidgetToken = widgetToken; - this.mContentRect = contentRect; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mContentRect); - this.mSurfacePackage = surfacePackage; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mSurfacePackage); - - // onConstructed(); // You can define this method to get a callback - } - - @DataClass.Generated.Member - public static final @NonNull Parcelable.Creator<WidgetInfo> CREATOR - = new Parcelable.Creator<WidgetInfo>() { - @Override - public WidgetInfo[] newArray(int size) { - return new WidgetInfo[size]; - } - - @Override - public WidgetInfo createFromParcel(@NonNull Parcel in) { - return new WidgetInfo(in); - } - }; - - @DataClass.Generated( - time = 1643281495056L, - codegenVersion = "1.0.23", - sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/WidgetInfo.java", - inputSignatures = "private final long mWidgetToken\nprivate final @android.annotation.NonNull android.graphics.Rect mContentRect\nprivate final @android.annotation.NonNull android.view.SurfaceControlViewHost.SurfacePackage mSurfacePackage\nclass WidgetInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)") - @Deprecated - private void __metadata() {} - - - //@formatter:on - // End of generated code - -} diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index 1d6778b8a4a9..55b2251ac196 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -1498,6 +1498,11 @@ public class HorizontalScrollView extends FrameLayout { * @return The unconsumed delta after the EdgeEffects have had an opportunity to consume. */ private int consumeFlingInStretch(int unconsumed) { + int scrollX = getScrollX(); + if (scrollX < 0 || scrollX > getScrollRange()) { + // We've overscrolled, so don't stretch + return unconsumed; + } if (unconsumed > 0 && mEdgeGlowLeft != null && mEdgeGlowLeft.getDistance() != 0f) { int size = getWidth(); float deltaDistance = -unconsumed * FLING_DESTRETCH_FACTOR / size; diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index eeb6b43a89f2..d330ebf73323 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -1566,6 +1566,11 @@ public class ScrollView extends FrameLayout { * @return The unconsumed delta after the EdgeEffects have had an opportunity to consume. */ private int consumeFlingInStretch(int unconsumed) { + int scrollY = getScrollY(); + if (scrollY < 0 || scrollY > getScrollRange()) { + // We've overscrolled, so don't stretch + return unconsumed; + } if (unconsumed > 0 && mEdgeGlowTop != null && mEdgeGlowTop.getDistance() != 0f) { int size = getHeight(); float deltaDistance = -unconsumed * FLING_DESTRETCH_FACTOR / size; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 56349d10a404..7dbab96f3684 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -13202,9 +13202,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mTextOperationUser == null) { return super.isStylusHandwritingAvailable(); } - final int userId = mTextOperationUser.getIdentifier(); final InputMethodManager imm = getInputMethodManager(); - return imm.isStylusHandwritingAvailableAsUser(userId); + return imm.isStylusHandwritingAvailableAsUser(mTextOperationUser); } @Nullable diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl index 534c9de8102c..5ba2f6caac2d 100644 --- a/core/java/android/window/IWindowOrganizerController.aidl +++ b/core/java/android/window/IWindowOrganizerController.aidl @@ -80,13 +80,8 @@ interface IWindowOrganizerController { * Finishes a transition. This must be called for all created transitions. * @param transitionToken Which transition to finish * @param t Changes to make before finishing but in the same SF Transaction. Can be null. - * @param callback Called when t is finished applying. - * @return An ID for the sync operation (see {@link #applySyncTransaction}. This will be - * negative if no sync transaction was attached (null t or callback) */ - int finishTransition(in IBinder transitionToken, - in @nullable WindowContainerTransaction t, - in IWindowContainerTransactionCallback callback); + void finishTransition(in IBinder transitionToken, in @nullable WindowContainerTransaction t); /** @return An interface enabling the management of task organizers. */ ITaskOrganizerController getTaskOrganizerController(); diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java index 695d01e92316..2dc2cbca0548 100644 --- a/core/java/android/window/WindowOrganizer.java +++ b/core/java/android/window/WindowOrganizer.java @@ -115,19 +115,15 @@ public class WindowOrganizer { * Finishes a running transition. * @param transitionToken The transition to finish. Can't be null. * @param t A set of window operations to apply before finishing. - * @param callback A sync callback (if provided). See {@link #applySyncTransaction}. - * @return An ID for the sync operation if performed. See {@link #applySyncTransaction}. * * @hide */ @SuppressLint("ExecutorRegistration") @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) - public int finishTransition(@NonNull IBinder transitionToken, - @Nullable WindowContainerTransaction t, - @Nullable WindowContainerTransactionCallback callback) { + public void finishTransition(@NonNull IBinder transitionToken, + @Nullable WindowContainerTransaction t) { try { - return getWindowOrganizerController().finishTransition(transitionToken, t, - callback != null ? callback.mInterface : null); + getWindowOrganizerController().finishTransition(transitionToken, t); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java index 6a32529f31c7..c1d1b2754895 100644 --- a/core/java/android/window/WindowTokenClient.java +++ b/core/java/android/window/WindowTokenClient.java @@ -23,12 +23,12 @@ import android.annotation.AnyThread; import android.annotation.MainThread; import android.annotation.NonNull; import android.app.ActivityThread; -import android.app.IWindowToken; import android.app.ResourcesManager; import android.content.Context; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.inputmethodservice.AbstractInputMethodService; +import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Debug; @@ -37,6 +37,7 @@ import android.os.IBinder; import android.util.Log; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.function.pooled.PooledLambda; import java.lang.ref.WeakReference; @@ -52,7 +53,7 @@ import java.lang.ref.WeakReference; * * @hide */ -public class WindowTokenClient extends IWindowToken.Stub { +public class WindowTokenClient extends Binder { private static final String TAG = WindowTokenClient.class.getSimpleName(); /** @@ -95,16 +96,16 @@ public class WindowTokenClient extends IWindowToken.Stub { * @param newConfig the updated {@link Configuration} * @param newDisplayId the updated {@link android.view.Display} ID */ - @AnyThread - @Override + @VisibleForTesting + @MainThread public void onConfigurationChanged(Configuration newConfig, int newDisplayId) { - // TODO(b/290876897): No need to post on mHandler after migrating to ClientTransaction - postOnConfigurationChanged(newConfig, newDisplayId); + onConfigurationChanged(newConfig, newDisplayId, true /* shouldReportConfigChange */); } /** * Posts an {@link #onConfigurationChanged} to the main thread. */ + @VisibleForTesting public void postOnConfigurationChanged(@NonNull Configuration newConfig, int newDisplayId) { mHandler.post(PooledLambda.obtainRunnable(this::onConfigurationChanged, newConfig, newDisplayId, true /* shouldReportConfigChange */).recycleOnUse()); @@ -120,14 +121,13 @@ public class WindowTokenClient extends IWindowToken.Stub { * <p> * Note that this method must be executed on the main thread if * {@code shouldReportConfigChange} is {@code true}, which is usually from - * {@link IWindowToken#onConfigurationChanged(Configuration, int)} + * {@link #onConfigurationChanged(Configuration, int)} * directly, while this method could be run on any thread if it is used to initialize * Context's {@code Configuration} via {@link WindowTokenClientController#attachToDisplayArea} * or {@link WindowTokenClientController#attachToDisplayContent}. * * @param shouldReportConfigChange {@code true} to indicate that the {@code Configuration} * should be dispatched to listeners. - * */ @AnyThread public void onConfigurationChanged(Configuration newConfig, int newDisplayId, @@ -193,16 +193,12 @@ public class WindowTokenClient extends IWindowToken.Stub { } } - @AnyThread - @Override - public void onWindowTokenRemoved() { - // TODO(b/290876897): No need to post on mHandler after migrating to ClientTransaction - mHandler.post(PooledLambda.obtainRunnable( - WindowTokenClient::onWindowTokenRemovedInner, this).recycleOnUse()); - } - + /** + * Called when the attached window is removed from the display. + */ + @VisibleForTesting @MainThread - private void onWindowTokenRemovedInner() { + public void onWindowTokenRemoved() { final Context context = mContextRef.get(); if (context != null) { context.destroy(); diff --git a/core/java/android/window/WindowTokenClientController.java b/core/java/android/window/WindowTokenClientController.java index 7a84123c91e1..10f6d5e7abaa 100644 --- a/core/java/android/window/WindowTokenClientController.java +++ b/core/java/android/window/WindowTokenClientController.java @@ -160,7 +160,7 @@ public class WindowTokenClientController { /** Detaches a {@link WindowTokenClient} from associated WindowContainer if there's one. */ public void detachIfNeeded(@NonNull WindowTokenClient client) { synchronized (mLock) { - if (mWindowTokenClientMap.remove(client.asBinder()) == null) { + if (mWindowTokenClientMap.remove(client) == null) { return; } } @@ -174,7 +174,7 @@ public class WindowTokenClientController { private void onWindowContextTokenAttached(@NonNull WindowTokenClient client, @NonNull WindowContextInfo info, boolean shouldReportConfigChange) { synchronized (mLock) { - mWindowTokenClientMap.put(client.asBinder(), client); + mWindowTokenClientMap.put(client, client); } if (shouldReportConfigChange) { // Should trigger an #onConfigurationChanged callback to the WindowContext. Post the diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java index f19f6c7949d2..2efe44544f37 100644 --- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java +++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java @@ -128,7 +128,7 @@ public class AccessibilityShortcutController { DialogStatus.SHOWN, }) /** Denotes the user shortcut type. */ - private @interface DialogStatus { + @interface DialogStatus { int NOT_SHOWN = 0; int SHOWN = 1; } @@ -333,12 +333,35 @@ public class AccessibilityShortcutController { // Avoid non-a11y users accidentally turning shortcut on without reading this carefully. // Put "don't turn on" as the primary action. final AlertDialog alertDialog = mFrameworkObjectProvider.getAlertDialogBuilder( - // Use SystemUI context so we pick up any theme set in a vendor overlay - mFrameworkObjectProvider.getSystemUiContext()) + // Use SystemUI context so we pick up any theme set in a vendor overlay + mFrameworkObjectProvider.getSystemUiContext()) .setTitle(getShortcutWarningTitle(targets)) .setMessage(getShortcutWarningMessage(targets)) .setCancelable(false) - .setNegativeButton(R.string.accessibility_shortcut_on, null) + .setNegativeButton(R.string.accessibility_shortcut_on, + (DialogInterface d, int which) -> { + String targetServices = Settings.Secure.getStringForUser( + mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, userId); + String defaultService = mContext.getString( + R.string.config_defaultAccessibilityService); + // If the targetServices is null, means the user enables a + // shortcut for the default service by triggering the volume keys + // shortcut in the SUW instead of intentionally configuring the + // shortcut on UI. + if (targetServices == null && !TextUtils.isEmpty(defaultService)) { + // The defaultService in the string resource could be a shorten + // form like com.google.android.marvin.talkback/.TalkBackService. + // Converts it to the componentName for consistency before saving + // to the Settings. + final ComponentName configDefaultService = + ComponentName.unflattenFromString(defaultService); + Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, + configDefaultService.flattenToString(), + userId); + } + }) .setPositiveButton(R.string.accessibility_shortcut_off, (DialogInterface d, int which) -> { Settings.Secure.putStringForUser(mContext.getContentResolver(), diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index d433cd652606..5f688f6406bc 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -158,7 +158,8 @@ interface IBatteryStats { @EnforcePermission("UPDATE_DEVICE_STATS") void notePhoneSignalStrength(in SignalStrength signalStrength); @EnforcePermission("UPDATE_DEVICE_STATS") - void notePhoneDataConnectionState(int dataType, boolean hasData, int serviceType, int nrFrequency); + void notePhoneDataConnectionState(int dataType, boolean hasData, int serviceType, int nrState, + int nrFrequency); @EnforcePermission("UPDATE_DEVICE_STATS") void notePhoneState(int phoneState); @EnforcePermission("UPDATE_DEVICE_STATS") diff --git a/core/java/com/android/internal/app/NetInitiatedActivity.java b/core/java/com/android/internal/app/NetInitiatedActivity.java deleted file mode 100644 index f34aabbeded6..000000000000 --- a/core/java/com/android/internal/app/NetInitiatedActivity.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2007 Google Inc. - * - * 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.app; - -import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.location.LocationManagerInternal; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.util.Log; - -import com.android.internal.R; -import com.android.internal.location.GpsNetInitiatedHandler; -import com.android.server.LocalServices; - -/** - * This activity is shown to the user for them to accept or deny network-initiated - * requests. It uses the alert dialog style. It will be launched from a notification. - */ -public class NetInitiatedActivity extends AlertActivity implements DialogInterface.OnClickListener { - - private static final String TAG = "NetInitiatedActivity"; - - private static final boolean DEBUG = true; - - private static final int POSITIVE_BUTTON = AlertDialog.BUTTON_POSITIVE; - private static final int NEGATIVE_BUTTON = AlertDialog.BUTTON_NEGATIVE; - - private static final int GPS_NO_RESPONSE_TIME_OUT = 1; - // Received ID from intent, -1 when no notification is in progress - private int notificationId = -1; - private int timeout = -1; - private int default_response = -1; - private int default_response_timeout = 6; - - private final Handler mHandler = new Handler() { - public void handleMessage(Message msg) { - switch (msg.what) { - case GPS_NO_RESPONSE_TIME_OUT: { - if (notificationId != -1) { - sendUserResponse(default_response); - } - finish(); - } - break; - default: - } - } - }; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); - - // Set up the "dialog" - final Intent intent = getIntent(); - final AlertController.AlertParams p = mAlertParams; - Context context = getApplicationContext(); - p.mTitle = intent.getStringExtra(GpsNetInitiatedHandler.NI_INTENT_KEY_TITLE); - p.mMessage = intent.getStringExtra(GpsNetInitiatedHandler.NI_INTENT_KEY_MESSAGE); - p.mPositiveButtonText = String.format(context.getString(R.string.gpsVerifYes)); - p.mPositiveButtonListener = this; - p.mNegativeButtonText = String.format(context.getString(R.string.gpsVerifNo)); - p.mNegativeButtonListener = this; - - notificationId = intent.getIntExtra(GpsNetInitiatedHandler.NI_INTENT_KEY_NOTIF_ID, -1); - timeout = intent.getIntExtra(GpsNetInitiatedHandler.NI_INTENT_KEY_TIMEOUT, default_response_timeout); - default_response = intent.getIntExtra(GpsNetInitiatedHandler.NI_INTENT_KEY_DEFAULT_RESPONSE, GpsNetInitiatedHandler.GPS_NI_RESPONSE_ACCEPT); - if (DEBUG) Log.d(TAG, "onCreate() : notificationId: " + notificationId + " timeout: " + timeout + " default_response:" + default_response); - - mHandler.sendMessageDelayed(mHandler.obtainMessage(GPS_NO_RESPONSE_TIME_OUT), (timeout * 1000)); - setupAlert(); - } - - @Override - protected void onResume() { - super.onResume(); - if (DEBUG) Log.d(TAG, "onResume"); - } - - @Override - protected void onPause() { - super.onPause(); - if (DEBUG) Log.d(TAG, "onPause"); - } - - /** - * {@inheritDoc} - */ - public void onClick(DialogInterface dialog, int which) { - if (which == POSITIVE_BUTTON) { - sendUserResponse(GpsNetInitiatedHandler.GPS_NI_RESPONSE_ACCEPT); - } - if (which == NEGATIVE_BUTTON) { - sendUserResponse(GpsNetInitiatedHandler.GPS_NI_RESPONSE_DENY); - } - - // No matter what, finish the activity - finish(); - notificationId = -1; - } - - // Respond to NI Handler under GnssLocationProvider, 1 = accept, 2 = deny - private void sendUserResponse(int response) { - if (DEBUG) Log.d(TAG, "sendUserResponse, response: " + response); - LocationManagerInternal lm = LocalServices.getService(LocationManagerInternal.class); - lm.sendNiResponse(notificationId, response); - } -} diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 2445daf89b64..ac15f11ee989 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -40,6 +40,7 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTE import static com.android.internal.annotations.VisibleForTesting.Visibility.PROTECTED; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringRes; import android.annotation.UiThread; @@ -68,6 +69,7 @@ import android.content.pm.UserInfo; import android.content.res.Configuration; import android.content.res.TypedArray; import android.graphics.Insets; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; @@ -93,6 +95,7 @@ import android.view.ViewGroup.LayoutParams; import android.view.Window; import android.view.WindowInsets; import android.view.WindowManager; +import android.view.accessibility.AccessibilityEvent; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.Button; @@ -488,6 +491,14 @@ public class ResolverActivity extends Activity implements rdl.setOnApplyWindowInsetsListener(this::onApplyWindowInsets); mResolverDrawerLayout = rdl; + + for (int i = 0, size = mMultiProfilePagerAdapter.getCount(); i < size; i++) { + View view = mMultiProfilePagerAdapter.getItem(i).rootView.findViewById( + R.id.resolver_list); + if (view != null) { + view.setAccessibilityDelegate(new AppListAccessibilityDelegate(rdl)); + } + } } mProfileView = findViewById(R.id.profile_button); @@ -2607,4 +2618,41 @@ public class ResolverActivity extends Activity implements } return resolveInfo.userHandle; } + + /** + * An a11y delegate that expands resolver drawer when gesture navigation reaches a partially + * invisible target in the list. + */ + private static class AppListAccessibilityDelegate extends View.AccessibilityDelegate { + private final ResolverDrawerLayout mDrawer; + @Nullable + private final View mBottomBar; + private final Rect mRect = new Rect(); + + private AppListAccessibilityDelegate(ResolverDrawerLayout drawer) { + mDrawer = drawer; + mBottomBar = mDrawer.findViewById(R.id.button_bar_container); + } + + @Override + public boolean onRequestSendAccessibilityEvent(@androidx.annotation.NonNull ViewGroup host, + @NonNull View child, + @NonNull AccessibilityEvent event) { + boolean result = super.onRequestSendAccessibilityEvent(host, child, event); + if (result && event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED + && mDrawer.isCollapsed()) { + child.getBoundsOnScreen(mRect); + int childTop = mRect.top; + int childBottom = mRect.bottom; + mDrawer.getBoundsOnScreen(mRect, true); + int bottomBarHeight = mBottomBar == null ? 0 : mBottomBar.getHeight(); + int drawerTop = mRect.top; + int drawerBottom = mRect.bottom - bottomBarHeight; + if (drawerTop > childTop || childBottom > drawerBottom) { + mDrawer.setCollapsed(false); + } + } + return result; + } + } } diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java index 10336bd36c28..9233050c97ad 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java @@ -70,10 +70,6 @@ public class SystemUiSystemPropertiesFlags { public static final Flag OTP_REDACTION = devFlag("persist.sysui.notification.otp_redaction"); - /** Gating the removal of sorting-notifications-by-interruptiveness. */ - public static final Flag NO_SORT_BY_INTERRUPTIVENESS = - releasedFlag("persist.sysui.notification.no_sort_by_interruptiveness"); - /** Gating the logging of DND state change events. */ public static final Flag LOG_DND_STATE_EVENTS = releasedFlag("persist.sysui.notification.log_dnd_state_events"); @@ -86,7 +82,7 @@ public class SystemUiSystemPropertiesFlags { public static final Flag RANKING_UPDATE_ASHMEM = devFlag( "persist.sysui.notification.ranking_update_ashmem"); - public static final Flag PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS = devFlag( + public static final Flag PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS = releasedFlag( "persist.sysui.notification.propagate_channel_updates_to_conversations"); } diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java index 1cde7421fe6b..0a938ef4d81c 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistory.java +++ b/core/java/com/android/internal/os/BatteryStatsHistory.java @@ -1226,6 +1226,17 @@ public class BatteryStatsHistory { } /** + * Records a data connection type change event. + */ + public void recordNrStateChangeEvent(long elapsedRealtimeMs, long uptimeMs, + int nrState) { + mHistoryCur.states2 = setBitField(mHistoryCur.states2, nrState, + HistoryItem.STATE2_NR_STATE_SHIFT, + HistoryItem.STATE2_NR_STATE_MASK); + writeHistoryItem(elapsedRealtimeMs, uptimeMs); + } + + /** * Records a WiFi supplicant state change event. */ public void recordWifiSupplicantStateChangeEvent(long elapsedRealtimeMs, long uptimeMs, @@ -1556,7 +1567,7 @@ public class BatteryStatsHistory { State2 change int: if C in the first token is set, 31 23 15 7 0 - █M|L|K|J|I|H|H|G█F|E|D|C| | | | █ | | | | | | |N█N|B|B|B|A|A|A|A█ + █M|L|K|J|I|H|H|G█F|E|D|C| | | | █ | | | | |O|O|N█N|B|B|B|A|A|A|A█ A: 4 bits indicating the wifi supplicant state: {@link BatteryStats#WIFI_SUPPL_STATE_NAMES}. B: 3 bits indicating the wifi signal strength: 0, 1, 2, 3, 4. @@ -1572,6 +1583,7 @@ public class BatteryStatsHistory { L: video was playing. M: power save mode was on. N: 2 bits indicating the gps signal strength: poor, good, none. + O: 2 bits indicating nr state: none, restricted, not restricted, connected. Wakelock/wakereason struct: if D in the first token is set, Event struct: if E in the first token is set, diff --git a/core/java/com/android/internal/os/TEST_MAPPING b/core/java/com/android/internal/os/TEST_MAPPING index 60b160ad18c2..d552e0b8c643 100644 --- a/core/java/com/android/internal/os/TEST_MAPPING +++ b/core/java/com/android/internal/os/TEST_MAPPING @@ -38,11 +38,18 @@ ], "name": "FrameworksServicesTests", "options": [ - { "include-filter": "com.android.server.am.BatteryStatsServiceTest" }, - { "include-filter": "com.android.server.power.stats.BatteryStatsTests" } + { "include-filter": "com.android.server.am.BatteryStatsServiceTest" } ] }, { + "file_patterns": [ + "Battery[^/]*\\.java", + "Kernel[^/]*\\.java", + "[^/]*Power[^/]*\\.java" + ], + "name": "PowerStatsTests" + }, + { "name": "FrameworksCoreTests", "options": [ { diff --git a/core/java/com/android/internal/power/TEST_MAPPING b/core/java/com/android/internal/power/TEST_MAPPING index c6cab183d970..1946f5cc99eb 100644 --- a/core/java/com/android/internal/power/TEST_MAPPING +++ b/core/java/com/android/internal/power/TEST_MAPPING @@ -8,11 +8,7 @@ ] }, { - "name": "FrameworksServicesTests", - "options": [ - { "include-filter": "com.android.server.am.BatteryStatsServiceTest" }, - { "include-filter": "com.android.server.power.stats.BatteryStatsTests" } - ] + "name": "PowerStatsTests" } ] } diff --git a/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java index f7af67b3b2a8..e9449eb96bb3 100644 --- a/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java +++ b/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java @@ -21,7 +21,6 @@ import android.content.Context; import android.graphics.Rect; import android.view.MenuItem; import android.view.View; -import android.view.selectiontoolbar.SelectionToolbarManager; import android.widget.PopupWindow; import java.util.List; @@ -89,14 +88,10 @@ public interface FloatingToolbarPopup { @Nullable PopupWindow.OnDismissListener onDismiss); /** - * Returns {@link RemoteFloatingToolbarPopup} implementation if the system selection toolbar - * enabled, otherwise returns {@link LocalFloatingToolbarPopup} implementation. + * Returns {@link LocalFloatingToolbarPopup} implementation. */ static FloatingToolbarPopup createInstance(Context context, View parent) { - boolean enabled = SelectionToolbarManager.isRemoteSelectionToolbarEnabled(context); - return enabled - ? new RemoteFloatingToolbarPopup(context, parent) - : new LocalFloatingToolbarPopup(context, parent); + return new LocalFloatingToolbarPopup(context, parent); } } diff --git a/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java deleted file mode 100644 index 8787c39458b9..000000000000 --- a/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java +++ /dev/null @@ -1,572 +0,0 @@ -/* - * Copyright (C) 2021 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.widget.floatingtoolbar; - -import static android.view.selectiontoolbar.SelectionToolbarManager.NO_TOOLBAR_ID; - -import android.annotation.IntDef; -import android.annotation.NonNull; -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; -import android.graphics.PixelFormat; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.Icon; -import android.text.TextUtils; -import android.util.Log; -import android.view.Gravity; -import android.view.MenuItem; -import android.view.SurfaceView; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.view.selectiontoolbar.ISelectionToolbarCallback; -import android.view.selectiontoolbar.SelectionToolbarManager; -import android.view.selectiontoolbar.ShowInfo; -import android.view.selectiontoolbar.ToolbarMenuItem; -import android.view.selectiontoolbar.WidgetInfo; -import android.widget.LinearLayout; -import android.widget.PopupWindow; - -import com.android.internal.R; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Objects; - -/** - * A popup window used by the floating toolbar to render menu items in the remote system process. - * - * It holds 2 panels (i.e. main panel and overflow panel) and an overflow button - * to transition between panels. - */ -public final class RemoteFloatingToolbarPopup implements FloatingToolbarPopup { - - private static final boolean DEBUG = - Log.isLoggable(FloatingToolbar.FLOATING_TOOLBAR_TAG, Log.VERBOSE); - - private static final int TOOLBAR_STATE_SHOWN = 1; - private static final int TOOLBAR_STATE_HIDDEN = 2; - private static final int TOOLBAR_STATE_DISMISSED = 3; - - @IntDef(prefix = {"TOOLBAR_STATE_"}, value = { - TOOLBAR_STATE_SHOWN, - TOOLBAR_STATE_HIDDEN, - TOOLBAR_STATE_DISMISSED - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ToolbarState { - } - - @NonNull - private final SelectionToolbarManager mSelectionToolbarManager; - // Parent for the popup window. - @NonNull - private final View mParent; - // A popup window used for showing menu items rendered by the remote system process - @NonNull - private final PopupWindow mPopupWindow; - // The callback to handle remote rendered selection toolbar. - @NonNull - private final SelectionToolbarCallbackImpl mSelectionToolbarCallback; - - // tracks this popup state. - private @ToolbarState int mState; - - // The token of the current showing floating toolbar. - private long mFloatingToolbarToken; - private final Rect mPreviousContentRect = new Rect(); - private List<MenuItem> mMenuItems; - private MenuItem.OnMenuItemClickListener mMenuItemClickListener; - 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]; - - public RemoteFloatingToolbarPopup(Context context, View parent) { - mParent = Objects.requireNonNull(parent); - 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, - MenuItem.OnMenuItemClickListener menuItemClickListener, Rect contentRect) { - Objects.requireNonNull(menuItems); - Objects.requireNonNull(menuItemClickListener); - if (isShowing() && Objects.equals(menuItems, mMenuItems) - && Objects.equals(contentRect, mPreviousContentRect)) { - if (DEBUG) { - Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, - "Ignore duplicate show() for the same content."); - } - return; - } - - boolean isLayoutRequired = mMenuItems == null - || !MenuItemRepr.reprEquals(menuItems, mMenuItems) - || mWidthChanged; - if (isLayoutRequired) { - mSelectionToolbarManager.dismissToolbar(mFloatingToolbarToken); - doDismissPopupWindow(); - } - mMenuItemClickListener = menuItemClickListener; - mMenuItems = menuItems; - - mParent.getWindowVisibleDisplayFrame(mScreenViewPort); - final int suggestWidth = mSuggestedWidth > 0 - ? mSuggestedWidth - : mParent.getResources().getDimensionPixelSize( - R.dimen.floating_toolbar_preferred_width); - final ShowInfo showInfo = new ShowInfo( - mFloatingToolbarToken, isLayoutRequired, - getToolbarMenuItems(mMenuItems), - contentRect, - suggestWidth, - mScreenViewPort, - mParent.getViewRootImpl().getInputToken(), mIsLightTheme); - if (DEBUG) { - Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, - "RemoteFloatingToolbarPopup.show() for " + showInfo); - } - mSelectionToolbarManager.showToolbar(showInfo, mSelectionToolbarCallback); - mPreviousContentRect.set(contentRect); - } - - @UiThread - @Override - public void dismiss() { - if (mState == TOOLBAR_STATE_DISMISSED) { - Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG, - "The floating toolbar already dismissed."); - return; - } - if (DEBUG) { - Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, - "RemoteFloatingToolbarPopup.dismiss()."); - } - mSelectionToolbarManager.dismissToolbar(mFloatingToolbarToken); - doDismissPopupWindow(); - } - - @UiThread - @Override - public void hide() { - if (mState == TOOLBAR_STATE_DISMISSED || mState == TOOLBAR_STATE_HIDDEN) { - if (DEBUG) { - Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, - "The floating toolbar already dismissed or hidden."); - } - return; - } - if (DEBUG) { - Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, - "RemoteFloatingToolbarPopup.hide()."); - } - mSelectionToolbarManager.hideToolbar(mFloatingToolbarToken); - mState = TOOLBAR_STATE_HIDDEN; - mPopupWindow.dismiss(); - } - - @UiThread - @Override - public void setSuggestedWidth(int suggestedWidth) { - int difference = Math.abs(suggestedWidth - mSuggestedWidth); - mWidthChanged = difference > (mSuggestedWidth * 0.2); - mSuggestedWidth = suggestedWidth; - } - - @Override - public void setWidthChanged(boolean widthChanged) { - mWidthChanged = widthChanged; - } - - @UiThread - @Override - public boolean isHidden() { - return mState == TOOLBAR_STATE_HIDDEN; - } - - @UiThread - @Override - public boolean isShowing() { - return mState == TOOLBAR_STATE_SHOWN; - } - - @UiThread - @Override - public boolean setOutsideTouchable(boolean outsideTouchable, - @Nullable PopupWindow.OnDismissListener onDismiss) { - if (mState == TOOLBAR_STATE_DISMISSED) { - return false; - } - boolean ret = false; - if (mPopupWindow.isOutsideTouchable() ^ outsideTouchable) { - mPopupWindow.setOutsideTouchable(outsideTouchable); - mPopupWindow.setFocusable(!outsideTouchable); - mPopupWindow.update(); - ret = true; - } - mPopupWindow.setOnDismissListener(onDismiss); - return ret; - } - - private void updatePopupWindowContent(WidgetInfo widgetInfo) { - if (DEBUG) { - Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, "updatePopupWindowContent."); - } - ViewGroup contentContainer = (ViewGroup) mPopupWindow.getContentView(); - contentContainer.removeAllViews(); - SurfaceView surfaceView = new SurfaceView(mParent.getContext()); - surfaceView.setZOrderOnTop(true); - surfaceView.getHolder().setFormat(PixelFormat.TRANSPARENT); - surfaceView.setChildSurfacePackage(widgetInfo.getSurfacePackage()); - contentContainer.addView(surfaceView); - } - - private MenuItem getMenuItemByToolbarMenuItem(ToolbarMenuItem toolbarMenuItem) { - for (MenuItem item : mMenuItems) { - if (toolbarMenuItem.getItemId() == item.getItemId()) { - return item; - } - } - return null; - } - - private Point getCoordinatesInWindow(int x, int y) { - // We later specify the location of PopupWindow relative to the attached window. - // The idea here is that 1) we can get the location of a View in both window coordinates - // and screen coordinates, where the offset between them should be equal to the window - // origin, and 2) we can use an arbitrary for this calculation while calculating the - // location of the rootview is supposed to be least expensive. - // TODO: Consider to use PopupWindow.setIsLaidOutInScreen(true) so that we can avoid - // the following calculation. - mParent.getRootView().getLocationOnScreen(mCoordsOnScreen); - mParent.getRootView().getLocationInWindow(mCoordsOnWindow); - int windowLeftOnScreen = mCoordsOnScreen[0] - mCoordsOnWindow[0]; - int windowTopOnScreen = mCoordsOnScreen[1] - mCoordsOnWindow[1]; - return new Point(Math.max(0, x - windowLeftOnScreen), Math.max(0, y - windowTopOnScreen)); - } - - private static List<ToolbarMenuItem> getToolbarMenuItems(List<MenuItem> menuItems) { - final List<ToolbarMenuItem> list = new ArrayList<>(menuItems.size()); - for (MenuItem menuItem : menuItems) { - // TODO: use ToolbarMenuItem.Builder(MenuItem) instead - ToolbarMenuItem toolbarMenuItem = new ToolbarMenuItem.Builder(menuItem.getItemId(), - menuItem.getTitle(), menuItem.getContentDescription(), menuItem.getGroupId(), - convertDrawableToIcon(menuItem.getIcon()), - menuItem.getTooltipText(), - ToolbarMenuItem.getPriorityFromMenuItem(menuItem)).build(); - list.add(toolbarMenuItem); - } - return list; - } - - private static Icon convertDrawableToIcon(Drawable drawable) { - if (drawable == null) { - return null; - } - if (drawable instanceof BitmapDrawable) { - final BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; - if (bitmapDrawable.getBitmap() != null) { - return Icon.createWithBitmap(bitmapDrawable.getBitmap()); - } - } - final Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), - drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); - drawable.draw(canvas); - return Icon.createWithBitmap(bitmap); - } - - private static PopupWindow createPopupWindow(Context content) { - ViewGroup popupContentHolder = new LinearLayout(content); - PopupWindow popupWindow = new PopupWindow(popupContentHolder); - popupWindow.setClippingEnabled(false); - popupWindow.setWindowLayoutType( - WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL); - popupWindow.setAnimationStyle(0); - popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); - return popupWindow; - } - - private void doDismissPopupWindow() { - if (DEBUG) { - Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, "RemoteFloatingToolbarPopup.doDismiss()."); - } - mState = TOOLBAR_STATE_DISMISSED; - mMenuItems = null; - mMenuItemClickListener = null; - mFloatingToolbarToken = 0; - mSuggestedWidth = 0; - mWidthChanged = true; - resetCoords(); - mPreviousContentRect.setEmpty(); - mScreenViewPort.setEmpty(); - mPopupWindow.dismiss(); - } - - private void resetCoords() { - mCoordsOnScreen[0] = 0; - mCoordsOnScreen[1] = 0; - mCoordsOnWindow[0] = 0; - mCoordsOnWindow[1] = 0; - } - - private void runOnUiThread(Runnable runnable) { - mParent.post(runnable); - } - - private void onShow(WidgetInfo info) { - runOnUiThread(() -> { - mFloatingToolbarToken = info.getWidgetToken(); - mState = TOOLBAR_STATE_SHOWN; - updatePopupWindowContent(info); - Rect contentRect = info.getContentRect(); - mPopupWindow.setWidth(contentRect.width()); - mPopupWindow.setHeight(contentRect.height()); - final Point coords = getCoordinatesInWindow(contentRect.left, contentRect.top); - mPopupWindow.showAtLocation(mParent, Gravity.NO_GRAVITY, coords.x, coords.y); - }); - } - - private void onWidgetUpdated(WidgetInfo info) { - runOnUiThread(() -> { - if (!isShowing()) { - Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG, - "onWidgetUpdated(): The widget isn't showing."); - return; - } - updatePopupWindowContent(info); - Rect contentRect = info.getContentRect(); - Point coords = getCoordinatesInWindow(contentRect.left, contentRect.top); - if (DEBUG) { - Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, - "PopupWindow x= " + coords.x + " y= " + coords.y + " w=" - + contentRect.width() + " h=" + contentRect.height()); - } - mPopupWindow.update(coords.x, coords.y, contentRect.width(), contentRect.height()); - }); - } - - private void onToolbarShowTimeout() { - runOnUiThread(() -> { - if (mState == TOOLBAR_STATE_DISMISSED) { - return; - } - doDismissPopupWindow(); - }); - } - - private void onMenuItemClicked(ToolbarMenuItem toolbarMenuItem) { - runOnUiThread(() -> { - if (mMenuItems == null || mMenuItemClickListener == null) { - return; - } - MenuItem item = getMenuItemByToolbarMenuItem(toolbarMenuItem); - if (DEBUG) { - Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, - "SelectionToolbarCallbackImpl onMenuItemClicked. toolbarMenuItem=" - + toolbarMenuItem + " item=" + item); - } - // TODO: handle the menu item like clipboard - if (item != null) { - mMenuItemClickListener.onMenuItemClick(item); - } else { - Log.e(FloatingToolbar.FLOATING_TOOLBAR_TAG, - "onMenuItemClicked: cannot find menu item."); - } - }); - } - - private static class SelectionToolbarCallbackImpl extends ISelectionToolbarCallback.Stub { - - private final WeakReference<RemoteFloatingToolbarPopup> mRemotePopup; - - SelectionToolbarCallbackImpl(RemoteFloatingToolbarPopup popup) { - mRemotePopup = new WeakReference<>(popup); - } - - @Override - public void onShown(WidgetInfo info) { - if (DEBUG) { - Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, - "SelectionToolbarCallbackImpl onShown: " + info); - } - final RemoteFloatingToolbarPopup remoteFloatingToolbarPopup = mRemotePopup.get(); - if (remoteFloatingToolbarPopup != null) { - remoteFloatingToolbarPopup.onShow(info); - } else { - Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG, - "Lost remoteFloatingToolbarPopup reference for onShown."); - } - } - - @Override - public void onWidgetUpdated(WidgetInfo info) { - if (DEBUG) { - Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, - "SelectionToolbarCallbackImpl onWidgetUpdated: info = " + info); - } - final RemoteFloatingToolbarPopup remoteFloatingToolbarPopup = mRemotePopup.get(); - if (remoteFloatingToolbarPopup != null) { - remoteFloatingToolbarPopup.onWidgetUpdated(info); - } else { - Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG, - "Lost remoteFloatingToolbarPopup reference for onWidgetUpdated."); - } - } - - @Override - public void onToolbarShowTimeout() { - final RemoteFloatingToolbarPopup remoteFloatingToolbarPopup = mRemotePopup.get(); - if (remoteFloatingToolbarPopup != null) { - remoteFloatingToolbarPopup.onToolbarShowTimeout(); - } else { - Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG, - "Lost remoteFloatingToolbarPopup reference for onToolbarShowTimeout."); - } - } - - @Override - public void onMenuItemClicked(ToolbarMenuItem toolbarMenuItem) { - final RemoteFloatingToolbarPopup remoteFloatingToolbarPopup = mRemotePopup.get(); - if (remoteFloatingToolbarPopup != null) { - remoteFloatingToolbarPopup.onMenuItemClicked(toolbarMenuItem); - } else { - Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG, - "Lost remoteFloatingToolbarPopup reference for onMenuItemClicked."); - } - } - - @Override - public void onError(int errorCode) { - if (DEBUG) { - Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, - "SelectionToolbarCallbackImpl onError: " + errorCode); - } - } - } - - /** - * Represents the identity of a MenuItem that is rendered in a FloatingToolbarPopup. - */ - static final class MenuItemRepr { - - public final int mItemId; - public final int mGroupId; - @Nullable - public final String mTitle; - @Nullable private final Drawable mIcon; - - private MenuItemRepr( - int itemId, int groupId, @Nullable CharSequence title, - @Nullable Drawable icon) { - mItemId = itemId; - mGroupId = groupId; - mTitle = (title == null) ? null : title.toString(); - mIcon = icon; - } - - /** - * Creates an instance of MenuItemRepr for the specified menu item. - */ - public static MenuItemRepr of(MenuItem menuItem) { - return new MenuItemRepr( - menuItem.getItemId(), - menuItem.getGroupId(), - menuItem.getTitle(), - menuItem.getIcon()); - } - - /** - * Returns this object's hashcode. - */ - @Override - public int hashCode() { - return Objects.hash(mItemId, mGroupId, mTitle, mIcon); - } - - /** - * Returns true if this object is the same as the specified object. - */ - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (!(o instanceof LocalFloatingToolbarPopup.MenuItemRepr)) { - return false; - } - final MenuItemRepr other = (MenuItemRepr) o; - return mItemId == other.mItemId - && mGroupId == other.mGroupId - && TextUtils.equals(mTitle, other.mTitle) - // Many Drawables (icons) do not implement equals(). Using equals() here instead - // of reference comparisons in case a Drawable subclass implements equals(). - && Objects.equals(mIcon, other.mIcon); - } - - /** - * Returns true if the two menu item collections are the same based on MenuItemRepr. - */ - public static boolean reprEquals( - Collection<MenuItem> menuItems1, Collection<MenuItem> menuItems2) { - if (menuItems1.size() != menuItems2.size()) { - return false; - } - - final Iterator<MenuItem> menuItems2Iter = menuItems2.iterator(); - for (MenuItem menuItem1 : menuItems1) { - final MenuItem menuItem2 = menuItems2Iter.next(); - if (!MenuItemRepr.of(menuItem1).equals( - MenuItemRepr.of(menuItem2))) { - return false; - } - } - return true; - } - } -} diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 4cf17b78f489..199854818989 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -523,13 +523,14 @@ android_media_AudioSystem_dyn_policy_callback(int event, String8 regId, int val) } jclass clazz = env->FindClass(kClassPathName); - const char* zechars = regId.string(); - jstring zestring = env->NewStringUTF(zechars); + const char *regIdString = regId.string(); + jstring regIdJString = env->NewStringUTF(regIdString); env->CallStaticVoidMethod(clazz, gAudioPolicyEventHandlerMethods.postDynPolicyEventFromNative, - event, zestring, val); + event, regIdJString, val); - env->ReleaseStringUTFChars(zestring, zechars); + const char *regIdJChars = env->GetStringUTFChars(regIdJString, NULL); + env->ReleaseStringUTFChars(regIdJString, regIdJChars); env->DeleteLocalRef(clazz); } diff --git a/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp b/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp index bba1760bc45c..d4f6e1868695 100644 --- a/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp +++ b/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp @@ -44,6 +44,8 @@ static struct fabricated_overlay_internal_entry_offsets_t { jfieldID stringData; jfieldID binaryData; jfieldID configuration; + jfieldID binaryDataOffset; + jfieldID binaryDataSize; } gFabricatedOverlayInternalEntryOffsets; static struct parcel_file_descriptor_offsets_t { @@ -281,10 +283,17 @@ static void CreateFrroFile(JNIEnv* env, jclass /*clazz*/, jstring jsFrroFilePath auto binary_data = getNullableFileDescriptor(env, entry, gFabricatedOverlayInternalEntryOffsets.binaryData); + + const auto data_offset = + env->GetLongField(entry, gFabricatedOverlayInternalEntryOffsets.binaryDataOffset); + const auto data_size = + env->GetLongField(entry, gFabricatedOverlayInternalEntryOffsets.binaryDataSize); entries_params.push_back( FabricatedOverlayEntryParameters{resourceName.c_str(), (DataType)dataType, (DataValue)data, string_data.value_or(std::string()), binary_data, + static_cast<off64_t>(data_offset), + static_cast<size_t>(data_size), configuration.value_or(std::string())}); ALOGV("resourceName = %s, dataType = 0x%08x, data = 0x%08x, dataString = %s," " binaryData = %d, configuration = %s", @@ -440,6 +449,12 @@ int register_com_android_internal_content_om_OverlayManagerImpl(JNIEnv* env) { gFabricatedOverlayInternalEntryOffsets.configuration = GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject, "configuration", "Ljava/lang/String;"); + gFabricatedOverlayInternalEntryOffsets.binaryDataOffset = + GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject, + "binaryDataOffset", "J"); + gFabricatedOverlayInternalEntryOffsets.binaryDataSize = + GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject, + "binaryDataSize", "J"); jclass parcelFileDescriptorClass = android::FindClassOrDie(env, "android/os/ParcelFileDescriptor"); diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 86cf80e8847c..67710f64e1e4 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -821,7 +821,6 @@ <protected-broadcast android:name="android.intent.action.PROFILE_REMOVED" /> <protected-broadcast android:name="com.android.internal.telephony.cat.SMS_SENT_ACTION" /> <protected-broadcast android:name="com.android.internal.telephony.cat.SMS_DELIVERY_ACTION" /> - <protected-broadcast android:name="com.android.internal.telephony.data.ACTION_RETRY" /> <protected-broadcast android:name="android.companion.virtual.action.VIRTUAL_DEVICE_REMOVED" /> <protected-broadcast android:name="com.android.internal.intent.action.FLASH_NOTIFICATION_START_PREVIEW" /> <protected-broadcast android:name="com.android.internal.intent.action.FLASH_NOTIFICATION_STOP_PREVIEW" /> diff --git a/core/res/res/values/config_device_idle.xml b/core/res/res/values/config_device_idle.xml index 764dbbe5dc93..98a5ff9c4a79 100644 --- a/core/res/res/values/config_device_idle.xml +++ b/core/res/res/values/config_device_idle.xml @@ -120,7 +120,7 @@ <!-- Default for DeviceIdleController.Constants.USE_WINDOW_ALARMS --> <bool name="device_idle_use_window_alarms">true</bool> - <!-- Default for DeviceIdleController.Constants.USE_BODY_SENSOR --> - <bool name="device_idle_use_body_sensor">false</bool> + <!-- Default for DeviceIdleController.Constants.USE_MODE_MANAGER --> + <bool name="device_idle_use_mode_manager">false</bool> </resources> diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index 18abe7073757..bda194add759 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -160,6 +160,7 @@ 3 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_SMS} 4 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VIDEO} 5 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_EMERGENCY} + 6 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_MMS} Example of a config string: "10011:2,3" The PLMNs not configured in this array will be ignored and will not be used for satellite diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index a11eaa91b2a6..d828f33ca514 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1786,10 +1786,6 @@ <string name="biometric_dialog_default_title">Verify it\u2019s you</string> <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with a biometric (e.g. fingerprint or face). [CHAR LIMIT=70] --> <string name="biometric_dialog_default_subtitle">Use your biometric to continue</string> - <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with fingerprint. [CHAR LIMIT=70] --> - <string name="biometric_dialog_fingerprint_subtitle">Use your fingerprint to continue</string> - <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with face. [CHAR LIMIT=70] --> - <string name="biometric_dialog_face_subtitle">Use your face to continue</string> <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with a biometric (e.g. fingerprint or face) or their screen lock credential (i.e. PIN, pattern, or password). [CHAR LIMIT=90] --> <string name="biometric_or_screen_lock_dialog_default_subtitle">Use your biometric or screen lock to continue</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 5806e93d4a93..8a395f88cb63 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2585,8 +2585,6 @@ <java-symbol type="string" name="biometric_or_screen_lock_app_setting_name" /> <java-symbol type="string" name="biometric_dialog_default_title" /> <java-symbol type="string" name="biometric_dialog_default_subtitle" /> - <java-symbol type="string" name="biometric_dialog_face_subtitle" /> - <java-symbol type="string" name="biometric_dialog_fingerprint_subtitle" /> <java-symbol type="string" name="biometric_or_screen_lock_dialog_default_subtitle" /> <java-symbol type="string" name="biometric_error_hw_unavailable" /> <java-symbol type="string" name="biometric_error_user_canceled" /> @@ -4522,7 +4520,7 @@ <java-symbol type="integer" name="device_idle_notification_allowlist_duration_ms" /> <java-symbol type="bool" name="device_idle_wait_for_unlock" /> <java-symbol type="bool" name="device_idle_use_window_alarms" /> - <java-symbol type="bool" name="device_idle_use_body_sensor" /> + <java-symbol type="bool" name="device_idle_use_mode_manager" /> <!-- Binder heavy hitter watcher configs --> <java-symbol type="bool" name="config_defaultBinderHeavyHitterWatcherEnabled" /> diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index c7aaeb0339bd..c14da299c6ef 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -91,7 +91,6 @@ android_test { java_resources: [":ApkVerityTestCertDer"], data: [ - ":BstatsTestApp", ":BinderDeathRecipientHelperApp1", ":BinderDeathRecipientHelperApp2", ":com.android.cts.helpers.aosp", diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 129de649a4b5..31755efb88ed 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -231,6 +231,28 @@ </intent-filter> </activity> + <activity android:name="android.widget.HorizontalScrollViewActivity" + android:label="HorizontalScrollViewActivity" + android:screenOrientation="portrait" + android:exported="true" + android:theme="@android:style/Theme.Material.Light"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" /> + </intent-filter> + </activity> + + <activity android:name="android.widget.ScrollViewActivity" + android:label="ScrollViewActivity" + android:screenOrientation="portrait" + android:exported="true" + android:theme="@android:style/Theme.Material.Light"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" /> + </intent-filter> + </activity> + <activity android:name="android.widget.DatePickerActivity" android:label="DatePickerActivity" android:screenOrientation="portrait" diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml index 3e4c47b36ed9..05b309b2cd52 100644 --- a/core/tests/coretests/AndroidTest.xml +++ b/core/tests/coretests/AndroidTest.xml @@ -20,7 +20,6 @@ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true" /> <option name="test-file-name" value="FrameworksCoreTests.apk" /> - <option name="test-file-name" value="BstatsTestApp.apk" /> <option name="test-file-name" value="BinderDeathRecipientHelperApp1.apk" /> <option name="test-file-name" value="BinderDeathRecipientHelperApp2.apk" /> </target_preparer> diff --git a/core/tests/coretests/BstatsTestApp/OWNERS b/core/tests/coretests/BstatsTestApp/OWNERS deleted file mode 100644 index 4068e2bc03b7..000000000000 --- a/core/tests/coretests/BstatsTestApp/OWNERS +++ /dev/null @@ -1 +0,0 @@ -include /BATTERY_STATS_OWNERS diff --git a/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml b/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml new file mode 100644 index 000000000000..866e1a95c3f5 --- /dev/null +++ b/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml @@ -0,0 +1,118 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2015 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 + --> + +<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:id="@+id/horizontal_scroll_view"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <View + android:background="#F00" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#880" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#0F0" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#088" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#00F" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#808" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#F00" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#880" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#0F0" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#088" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#00F" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#808" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#F00" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#880" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#0F0" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#088" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#00F" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#808" + android:layout_width="100dp" + android:layout_height="100dp" /> + + </LinearLayout> +</HorizontalScrollView> diff --git a/core/tests/coretests/res/layout/activity_scroll_view.xml b/core/tests/coretests/res/layout/activity_scroll_view.xml new file mode 100644 index 000000000000..61fabf8ee437 --- /dev/null +++ b/core/tests/coretests/res/layout/activity_scroll_view.xml @@ -0,0 +1,118 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:id="@+id/scroll_view"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <View + android:background="#F00" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#880" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#0F0" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#088" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#00F" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#808" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#F00" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#880" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#0F0" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#088" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#00F" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#808" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#F00" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#880" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#0F0" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#088" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#00F" + android:layout_width="100dp" + android:layout_height="100dp" /> + + <View + android:background="#808" + android:layout_width="100dp" + android:layout_height="100dp" /> + + </LinearLayout> +</ScrollView> diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java index 2c03fdc2ef0d..b0826ab77035 100644 --- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java +++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java @@ -69,6 +69,24 @@ public class PerformanceHintManagerTest { } @Test + public void testCreateHintSession_noTids() { + assertThrows(NullPointerException.class, () -> { + mPerformanceHintManager.createHintSession( + null, DEFAULT_TARGET_NS); + }); + assertThrows(IllegalArgumentException.class, () -> { + mPerformanceHintManager.createHintSession( + new int[]{}, DEFAULT_TARGET_NS); + }); + } + + @Test + public void testCreateHintSession_invalidTids() { + assertNull(mPerformanceHintManager.createHintSession( + new int[]{-1}, DEFAULT_TARGET_NS)); + } + + @Test public void testGetPreferredUpdateRateNanos() { if (createSession() != null) { assertTrue(mPerformanceHintManager.getPreferredUpdateRateNanos() > 0); diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewActivity.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewActivity.java new file mode 100644 index 000000000000..21013545008c --- /dev/null +++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewActivity.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 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.widget; + +import android.app.Activity; +import android.os.Bundle; + +import com.android.frameworks.coretests.R; + +/** + * An activity for testing the TextView widget. + */ +public class HorizontalScrollViewActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_horizontal_scroll_view); + } +} diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java new file mode 100644 index 000000000000..86f26e59e370 --- /dev/null +++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2023 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.widget; + +import static org.junit.Assert.assertEquals; + +import android.platform.test.annotations.Presubmit; +import android.util.PollingCheck; + +import androidx.test.filters.MediumTest; +import androidx.test.rule.ActivityTestRule; +import androidx.test.runner.AndroidJUnit4; + +import com.android.frameworks.coretests.R; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@MediumTest +@Presubmit +public class HorizontalScrollViewFunctionalTest { + private HorizontalScrollViewActivity mActivity; + private HorizontalScrollView mHorizontalScrollView; + @Rule + public ActivityTestRule<HorizontalScrollViewActivity> mActivityRule = new ActivityTestRule<>( + HorizontalScrollViewActivity.class); + + @Before + public void setUp() throws Exception { + mActivity = mActivityRule.getActivity(); + mHorizontalScrollView = mActivity.findViewById(R.id.horizontal_scroll_view); + } + + @Test + public void testScrollAfterFlingTop() { + mHorizontalScrollView.scrollTo(100, 0); + mHorizontalScrollView.fling(-10000); + PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() > 0); + PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() == 0f); + assertEquals(0, mHorizontalScrollView.getScrollX()); + } + + @Test + public void testScrollAfterFlingBottom() { + int childWidth = mHorizontalScrollView.getChildAt(0).getWidth(); + int maxScroll = childWidth - mHorizontalScrollView.getWidth(); + mHorizontalScrollView.scrollTo(maxScroll - 100, 0); + mHorizontalScrollView.fling(10000); + PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() > 0); + PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() == 0f); + assertEquals(maxScroll, mHorizontalScrollView.getScrollX()); + } +} + diff --git a/core/java/android/view/selectiontoolbar/ShowInfo.aidl b/core/tests/coretests/src/android/widget/ScrollViewActivity.java index dce9c15dce3e..899d63163aa4 100644 --- a/core/java/android/view/selectiontoolbar/ShowInfo.aidl +++ b/core/tests/coretests/src/android/widget/ScrollViewActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2023 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. @@ -14,9 +14,21 @@ * limitations under the License. */ -package android.view.selectiontoolbar; +package android.widget; + +import android.app.Activity; +import android.os.Bundle; + +import com.android.frameworks.coretests.R; /** - * @hide + * An activity for testing the TextView widget. */ -parcelable ShowInfo; +public class ScrollViewActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_scroll_view); + } +} diff --git a/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java new file mode 100644 index 000000000000..a49bb6af13d2 --- /dev/null +++ b/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2023 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.widget; + +import static org.junit.Assert.assertEquals; + +import android.platform.test.annotations.Presubmit; +import android.util.PollingCheck; + +import androidx.test.filters.MediumTest; +import androidx.test.rule.ActivityTestRule; +import androidx.test.runner.AndroidJUnit4; + +import com.android.frameworks.coretests.R; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@MediumTest +@Presubmit +public class ScrollViewFunctionalTest { + private ScrollViewActivity mActivity; + private ScrollView mScrollView; + @Rule + public ActivityTestRule<ScrollViewActivity> mActivityRule = new ActivityTestRule<>( + ScrollViewActivity.class); + + @Before + public void setUp() throws Exception { + mActivity = mActivityRule.getActivity(); + mScrollView = mActivity.findViewById(R.id.scroll_view); + } + + @Test + public void testScrollAfterFlingTop() { + mScrollView.scrollTo(0, 100); + mScrollView.fling(-10000); + PollingCheck.waitFor(() -> mScrollView.mEdgeGlowTop.getDistance() > 0); + PollingCheck.waitFor(() -> mScrollView.mEdgeGlowTop.getDistance() == 0f); + assertEquals(0, mScrollView.getScrollY()); + } + + @Test + public void testScrollAfterFlingBottom() { + int childHeight = mScrollView.getChildAt(0).getHeight(); + int maxScroll = childHeight - mScrollView.getHeight(); + mScrollView.scrollTo(0, maxScroll - 100); + mScrollView.fling(10000); + PollingCheck.waitFor(() -> mScrollView.mEdgeGlowBottom.getDistance() > 0); + PollingCheck.waitFor(() -> mScrollView.mEdgeGlowBottom.getDistance() == 0f); + assertEquals(maxScroll, mScrollView.getScrollY()); + } +} + diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java index a6e74d0d6b94..68c0693fb23a 100644 --- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java +++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java @@ -23,12 +23,12 @@ import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.atMost; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -254,18 +254,15 @@ public class WindowOnBackInvokedDispatcherTest { callbackInfo1.getCallback().onBackStarted(mBackEvent); waitForIdle(); - verify(mCallback1).onBackStarted(any(BackEvent.class)); - verifyZeroInteractions(mCallback2); + verify(mCallback1, times(1)).onBackStarted(any(BackEvent.class)); + verify(mCallback2, never()).onBackStarted(any(BackEvent.class)); + clearInvocations(mCallback1); callbackInfo2.getCallback().onBackStarted(mBackEvent); waitForIdle(); - verify(mCallback2).onBackStarted(any(BackEvent.class)); - - // Calls sequence: BackProgressAnimator.onBackStarted() -> BackProgressAnimator.reset() -> - // Spring.animateToFinalPosition(0). This causes a progress event to be fired. - verify(mCallback1, atMost(1)).onBackProgressed(any(BackEvent.class)); - verifyNoMoreInteractions(mCallback1); + verify(mCallback1, never()).onBackStarted(any(BackEvent.class)); + verify(mCallback2, times(1)).onBackStarted(any(BackEvent.class)); } @Test diff --git a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java index 7bd6f05d3775..a21c91782214 100644 --- a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java +++ b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java @@ -58,8 +58,6 @@ public class WindowTokenClientControllerTest { @Mock private WindowTokenClient mWindowTokenClient; @Mock - private IBinder mClientToken; - @Mock private IBinder mWindowToken; // Can't mock final class. private final Configuration mConfiguration = new Configuration(); @@ -70,7 +68,6 @@ public class WindowTokenClientControllerTest { @Before public void setup() { MockitoAnnotations.initMocks(this); - doReturn(mClientToken).when(mWindowTokenClient).asBinder(); mController = spy(WindowTokenClientController.createInstanceForTesting()); doReturn(mWindowManagerService).when(mController).getWindowManagerService(); mWindowContextInfo = new WindowContextInfo(mConfiguration, DEFAULT_DISPLAY); @@ -205,7 +202,7 @@ public class WindowTokenClientControllerTest { .attachWindowContextToWindowToken(any(), any(), any()); // No invoke if not attached. - mController.onWindowContextInfoChanged(mClientToken, mWindowContextInfo); + mController.onWindowContextInfoChanged(mWindowTokenClient, mWindowContextInfo); verify(mWindowTokenClient, never()).onConfigurationChanged(any(), anyInt()); @@ -216,7 +213,7 @@ public class WindowTokenClientControllerTest { // Invoke onConfigurationChanged when onWindowContextInfoChanged mController.onWindowContextInfoChanged( - mClientToken, new WindowContextInfo(mConfiguration, DEFAULT_DISPLAY + 1)); + mWindowTokenClient, new WindowContextInfo(mConfiguration, DEFAULT_DISPLAY + 1)); verify(mWindowTokenClient).onConfigurationChanged(mConfiguration, DEFAULT_DISPLAY + 1); } @@ -227,7 +224,7 @@ public class WindowTokenClientControllerTest { .attachWindowContextToWindowToken(any(), any(), any()); // No invoke if not attached. - mController.onWindowContextWindowRemoved(mClientToken); + mController.onWindowContextWindowRemoved(mWindowTokenClient); verify(mWindowTokenClient, never()).onWindowTokenRemoved(); @@ -237,7 +234,7 @@ public class WindowTokenClientControllerTest { verify(mWindowTokenClient, never()).onWindowTokenRemoved(); // Invoke onWindowTokenRemoved when onWindowContextWindowRemoved - mController.onWindowContextWindowRemoved(mClientToken); + mController.onWindowContextWindowRemoved(mWindowTokenClient); verify(mWindowTokenClient).onWindowTokenRemoved(); } diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java index 2de1230cf706..cd5ec851e9eb 100644 --- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java +++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java @@ -27,9 +27,7 @@ import static com.android.internal.accessibility.AccessibilityShortcutController import static com.android.internal.accessibility.AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME; import static com.android.internal.accessibility.AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; +import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import static org.mockito.AdditionalMatchers.aryEq; @@ -68,7 +66,6 @@ import android.provider.Settings; import android.speech.tts.TextToSpeech; import android.speech.tts.Voice; import android.test.mock.MockContentResolver; -import android.text.TextUtils; import android.view.Window; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; @@ -76,7 +73,7 @@ import android.view.accessibility.IAccessibilityManager; import android.widget.Toast; import androidx.test.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.internal.R; import com.android.internal.accessibility.AccessibilityShortcutController.FrameworkObjectProvider; @@ -232,7 +229,7 @@ public class AccessibilityShortcutControllerTest { throws Exception { configureNoShortcutService(); configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN); - assertFalse(getController().isAccessibilityShortcutAvailable(false)); + assertThat(getController().isAccessibilityShortcutAvailable(false)).isFalse(); } @Test @@ -240,7 +237,7 @@ public class AccessibilityShortcutControllerTest { throws Exception { configureValidShortcutService(); configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN); - assertTrue(getController().isAccessibilityShortcutAvailable(false)); + assertThat(getController().isAccessibilityShortcutAvailable(false)).isTrue(); } @Test @@ -248,7 +245,7 @@ public class AccessibilityShortcutControllerTest { throws Exception { configureValidShortcutService(); configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN); - assertFalse(getController().isAccessibilityShortcutAvailable(true)); + assertThat(getController().isAccessibilityShortcutAvailable(true)).isFalse(); } @Test @@ -256,7 +253,7 @@ public class AccessibilityShortcutControllerTest { throws Exception { configureValidShortcutService(); configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN); - assertTrue(getController().isAccessibilityShortcutAvailable(true)); + assertThat(getController().isAccessibilityShortcutAvailable(true)).isTrue(); } @Test @@ -267,10 +264,14 @@ public class AccessibilityShortcutControllerTest { configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN); Settings.Secure.putString( mContentResolver, ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, null); - Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0); - assertFalse(getController().isAccessibilityShortcutAvailable(true)); - Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1); - assertTrue(getController().isAccessibilityShortcutAvailable(true)); + Settings.Secure.putInt( + mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, + AccessibilityShortcutController.DialogStatus.NOT_SHOWN); + assertThat(getController().isAccessibilityShortcutAvailable(true)).isFalse(); + Settings.Secure.putInt( + mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, + AccessibilityShortcutController.DialogStatus.SHOWN); + assertThat(getController().isAccessibilityShortcutAvailable(true)).isTrue(); } @Test @@ -281,7 +282,9 @@ public class AccessibilityShortcutControllerTest { AccessibilityShortcutController accessibilityShortcutController = getController(); configureNoShortcutService(); accessibilityShortcutController.onSettingsChanged(); - assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable(false)); + assertThat( + accessibilityShortcutController.isAccessibilityShortcutAvailable(false) + ).isFalse(); } @Test @@ -292,7 +295,9 @@ public class AccessibilityShortcutControllerTest { AccessibilityShortcutController accessibilityShortcutController = getController(); configureValidShortcutService(); accessibilityShortcutController.onSettingsChanged(); - assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable(false)); + assertThat( + accessibilityShortcutController.isAccessibilityShortcutAvailable(false) + ).isTrue(); } @Test @@ -302,7 +307,9 @@ public class AccessibilityShortcutControllerTest { AccessibilityShortcutController accessibilityShortcutController = getController(); configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN); accessibilityShortcutController.onSettingsChanged(); - assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable(false)); + assertThat( + accessibilityShortcutController.isAccessibilityShortcutAvailable(false) + ).isTrue(); } @Test @@ -313,7 +320,9 @@ public class AccessibilityShortcutControllerTest { AccessibilityShortcutController accessibilityShortcutController = getController(); configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN); accessibilityShortcutController.onSettingsChanged(); - assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable(true)); + assertThat( + accessibilityShortcutController.isAccessibilityShortcutAvailable(true) + ).isFalse(); } @Test @@ -324,7 +333,9 @@ public class AccessibilityShortcutControllerTest { AccessibilityShortcutController accessibilityShortcutController = getController(); configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN); accessibilityShortcutController.onSettingsChanged(); - assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable(true)); + assertThat( + accessibilityShortcutController.isAccessibilityShortcutAvailable(true) + ).isTrue(); } @Test @@ -341,11 +352,15 @@ public class AccessibilityShortcutControllerTest { configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN); configureValidShortcutService(); AccessibilityShortcutController accessibilityShortcutController = getController(); - Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0); + Settings.Secure.putInt( + mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, + AccessibilityShortcutController.DialogStatus.NOT_SHOWN); accessibilityShortcutController.performAccessibilityShortcut(); - assertEquals(1, Settings.Secure.getInt( - mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0)); + assertThat(Settings.Secure.getInt( + mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, + AccessibilityShortcutController.DialogStatus.NOT_SHOWN)).isEqualTo( + AccessibilityShortcutController.DialogStatus.SHOWN); verify(mResources).getString( R.string.accessibility_shortcut_single_service_warning_title, PACKAGE_NAME_STRING); verify(mAlertDialog).show(); @@ -357,11 +372,12 @@ public class AccessibilityShortcutControllerTest { @Test public void testOnAccessibilityShortcut_withDialogShowing_callsServer() - throws Exception { + throws Exception { configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN); configureValidShortcutService(); AccessibilityShortcutController accessibilityShortcutController = getController(); - Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0); + Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, + AccessibilityShortcutController.DialogStatus.NOT_SHOWN); accessibilityShortcutController.performAccessibilityShortcut(); accessibilityShortcutController.performAccessibilityShortcut(); verify(mToast).show(); @@ -374,11 +390,12 @@ public class AccessibilityShortcutControllerTest { @Test public void testOnAccessibilityShortcut_ifCanceledFirstTime_showsWarningDialog() - throws Exception { + throws Exception { configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN); configureValidShortcutService(); AccessibilityShortcutController accessibilityShortcutController = getController(); - Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0); + Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, + AccessibilityShortcutController.DialogStatus.NOT_SHOWN); accessibilityShortcutController.performAccessibilityShortcut(); ArgumentCaptor<AlertDialog.OnCancelListener> cancelListenerCaptor = ArgumentCaptor.forClass(AlertDialog.OnCancelListener.class); @@ -394,49 +411,98 @@ public class AccessibilityShortcutControllerTest { public void testClickingDisableButtonInDialog_shouldClearShortcutId() throws Exception { configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN); configureValidShortcutService(); - Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0); + Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, + AccessibilityShortcutController.DialogStatus.NOT_SHOWN); getController().performAccessibilityShortcut(); ArgumentCaptor<DialogInterface.OnClickListener> captor = ArgumentCaptor.forClass(DialogInterface.OnClickListener.class); verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.accessibility_shortcut_off), captor.capture()); - // Call the button callback, if one exists - if (captor.getValue() != null) { - captor.getValue().onClick(null, 0); - } - assertTrue(TextUtils.isEmpty( - Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE))); - assertEquals(0, Settings.Secure.getInt( - mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)); + captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE); + + assertThat( + Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE) + ).isEmpty(); + assertThat(Settings.Secure.getInt( + mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo( + AccessibilityShortcutController.DialogStatus.NOT_SHOWN); } @Test public void testClickingTurnOnButtonInDialog_shouldLeaveShortcutReady() throws Exception { configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN); configureValidShortcutService(); - Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0); + Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, + AccessibilityShortcutController.DialogStatus.NOT_SHOWN); getController().performAccessibilityShortcut(); ArgumentCaptor<DialogInterface.OnClickListener> captor = - ArgumentCaptor.forClass(DialogInterface.OnClickListener.class); + ArgumentCaptor.forClass(DialogInterface.OnClickListener.class); verify(mAlertDialogBuilder).setNegativeButton(eq(R.string.accessibility_shortcut_on), captor.capture()); - // Call the button callback, if one exists - if (captor.getValue() != null) { - captor.getValue().onClick(null, 0); - } - assertEquals(SERVICE_NAME_STRING, - Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)); - assertEquals(1, Settings.Secure.getInt( - mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)); + captor.getValue().onClick(null, DialogInterface.BUTTON_NEGATIVE); + + assertThat( + Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE) + ).isEqualTo(SERVICE_NAME_STRING); + assertThat(Settings.Secure.getInt( + mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo( + AccessibilityShortcutController.DialogStatus.SHOWN); + } + + @Test + public void testTurnOnDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOn() + throws Exception { + configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN); + configureDefaultAccessibilityService(); + Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, + AccessibilityShortcutController.DialogStatus.NOT_SHOWN); + getController().performAccessibilityShortcut(); + + ArgumentCaptor<DialogInterface.OnClickListener> captor = + ArgumentCaptor.forClass(DialogInterface.OnClickListener.class); + verify(mAlertDialogBuilder).setNegativeButton(eq(R.string.accessibility_shortcut_on), + captor.capture()); + captor.getValue().onClick(null, DialogInterface.BUTTON_NEGATIVE); + + assertThat( + Settings.Secure.getString(mContentResolver, + ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEqualTo(SERVICE_NAME_STRING); + assertThat(Settings.Secure.getInt( + mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo( + AccessibilityShortcutController.DialogStatus.SHOWN); + } + + @Test + public void testTurnOffDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOff() + throws Exception { + configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN); + configureDefaultAccessibilityService(); + Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, + AccessibilityShortcutController.DialogStatus.NOT_SHOWN); + getController().performAccessibilityShortcut(); + + ArgumentCaptor<DialogInterface.OnClickListener> captor = + ArgumentCaptor.forClass(DialogInterface.OnClickListener.class); + verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.accessibility_shortcut_off), + captor.capture()); + captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE); + + assertThat( + Settings.Secure.getString(mContentResolver, + ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEmpty(); + assertThat(Settings.Secure.getInt( + mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo( + AccessibilityShortcutController.DialogStatus.NOT_SHOWN); } @Test public void testOnAccessibilityShortcut_afterDialogShown_shouldCallServer() throws Exception { configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN); configureValidShortcutService(); - Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1); + Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, + AccessibilityShortcutController.DialogStatus.SHOWN); getController().performAccessibilityShortcut(); verifyZeroInteractions(mAlertDialogBuilder, mAlertDialog); @@ -464,10 +530,10 @@ public class AccessibilityShortcutControllerTest { frameworkFeatureMap = AccessibilityShortcutController.getFrameworkShortcutFeaturesMap(); - assertTrue(frameworkFeatureMap.containsKey(COLOR_INVERSION_COMPONENT_NAME)); - assertTrue(frameworkFeatureMap.containsKey(DALTONIZER_COMPONENT_NAME)); - assertTrue(frameworkFeatureMap.containsKey(REDUCE_BRIGHT_COLORS_COMPONENT_NAME)); - assertTrue(frameworkFeatureMap.containsKey(ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME)); + assertThat(frameworkFeatureMap).containsKey(COLOR_INVERSION_COMPONENT_NAME); + assertThat(frameworkFeatureMap).containsKey(DALTONIZER_COMPONENT_NAME); + assertThat(frameworkFeatureMap).containsKey(REDUCE_BRIGHT_COLORS_COMPONENT_NAME); + assertThat(frameworkFeatureMap).containsKey(ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME); } @Test @@ -478,7 +544,7 @@ public class AccessibilityShortcutControllerTest { frameworkFeatureMap = AccessibilityShortcutController.getFrameworkShortcutFeaturesMap(); - assertTrue(frameworkFeatureMap.containsKey(ONE_HANDED_COMPONENT_NAME)); + assertThat(frameworkFeatureMap).containsKey(ONE_HANDED_COMPONENT_NAME); } @Test @@ -489,7 +555,7 @@ public class AccessibilityShortcutControllerTest { frameworkFeatureMap = AccessibilityShortcutController.getFrameworkShortcutFeaturesMap(); - assertFalse(frameworkFeatureMap.containsKey(ONE_HANDED_COMPONENT_NAME)); + assertThat(frameworkFeatureMap).doesNotContainKey(ONE_HANDED_COMPONENT_NAME); } @Test @@ -498,7 +564,8 @@ public class AccessibilityShortcutControllerTest { configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN); configureValidShortcutService(); when(mServiceInfo.loadSummary(any())).thenReturn(null); - Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1); + Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, + AccessibilityShortcutController.DialogStatus.SHOWN); getController().performAccessibilityShortcut(); verify(mAccessibilityManagerService).performAccessibilityShortcut(null); } @@ -508,7 +575,8 @@ public class AccessibilityShortcutControllerTest { throws Exception { configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN); configureFirstFrameworkFeature(); - Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1); + Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, + AccessibilityShortcutController.DialogStatus.SHOWN); getController().performAccessibilityShortcut(); verifyZeroInteractions(mToast); @@ -523,7 +591,8 @@ public class AccessibilityShortcutControllerTest { configureApplicationTargetSdkVersion(Build.VERSION_CODES.R); configureRequestAccessibilityButton(); configureEnabledService(); - Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1); + Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, + AccessibilityShortcutController.DialogStatus.SHOWN); getController().performAccessibilityShortcut(); verifyZeroInteractions(mToast); @@ -538,7 +607,8 @@ public class AccessibilityShortcutControllerTest { configureTtsSpokenPromptEnabled(); configureHandlerCallbackInvocation(); AccessibilityShortcutController accessibilityShortcutController = getController(); - Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0); + Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, + AccessibilityShortcutController.DialogStatus.NOT_SHOWN); accessibilityShortcutController.performAccessibilityShortcut(); verify(mAlertDialog).show(); @@ -563,7 +633,8 @@ public class AccessibilityShortcutControllerTest { configureTtsSpokenPromptEnabled(); configureHandlerCallbackInvocation(); AccessibilityShortcutController accessibilityShortcutController = getController(); - Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0); + Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, + AccessibilityShortcutController.DialogStatus.NOT_SHOWN); accessibilityShortcutController.performAccessibilityShortcut(); verify(mAlertDialog).show(); @@ -583,7 +654,8 @@ public class AccessibilityShortcutControllerTest { configureTtsSpokenPromptEnabled(); configureHandlerCallbackInvocation(); AccessibilityShortcutController accessibilityShortcutController = getController(); - Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0); + Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, + AccessibilityShortcutController.DialogStatus.NOT_SHOWN); Set<String> features = new HashSet<>(); features.add(TextToSpeech.Engine.KEY_FEATURE_NOT_INSTALLED); doReturn(features, Collections.emptySet()).when(mVoice).getFeatures(); @@ -682,4 +754,13 @@ public class AccessibilityShortcutControllerTest { accessibilityShortcutController.mFrameworkObjectProvider = mFrameworkObjectProvider; return accessibilityShortcutController; } + + private void configureDefaultAccessibilityService() throws Exception { + when(mAccessibilityManagerService + .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY)) + .thenReturn(Collections.singletonList(SERVICE_NAME_STRING)); + + when(mResources.getString(R.string.config_defaultAccessibilityService)).thenReturn( + SERVICE_NAME_STRING); + } } diff --git a/core/tests/coretests/src/com/android/internal/content/OWNERS b/core/tests/coretests/src/com/android/internal/content/OWNERS index dd9ede53239c..4bb83438b193 100644 --- a/core/tests/coretests/src/com/android/internal/content/OWNERS +++ b/core/tests/coretests/src/com/android/internal/content/OWNERS @@ -1,4 +1,2 @@ -per-file PackageMonitorTest.java = file:/core/java/android/content/pm/OWNERS - per-file Overlay* = file:/core/java/android/content/res/OWNERS diff --git a/core/tests/packagemonitortests/Android.bp b/core/tests/packagemonitortests/Android.bp new file mode 100644 index 000000000000..453b476edbc0 --- /dev/null +++ b/core/tests/packagemonitortests/Android.bp @@ -0,0 +1,41 @@ +// Copyright (C) 2023 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 { + // 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: "FrameworksCorePackageMonitorTests", + srcs: ["src/**/*.java"], + static_libs: [ + "androidx.test.runner", + "compatibility-device-util-axt", + "frameworks-base-testutils", + "mockito-target-minus-junit4", + "truth-prebuilt", + ], + libs: ["android.test.runner"], + platform_apis: true, + certificate: "platform", + test_suites: ["device-tests"], + data: [ + ":TestVisibilityApp", + ], +} diff --git a/core/tests/packagemonitortests/AndroidManifest.xml b/core/tests/packagemonitortests/AndroidManifest.xml new file mode 100644 index 000000000000..500a3e192077 --- /dev/null +++ b/core/tests/packagemonitortests/AndroidManifest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.packagemonitor"> + + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.frameworks.packagemonitor" + android:label="Frameworks PackageMonitor Core Tests" /> +</manifest> diff --git a/core/tests/packagemonitortests/AndroidTest.xml b/core/tests/packagemonitortests/AndroidTest.xml new file mode 100644 index 000000000000..898dc1877b4b --- /dev/null +++ b/core/tests/packagemonitortests/AndroidTest.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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 Core Tests."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-instrumentation" /> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="FrameworksCorePackageMonitorTests.apk" /> + </target_preparer> + + <!-- Create place to store apks --> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="mkdir -p /data/local/tmp/contenttests" /> + <option name="teardown-command" value="rm -rf /data/local/tmp/contenttests"/> + </target_preparer> + <!-- Load additional APKs onto device --> + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="push" value="TestVisibilityApp.apk->/data/local/tmp/contenttests/TestVisibilityApp.apk" /> + <option name="cleanup" value="true" /> + </target_preparer> + + <option name="test-tag" value="FrameworksCorePackageMonitorTests" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.frameworks.packagemonitor" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration> diff --git a/core/tests/packagemonitortests/OWNERS b/core/tests/packagemonitortests/OWNERS new file mode 100644 index 000000000000..d825dfd7cf00 --- /dev/null +++ b/core/tests/packagemonitortests/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/pm/OWNERS diff --git a/core/tests/coretests/src/com/android/internal/content/PackageMonitorTest.java b/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java index 7ccbf1d25674..e082c25fa499 100644 --- a/core/tests/coretests/src/com/android/internal/content/PackageMonitorTest.java +++ b/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java @@ -44,10 +44,10 @@ import org.mockito.MockitoAnnotations; */ @RunWith(AndroidJUnit4.class) public class PackageMonitorTest { - private static final String FAKE_PACKAGE_NAME = "com.android.internal.content.fakeapp"; private static final int FAKE_PACKAGE_UID = 123; private static final int FAKE_USER_ID = 0; + private static final int WAIT_CALLBACK_CALLED_IN_MS = 300; @Mock Context mMockContext; diff --git a/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorVisibilityTest.java b/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorVisibilityTest.java new file mode 100644 index 000000000000..a310edceb3b3 --- /dev/null +++ b/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorVisibilityTest.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2023 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.content; + +import static com.android.compatibility.common.util.ShellUtils.runShellCommand; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * A test to verify PackageMonitor implementation respects the app visibility. + */ +@RunWith(AndroidJUnit4.class) +public class PackageMonitorVisibilityTest { + private static final String TEST_DATA_PATH = "/data/local/tmp/contenttests/"; + private static final String TEAT_APK_PATH = + TEST_DATA_PATH + "TestVisibilityApp.apk"; + private static final String TEAT_APK_PACKAGE_NAME = "com.example.android.testvisibilityapp"; + private static final int WAIT_CALLBACK_CALLED_IN_SECONDS = 1; + @Test + public void testPackageMonitorPackageVisible() throws Exception { + TestVisibilityPackageMonitor testPackageMonitor = new TestVisibilityPackageMonitor(); + + try { + Context context = InstrumentationRegistry.getInstrumentation().getContext(); + testPackageMonitor.register(context, UserHandle.ALL, + new Handler(Looper.getMainLooper())); + + installTestPackage(true /* forceQueryable */); + boolean result = testPackageMonitor.mCallbackCountDownLatch.await( + WAIT_CALLBACK_CALLED_IN_SECONDS, TimeUnit.SECONDS); + + int expectedUid = context.getPackageManager().getPackageUid(TEAT_APK_PACKAGE_NAME, + PackageManager.PackageInfoFlags.of(0)); + assertThat(result).isTrue(); + assertThat(testPackageMonitor.mAddedPackageName).isEqualTo(TEAT_APK_PACKAGE_NAME); + assertThat(testPackageMonitor.mAddedPackageUid).isEqualTo(expectedUid); + } finally { + testPackageMonitor.unregister(); + uninstallTestPackage(); + } + } + + @Test + public void testPackageMonitorPackageNotVisible() throws Exception { + TestVisibilityPackageMonitor testPackageMonitor = new TestVisibilityPackageMonitor(); + + try { + Context context = InstrumentationRegistry.getInstrumentation().getContext(); + testPackageMonitor.register(context, UserHandle.ALL, + new Handler(Looper.getMainLooper())); + + installTestPackage(false /* forceQueryable */); + boolean result = testPackageMonitor.mCallbackCountDownLatch.await( + WAIT_CALLBACK_CALLED_IN_SECONDS, TimeUnit.SECONDS); + + assertThat(result).isFalse(); + } finally { + testPackageMonitor.unregister(); + uninstallTestPackage(); + } + } + + private static void installTestPackage(boolean forceQueryable) { + final StringBuilder cmd = new StringBuilder("pm install "); + if (forceQueryable) { + cmd.append("--force-queryable "); + } + cmd.append(TEAT_APK_PATH); + final String result = runShellCommand(cmd.toString()); + assertThat(result.trim()).contains("Success"); + } + + private static void uninstallTestPackage() { + runShellCommand("pm uninstall " + TEAT_APK_PACKAGE_NAME); + } + + private static class TestVisibilityPackageMonitor extends PackageMonitor { + String mAddedPackageName; + int mAddedPackageUid; + CountDownLatch mCallbackCountDownLatch = new CountDownLatch(1); + + @Override + public void onPackageAdded(String packageName, int uid) { + if (!TEAT_APK_PACKAGE_NAME.equals(packageName)) { + return; + } + mAddedPackageName = packageName; + mAddedPackageUid = uid; + mCallbackCountDownLatch.countDown(); + } + } +} diff --git a/core/tests/packagemonitortests/testapp/TestVisibilityApp/Android.bp b/core/tests/packagemonitortests/testapp/TestVisibilityApp/Android.bp new file mode 100644 index 000000000000..c0e98fcc595d --- /dev/null +++ b/core/tests/packagemonitortests/testapp/TestVisibilityApp/Android.bp @@ -0,0 +1,27 @@ +// Copyright (C) 2023 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_test_helper_app { + name: "TestVisibilityApp", + srcs: ["src/**/*.java"], + sdk_version: "current", + dex_preopt: { + enabled: false, + }, + test_suites: ["device-tests"], +} diff --git a/core/tests/packagemonitortests/testapp/TestVisibilityApp/AndroidManifest.xml b/core/tests/packagemonitortests/testapp/TestVisibilityApp/AndroidManifest.xml new file mode 100644 index 000000000000..0ba5058be979 --- /dev/null +++ b/core/tests/packagemonitortests/testapp/TestVisibilityApp/AndroidManifest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.example.android.testvisibilityapp"> + <application android:label="testvisibilityapp"> + <activity android:name="DummyActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/core/tests/packagemonitortests/testapp/TestVisibilityApp/res/layout/dummy_activity.xml b/core/tests/packagemonitortests/testapp/TestVisibilityApp/res/layout/dummy_activity.xml new file mode 100644 index 000000000000..9887dc7117d1 --- /dev/null +++ b/core/tests/packagemonitortests/testapp/TestVisibilityApp/res/layout/dummy_activity.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2023 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. +--> + +<EditText xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/text" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:textSize="18sp" + android:autoText="true" + android:capitalize="sentences" + android:text="@string/activity_text_text" /> + diff --git a/core/tests/packagemonitortests/testapp/TestVisibilityApp/res/values/strings.xml b/core/tests/packagemonitortests/testapp/TestVisibilityApp/res/values/strings.xml new file mode 100644 index 000000000000..5771bd50cfe7 --- /dev/null +++ b/core/tests/packagemonitortests/testapp/TestVisibilityApp/res/values/strings.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + + <string name="activity_text_text">Hello</string> + +</resources> diff --git a/core/tests/packagemonitortests/testapp/TestVisibilityApp/src/com/example/android/testvisibilityapp/DummyActivity.java b/core/tests/packagemonitortests/testapp/TestVisibilityApp/src/com/example/android/testvisibilityapp/DummyActivity.java new file mode 100644 index 000000000000..e74454e938b9 --- /dev/null +++ b/core/tests/packagemonitortests/testapp/TestVisibilityApp/src/com/example/android/testvisibilityapp/DummyActivity.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 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.example.android.testvisibilityapp; + +import android.app.Activity; +import android.os.Bundle; +import android.view.View; + +public class DummyActivity extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + View view = getLayoutInflater().inflate(R.layout.dummy_activity, null); + setContentView(view); + } +} + diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml index c6a9033210e3..43683ffad432 100644 --- a/data/etc/com.android.systemui.xml +++ b/data/etc/com.android.systemui.xml @@ -87,6 +87,5 @@ <permission name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS" /> <permission name="android.permission.READ_SEARCH_INDEXABLES" /> <permission name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"/> - <permission name="android.permission.QUERY_CLONED_APPS"/> </privapp-permissions> </permissions> diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index 99bebb8b9812..a2319a53a659 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -199,6 +199,8 @@ public abstract class ColorSpace { private static final float[] SRGB_PRIMARIES = { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f }; private static final float[] NTSC_1953_PRIMARIES = { 0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f }; + private static final float[] DCI_P3_PRIMARIES = + { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f }; private static final float[] BT2020_PRIMARIES = { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f }; /** @@ -211,6 +213,9 @@ public abstract class ColorSpace { private static final Rgb.TransferParameters SRGB_TRANSFER_PARAMETERS = new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4); + private static final Rgb.TransferParameters SMPTE_170M_TRANSFER_PARAMETERS = + new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45); + // HLG transfer with an SDR whitepoint of 203 nits private static final Rgb.TransferParameters BT2020_HLG_TRANSFER_PARAMETERS = new Rgb.TransferParameters(2.0, 2.0, 1 / 0.17883277, 0.28466892, 0.55991073, @@ -1559,10 +1564,10 @@ public abstract class ColorSpace { DataSpace.DATASPACE_SCRGB_LINEAR, Named.LINEAR_EXTENDED_SRGB.ordinal()); sNamedColorSpaces[Named.BT709.ordinal()] = new ColorSpace.Rgb( "Rec. ITU-R BT.709-5", - new float[] { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f }, + SRGB_PRIMARIES, ILLUMINANT_D65, null, - new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45), + SMPTE_170M_TRANSFER_PARAMETERS, Named.BT709.ordinal() ); sDataToColorSpaces.put(DataSpace.DATASPACE_BT709, Named.BT709.ordinal()); @@ -1577,7 +1582,7 @@ public abstract class ColorSpace { sDataToColorSpaces.put(DataSpace.DATASPACE_BT2020, Named.BT2020.ordinal()); sNamedColorSpaces[Named.DCI_P3.ordinal()] = new ColorSpace.Rgb( "SMPTE RP 431-2-2007 DCI (P3)", - new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f }, + DCI_P3_PRIMARIES, new float[] { 0.314f, 0.351f }, 2.6, 0.0f, 1.0f, @@ -1586,7 +1591,7 @@ public abstract class ColorSpace { sDataToColorSpaces.put(DataSpace.DATASPACE_DCI_P3, Named.DCI_P3.ordinal()); sNamedColorSpaces[Named.DISPLAY_P3.ordinal()] = new ColorSpace.Rgb( "Display P3", - new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f }, + DCI_P3_PRIMARIES, ILLUMINANT_D65, null, SRGB_TRANSFER_PARAMETERS, @@ -1598,7 +1603,7 @@ public abstract class ColorSpace { NTSC_1953_PRIMARIES, ILLUMINANT_C, null, - new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45), + SMPTE_170M_TRANSFER_PARAMETERS, Named.NTSC_1953.ordinal() ); sNamedColorSpaces[Named.SMPTE_C.ordinal()] = new ColorSpace.Rgb( @@ -1606,7 +1611,7 @@ public abstract class ColorSpace { new float[] { 0.630f, 0.340f, 0.310f, 0.595f, 0.155f, 0.070f }, ILLUMINANT_D65, null, - new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45), + SMPTE_170M_TRANSFER_PARAMETERS, Named.SMPTE_C.ordinal() ); sNamedColorSpaces[Named.ADOBE_RGB.ordinal()] = new ColorSpace.Rgb( @@ -3057,7 +3062,7 @@ public abstract class ColorSpace { * primaries for such a ColorSpace does not make sense, so we use a special * set of primaries that are all 1s.</p> * - * @return A new non-null array of 2 floats + * @return A new non-null array of 6 floats * * @see #getPrimaries(float[]) */ diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java index 381e9d472f0f..08b7bb89d10c 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java @@ -25,6 +25,7 @@ import android.window.TaskFragmentParentInfo; import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.window.extensions.core.util.function.Function; /** @@ -186,10 +187,21 @@ class SplitContainer { return (mSplitRule instanceof SplitPlaceholderRule); } - @NonNull - SplitInfo toSplitInfo() { - return new SplitInfo(mPrimaryContainer.toActivityStack(), - mSecondaryContainer.toActivityStack(), mCurrentSplitAttributes, mToken); + /** + * Returns the SplitInfo representing this container. + * + * @return the SplitInfo representing this container if the underlying TaskFragmentContainers + * are stable, or {@code null} if any TaskFragmentContainer is in an intermediate state. + */ + @Nullable + SplitInfo toSplitInfoIfStable() { + final ActivityStack primaryActivityStack = mPrimaryContainer.toActivityStackIfStable(); + final ActivityStack secondaryActivityStack = mSecondaryContainer.toActivityStackIfStable(); + if (primaryActivityStack == null || secondaryActivityStack == null) { + return null; + } + return new SplitInfo(primaryActivityStack, secondaryActivityStack, + mCurrentSplitAttributes, mToken); } static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index f95f3ffb4df3..3d72963944c2 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -1908,8 +1908,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (mEmbeddingCallback == null || !readyToReportToClient()) { return; } - final List<SplitInfo> currentSplitStates = getActiveSplitStates(); - if (mLastReportedSplitStates.equals(currentSplitStates)) { + final List<SplitInfo> currentSplitStates = getActiveSplitStatesIfStable(); + if (currentSplitStates == null || mLastReportedSplitStates.equals(currentSplitStates)) { return; } mLastReportedSplitStates.clear(); @@ -1919,13 +1919,21 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * Returns a list of descriptors for currently active split states. + * + * @return a list of descriptors for currently active split states if all the containers are in + * a stable state, or {@code null} otherwise. */ @GuardedBy("mLock") - @NonNull - private List<SplitInfo> getActiveSplitStates() { + @Nullable + private List<SplitInfo> getActiveSplitStatesIfStable() { final List<SplitInfo> splitStates = new ArrayList<>(); for (int i = mTaskContainers.size() - 1; i >= 0; i--) { - mTaskContainers.valueAt(i).getSplitStates(splitStates); + final List<SplitInfo> taskSplitStates = + mTaskContainers.valueAt(i).getSplitStatesIfStable(); + if (taskSplitStates == null) { + return null; + } + splitStates.addAll(taskSplitStates); } return splitStates; } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java index 969e3ed5b9b6..fa1eb9e1d167 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -329,11 +329,23 @@ class TaskContainer { } } - /** Adds the descriptors of split states in this Task to {@code outSplitStates}. */ - void getSplitStates(@NonNull List<SplitInfo> outSplitStates) { + /** + * Gets the descriptors of split states in this Task. + * + * @return a list of {@code SplitInfo} if all the SplitContainers are stable, or {@code null} if + * any SplitContainer is in an intermediate state. + */ + @Nullable + List<SplitInfo> getSplitStatesIfStable() { + final List<SplitInfo> splitStates = new ArrayList<>(); for (SplitContainer container : mSplitContainers) { - outSplitStates.add(container.toSplitInfo()); + final SplitInfo splitInfo = container.toSplitInfoIfStable(); + if (splitInfo == null) { + return null; + } + splitStates.add(splitInfo); } + return splitStates; } /** A wrapper class which contains the information of {@link TaskContainer} */ diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index 61df335515b8..0a694b5c3b64 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -217,6 +217,30 @@ class TaskFragmentContainer { /** List of non-finishing activities that belong to this container and live in this process. */ @NonNull List<Activity> collectNonFinishingActivities() { + final List<Activity> activities = collectNonFinishingActivities(false /* checkIfStable */); + if (activities == null) { + throw new IllegalStateException( + "Result activities should never be null when checkIfstable is false."); + } + return activities; + } + + /** + * Collects non-finishing activities that belong to this container and live in this process. + * + * @param checkIfStable if {@code true}, returns {@code null} when the container is in an + * intermediate state. + * @return List of non-finishing activities that belong to this container and live in this + * process, {@code null} if checkIfStable is {@code true} and the container is in an + * intermediate state. + */ + @Nullable + List<Activity> collectNonFinishingActivities(boolean checkIfStable) { + if (checkIfStable + && (mInfo == null || mInfo.isEmpty() || !mPendingAppearedActivities.isEmpty())) { + return null; + } + final List<Activity> allActivities = new ArrayList<>(); if (mInfo != null) { // Add activities reported from the server. @@ -224,6 +248,15 @@ class TaskFragmentContainer { final Activity activity = mController.getActivity(token); if (activity != null && !activity.isFinishing()) { allActivities.add(activity); + } else { + if (checkIfStable) { + // Return null except for a special case when the activity is started in + // background. + if (activity == null && !mTaskContainer.isVisible()) { + continue; + } + return null; + } } } } @@ -277,9 +310,19 @@ class TaskFragmentContainer { return false; } - @NonNull - ActivityStack toActivityStack() { - return new ActivityStack(collectNonFinishingActivities(), isEmpty(), mToken); + /** + * Returns the ActivityStack representing this container. + * + * @return ActivityStack representing this container if it is in a stable state. {@code null} if + * in an intermediate state. + */ + @Nullable + ActivityStack toActivityStackIfStable() { + final List<Activity> activities = collectNonFinishingActivities(true /* checkIfStable */); + if (activities == null) { + return null; + } + return new ActivityStack(activities, isEmpty(), mToken); } /** Adds the activity that will be reparented to this container. */ diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index 9af1fe916279..b504b0c0d138 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -1251,6 +1251,34 @@ public class SplitControllerTest { } @Test + public void testSplitInfoCallback_NotReportSplitIfUnstable() { + final Activity r0 = createMockActivity(); + final Activity r1 = createMockActivity(); + addSplitTaskFragments(r0, r1); + + // Should report new SplitInfo list if stable. + mSplitController.updateCallbackIfNecessary(); + assertEquals(1, mSplitInfos.size()); + + // Should not report new SplitInfo list if unstable, e.g. any Activity is finishing. + mSplitInfos.clear(); + final Activity r2 = createMockActivity(); + final Activity r3 = createMockActivity(); + doReturn(true).when(r2).isFinishing(); + addSplitTaskFragments(r2, r3); + + mSplitController.updateCallbackIfNecessary(); + assertTrue(mSplitInfos.isEmpty()); + + // Should report SplitInfo list if it becomes stable again. + mSplitInfos.clear(); + doReturn(false).when(r2).isFinishing(); + + mSplitController.updateCallbackIfNecessary(); + assertEquals(2, mSplitInfos.size()); + } + + @Test public void testSplitInfoCallback_reportSplitInMultipleTasks() { final int taskId0 = 1; final int taskId1 = 2; diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java index 000c65a75c81..21889960a8b2 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java @@ -48,6 +48,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; + /** * Test class for {@link TaskContainer}. * @@ -165,4 +167,35 @@ public class TaskContainerTest { doReturn(activity1).when(tf1).getTopNonFinishingActivity(); assertEquals(activity1, taskContainer.getTopNonFinishingActivity()); } + + @Test + public void testGetSplitStatesIfStable() { + final TaskContainer taskContainer = createTestTaskContainer(); + + final SplitContainer splitContainer0 = mock(SplitContainer.class); + final SplitContainer splitContainer1 = mock(SplitContainer.class); + final SplitInfo splitInfo0 = mock(SplitInfo.class); + final SplitInfo splitInfo1 = mock(SplitInfo.class); + taskContainer.addSplitContainer(splitContainer0); + taskContainer.addSplitContainer(splitContainer1); + + // When all the SplitContainers are stable, getSplitStatesIfStable() returns the list of + // SplitInfo representing the SplitContainers. + doReturn(splitInfo0).when(splitContainer0).toSplitInfoIfStable(); + doReturn(splitInfo1).when(splitContainer1).toSplitInfoIfStable(); + + List<SplitInfo> splitInfoList = taskContainer.getSplitStatesIfStable(); + + assertEquals(2, splitInfoList.size()); + assertEquals(splitInfo0, splitInfoList.get(0)); + assertEquals(splitInfo1, splitInfoList.get(1)); + + // When any SplitContainer is in an intermediate state, getSplitStatesIfStable() returns + // null. + doReturn(null).when(splitContainer0).toSplitInfoIfStable(); + + splitInfoList = taskContainer.getSplitStatesIfStable(); + + assertNull(splitInfoList); + } } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java index 78b85e642c13..cc00a49604ee 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java @@ -321,6 +321,32 @@ public class TaskFragmentContainerTest { } @Test + public void testCollectNonFinishingActivities_checkIfStable() { + final TaskContainer taskContainer = createTestTaskContainer(); + final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, + mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); + + // In case mInfo is null, collectNonFinishingActivities(true) should return null. + List<Activity> activities = + container.collectNonFinishingActivities(true /* checkIfStable */); + assertNull(activities); + + // collectNonFinishingActivities(true) should return proper value when the container is in a + // stable state. + final List<IBinder> runningActivities = Lists.newArrayList(mActivity.getActivityToken()); + doReturn(runningActivities).when(mInfo).getActivities(); + container.setInfo(mTransaction, mInfo); + activities = container.collectNonFinishingActivities(true /* checkIfStable */); + assertEquals(1, activities.size()); + + // In case any activity is finishing, collectNonFinishingActivities(true) should return + // null. + doReturn(true).when(mActivity).isFinishing(); + activities = container.collectNonFinishingActivities(true /* checkIfStable */); + assertNull(activities); + } + + @Test public void testAddPendingActivity() { final TaskContainer taskContainer = createTestTaskContainer(); final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java index 57d374b2b8f5..06ce37148eaf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java @@ -186,6 +186,6 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle if (callback == null) { throw new IllegalStateException("No finish callback found"); } - callback.onTransitionFinished(null /* wct */, null /* wctCB */); + callback.onTransitionFinished(null /* wct */); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index ee6996d3d23d..2c100653dae0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -661,14 +661,26 @@ public class BubblePositioner { final boolean startOnLeft = mContext.getResources().getConfiguration().getLayoutDirection() != LAYOUT_DIRECTION_RTL; - final float startingVerticalOffset = mContext.getResources().getDimensionPixelOffset( - R.dimen.bubble_stack_starting_offset_y); - // TODO: placement bug here because mPositionRect doesn't handle the overhanging edge - return new BubbleStackView.RelativeStackPosition( - startOnLeft, - startingVerticalOffset / mPositionRect.height()) - .getAbsolutePositionInRegion(getAllowableStackPositionRegion( - 1 /* default starts with 1 bubble */)); + final RectF allowableStackPositionRegion = getAllowableStackPositionRegion( + 1 /* default starts with 1 bubble */); + if (isLargeScreen()) { + // We want the stack to be visually centered on the edge, so we need to base it + // of a rect that includes insets. + final float desiredY = mScreenRect.height() / 2f - (mBubbleSize / 2f); + final float offset = desiredY / mScreenRect.height(); + return new BubbleStackView.RelativeStackPosition( + startOnLeft, + offset) + .getAbsolutePositionInRegion(allowableStackPositionRegion); + } else { + final float startingVerticalOffset = mContext.getResources().getDimensionPixelOffset( + R.dimen.bubble_stack_starting_offset_y); + // TODO: placement bug here because mPositionRect doesn't handle the overhanging edge + return new BubbleStackView.RelativeStackPosition( + startOnLeft, + startingVerticalOffset / mPositionRect.height()) + .getAbsolutePositionInRegion(allowableStackPositionRegion); + } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt new file mode 100644 index 000000000000..fd000ee1d24f --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.common.pip + +import android.content.Context +import android.content.res.Resources +import android.graphics.PointF +import android.util.Size +import com.android.wm.shell.R +import com.android.wm.shell.pip.PipDisplayLayoutState + +class LegacySizeSpecSource( + private val context: Context, + private val pipDisplayLayoutState: PipDisplayLayoutState +) : SizeSpecSource { + + private var mDefaultMinSize = 0 + /** The absolute minimum an overridden size's edge can be */ + private var mOverridableMinSize = 0 + /** The preferred minimum (and default minimum) size specified by apps. */ + private var mOverrideMinSize: Size? = null + + private var mDefaultSizePercent = 0f + private var mMinimumSizePercent = 0f + private var mMaxAspectRatioForMinSize = 0f + private var mMinAspectRatioForMinSize = 0f + + init { + reloadResources() + } + + private fun reloadResources() { + val res: Resources = context.getResources() + + mDefaultMinSize = res.getDimensionPixelSize( + R.dimen.default_minimal_size_pip_resizable_task) + mOverridableMinSize = res.getDimensionPixelSize( + R.dimen.overridable_minimal_size_pip_resizable_task) + + mDefaultSizePercent = res.getFloat(R.dimen.config_pictureInPictureDefaultSizePercent) + mMinimumSizePercent = res.getFraction(R.fraction.config_pipShortestEdgePercent, 1, 1) + + mMaxAspectRatioForMinSize = res.getFloat( + R.dimen.config_pictureInPictureAspectRatioLimitForMinSize) + mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize + } + + override fun onConfigurationChanged() { + reloadResources() + } + + override fun getMaxSize(aspectRatio: Float): Size { + val insetBounds = pipDisplayLayoutState.insetBounds + + val shorterLength: Int = Math.min(getDisplayBounds().width(), + getDisplayBounds().height()) + val totalHorizontalPadding: Int = (insetBounds.left + + (getDisplayBounds().width() - insetBounds.right)) + val totalVerticalPadding: Int = (insetBounds.top + + (getDisplayBounds().height() - insetBounds.bottom)) + + return if (aspectRatio > 1f) { + val maxWidth = Math.max(getDefaultSize(aspectRatio).width, + shorterLength - totalHorizontalPadding) + val maxHeight = (maxWidth / aspectRatio).toInt() + Size(maxWidth, maxHeight) + } else { + val maxHeight = Math.max(getDefaultSize(aspectRatio).height, + shorterLength - totalVerticalPadding) + val maxWidth = (maxHeight * aspectRatio).toInt() + Size(maxWidth, maxHeight) + } + } + + override fun getDefaultSize(aspectRatio: Float): Size { + if (mOverrideMinSize != null) { + return getMinSize(aspectRatio) + } + val smallestDisplaySize: Int = Math.min(getDisplayBounds().width(), + getDisplayBounds().height()) + val minSize = Math.max(getMinEdgeSize().toFloat(), + smallestDisplaySize * mDefaultSizePercent).toInt() + val width: Int + val height: Int + if (aspectRatio <= mMinAspectRatioForMinSize || + aspectRatio > mMaxAspectRatioForMinSize) { + // Beyond these points, we can just use the min size as the shorter edge + if (aspectRatio <= 1) { + // Portrait, width is the minimum size + width = minSize + height = Math.round(width / aspectRatio) + } else { + // Landscape, height is the minimum size + height = minSize + width = Math.round(height * aspectRatio) + } + } else { + // Within these points, ensure that the bounds fit within the radius of the limits + // at the points + val widthAtMaxAspectRatioForMinSize: Float = mMaxAspectRatioForMinSize * minSize + val radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize.toFloat()) + height = Math.round(Math.sqrt((radius * radius / + (aspectRatio * aspectRatio + 1)).toDouble())).toInt() + width = Math.round(height * aspectRatio) + } + return Size(width, height) + } + + override fun getMinSize(aspectRatio: Float): Size { + if (mOverrideMinSize != null) { + return adjustOverrideMinSizeToAspectRatio(aspectRatio)!! + } + val shorterLength: Int = Math.min(getDisplayBounds().width(), + getDisplayBounds().height()) + val minWidth: Int + val minHeight: Int + if (aspectRatio > 1f) { + minWidth = Math.min(getDefaultSize(aspectRatio).width.toFloat(), + shorterLength * mMinimumSizePercent).toInt() + minHeight = (minWidth / aspectRatio).toInt() + } else { + minHeight = Math.min(getDefaultSize(aspectRatio).height.toFloat(), + shorterLength * mMinimumSizePercent).toInt() + minWidth = (minHeight * aspectRatio).toInt() + } + return Size(minWidth, minHeight) + } + + override fun getSizeForAspectRatio(size: Size, aspectRatio: Float): Size { + val smallestSize = Math.min(size.width, size.height) + val minSize = Math.max(getMinEdgeSize(), smallestSize) + val width: Int + val height: Int + if (aspectRatio <= 1) { + // Portrait, width is the minimum size. + width = minSize + height = Math.round(width / aspectRatio) + } else { + // Landscape, height is the minimum size + height = minSize + width = Math.round(height * aspectRatio) + } + return Size(width, height) + } + + private fun getDisplayBounds() = pipDisplayLayoutState.displayBounds + + /** Sets the preferred size of PIP as specified by the activity in PIP mode. */ + override fun setOverrideMinSize(overrideMinSize: Size?) { + mOverrideMinSize = overrideMinSize + } + + /** Returns the preferred minimal size specified by the activity in PIP. */ + override fun getOverrideMinSize(): Size? { + val overrideMinSize = mOverrideMinSize ?: return null + return if (overrideMinSize.width < mOverridableMinSize || + overrideMinSize.height < mOverridableMinSize) { + Size(mOverridableMinSize, mOverridableMinSize) + } else { + overrideMinSize + } + } + + private fun getMinEdgeSize(): Int { + return if (mOverrideMinSize == null) mDefaultMinSize else getOverrideMinEdgeSize() + } + + /** + * Returns the adjusted overridden min size if it is set; otherwise, returns null. + * + * + * Overridden min size needs to be adjusted in its own way while making sure that the target + * aspect ratio is maintained + * + * @param aspectRatio target aspect ratio + */ + private fun adjustOverrideMinSizeToAspectRatio(aspectRatio: Float): Size? { + val size = getOverrideMinSize() ?: return null + val sizeAspectRatio = size.width / size.height.toFloat() + return if (sizeAspectRatio > aspectRatio) { + // Size is wider, fix the width and increase the height + Size(size.width, (size.width / aspectRatio).toInt()) + } else { + // Size is taller, fix the height and adjust the width. + Size((size.height * aspectRatio).toInt(), size.height) + } + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt new file mode 100644 index 000000000000..c5630686f528 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.common.pip + +import android.content.Context +import android.content.res.Resources +import android.os.SystemProperties +import android.util.Size +import com.android.wm.shell.R +import com.android.wm.shell.pip.PipDisplayLayoutState +import java.io.PrintWriter + +class PhoneSizeSpecSource( + private val context: Context, + private val pipDisplayLayoutState: PipDisplayLayoutState +) : SizeSpecSource { + private var DEFAULT_OPTIMIZED_ASPECT_RATIO = 9f / 16 + + private var mDefaultMinSize = 0 + /** The absolute minimum an overridden size's edge can be */ + private var mOverridableMinSize = 0 + /** The preferred minimum (and default minimum) size specified by apps. */ + private var mOverrideMinSize: Size? = null + + + /** Default and minimum percentages for the PIP size logic. */ + private val mDefaultSizePercent: Float + private val mMinimumSizePercent: Float + + /** Aspect ratio that the PIP size spec logic optimizes for. */ + private var mOptimizedAspectRatio = 0f + + init { + mDefaultSizePercent = SystemProperties + .get("com.android.wm.shell.pip.phone.def_percentage", "0.6").toFloat() + mMinimumSizePercent = SystemProperties + .get("com.android.wm.shell.pip.phone.min_percentage", "0.5").toFloat() + + reloadResources() + } + + private fun reloadResources() { + val res: Resources = context.getResources() + + mDefaultMinSize = res.getDimensionPixelSize( + R.dimen.default_minimal_size_pip_resizable_task) + mOverridableMinSize = res.getDimensionPixelSize( + R.dimen.overridable_minimal_size_pip_resizable_task) + + val requestedOptAspRatio = res.getFloat(R.dimen.config_pipLargeScreenOptimizedAspectRatio) + // make sure the optimized aspect ratio is valid with a default value to fall back to + mOptimizedAspectRatio = if (requestedOptAspRatio > 1) { + DEFAULT_OPTIMIZED_ASPECT_RATIO + } else { + requestedOptAspRatio + } + } + + override fun onConfigurationChanged() { + reloadResources() + } + + /** + * Calculates the max size of PIP. + * + * Optimizes for 16:9 aspect ratios, making them take full length of shortest display edge. + * As aspect ratio approaches values close to 1:1, the logic does not let PIP occupy the + * whole screen. A linear function is used to calculate these sizes. + * + * @param aspectRatio aspect ratio of the PIP window + * @return dimensions of the max size of the PIP + */ + override fun getMaxSize(aspectRatio: Float): Size { + val insetBounds = pipDisplayLayoutState.insetBounds + val displayBounds = pipDisplayLayoutState.displayBounds + + val totalHorizontalPadding: Int = (insetBounds.left + + (displayBounds.width() - insetBounds.right)) + val totalVerticalPadding: Int = (insetBounds.top + + (displayBounds.height() - insetBounds.bottom)) + val shorterLength: Int = Math.min(displayBounds.width() - totalHorizontalPadding, + displayBounds.height() - totalVerticalPadding) + var maxWidth: Int + val maxHeight: Int + + // use the optimized max sizing logic only within a certain aspect ratio range + if (aspectRatio >= mOptimizedAspectRatio && aspectRatio <= 1 / mOptimizedAspectRatio) { + // this formula and its derivation is explained in b/198643358#comment16 + maxWidth = Math.round(mOptimizedAspectRatio * shorterLength + + shorterLength * (aspectRatio - mOptimizedAspectRatio) / (1 + aspectRatio)) + // make sure the max width doesn't go beyond shorter screen length after rounding + maxWidth = Math.min(maxWidth, shorterLength) + maxHeight = Math.round(maxWidth / aspectRatio) + } else { + if (aspectRatio > 1f) { + maxWidth = shorterLength + maxHeight = Math.round(maxWidth / aspectRatio) + } else { + maxHeight = shorterLength + maxWidth = Math.round(maxHeight * aspectRatio) + } + } + return Size(maxWidth, maxHeight) + } + + /** + * Decreases the dimensions by a percentage relative to max size to get default size. + * + * @param aspectRatio aspect ratio of the PIP window + * @return dimensions of the default size of the PIP + */ + override fun getDefaultSize(aspectRatio: Float): Size { + val minSize = getMinSize(aspectRatio) + if (mOverrideMinSize != null) { + return minSize + } + val maxSize = getMaxSize(aspectRatio) + val defaultWidth = Math.max(Math.round(maxSize.width * mDefaultSizePercent), + minSize.width) + val defaultHeight = Math.round(defaultWidth / aspectRatio) + return Size(defaultWidth, defaultHeight) + } + + /** + * Decreases the dimensions by a certain percentage relative to max size to get min size. + * + * @param aspectRatio aspect ratio of the PIP window + * @return dimensions of the min size of the PIP + */ + override fun getMinSize(aspectRatio: Float): Size { + // if there is an overridden min size provided, return that + if (mOverrideMinSize != null) { + return adjustOverrideMinSizeToAspectRatio(aspectRatio)!! + } + val maxSize = getMaxSize(aspectRatio) + var minWidth = Math.round(maxSize.width * mMinimumSizePercent) + var minHeight = Math.round(maxSize.height * mMinimumSizePercent) + + // make sure the calculated min size is not smaller than the allowed default min size + if (aspectRatio > 1f) { + minHeight = Math.max(minHeight, mDefaultMinSize) + minWidth = Math.round(minHeight * aspectRatio) + } else { + minWidth = Math.max(minWidth, mDefaultMinSize) + minHeight = Math.round(minWidth / aspectRatio) + } + return Size(minWidth, minHeight) + } + + /** + * Returns the size for target aspect ratio making sure new size conforms with the rules. + * + * + * Recalculates the dimensions such that the target aspect ratio is achieved, while + * maintaining the same maximum size to current size ratio. + * + * @param size current size + * @param aspectRatio target aspect ratio + */ + override fun getSizeForAspectRatio(size: Size, aspectRatio: Float): Size { + if (size == mOverrideMinSize) { + return adjustOverrideMinSizeToAspectRatio(aspectRatio)!! + } + + val currAspectRatio = size.width.toFloat() / size.height + + // getting the percentage of the max size that current size takes + val currentMaxSize = getMaxSize(currAspectRatio) + val currentPercent = size.width.toFloat() / currentMaxSize.width + + // getting the max size for the target aspect ratio + val updatedMaxSize = getMaxSize(aspectRatio) + var width = Math.round(updatedMaxSize.width * currentPercent) + var height = Math.round(updatedMaxSize.height * currentPercent) + + // adjust the dimensions if below allowed min edge size + val minEdgeSize = + if (mOverrideMinSize == null) mDefaultMinSize else getOverrideMinEdgeSize() + + if (width < minEdgeSize && aspectRatio <= 1) { + width = minEdgeSize + height = Math.round(width / aspectRatio) + } else if (height < minEdgeSize && aspectRatio > 1) { + height = minEdgeSize + width = Math.round(height * aspectRatio) + } + + // reduce the dimensions of the updated size to the calculated percentage + return Size(width, height) + } + + /** Sets the preferred size of PIP as specified by the activity in PIP mode. */ + override fun setOverrideMinSize(overrideMinSize: Size?) { + mOverrideMinSize = overrideMinSize + } + + /** Returns the preferred minimal size specified by the activity in PIP. */ + override fun getOverrideMinSize(): Size? { + val overrideMinSize = mOverrideMinSize ?: return null + return if (overrideMinSize.width < mOverridableMinSize || + overrideMinSize.height < mOverridableMinSize) { + Size(mOverridableMinSize, mOverridableMinSize) + } else { + overrideMinSize + } + } + + /** + * Returns the adjusted overridden min size if it is set; otherwise, returns null. + * + * + * Overridden min size needs to be adjusted in its own way while making sure that the target + * aspect ratio is maintained + * + * @param aspectRatio target aspect ratio + */ + private fun adjustOverrideMinSizeToAspectRatio(aspectRatio: Float): Size? { + val size = getOverrideMinSize() ?: return null + val sizeAspectRatio = size.width / size.height.toFloat() + return if (sizeAspectRatio > aspectRatio) { + // Size is wider, fix the width and increase the height + Size(size.width, (size.width / aspectRatio).toInt()) + } else { + // Size is taller, fix the height and adjust the width. + Size((size.height * aspectRatio).toInt(), size.height) + } + } + + override fun dump(pw: PrintWriter, prefix: String) { + val innerPrefix = "$prefix " + pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize) + pw.println(innerPrefix + "mOverridableMinSize=" + mOverridableMinSize) + pw.println(innerPrefix + "mDefaultMinSize=" + mDefaultMinSize) + pw.println(innerPrefix + "mDefaultSizePercent=" + mDefaultSizePercent) + pw.println(innerPrefix + "mMinimumSizePercent=" + mMinimumSizePercent) + pw.println(innerPrefix + "mOptimizedAspectRatio=" + mOptimizedAspectRatio) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/SizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/SizeSpecSource.kt new file mode 100644 index 000000000000..7b3b9efb0de0 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/SizeSpecSource.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.common.pip + +import android.util.Size +import java.io.PrintWriter + +interface SizeSpecSource { + /** Returns max size allowed for the PIP window */ + fun getMaxSize(aspectRatio: Float): Size + + /** Returns default size for the PIP window */ + fun getDefaultSize(aspectRatio: Float): Size + + /** Returns min size allowed for the PIP window */ + fun getMinSize(aspectRatio: Float): Size + + /** Returns the adjusted size based on current size and target aspect ratio */ + fun getSizeForAspectRatio(size: Size, aspectRatio: Float): Size + + /** Overrides the minimum pip size requested by the app */ + fun setOverrideMinSize(overrideMinSize: Size?) + + /** Returns the minimum pip size requested by the app */ + fun getOverrideMinSize(): Size? + + /** Returns the minimum edge size of the override minimum size, or 0 if not set. */ + fun getOverrideMinEdgeSize(): Int { + val overrideMinSize = getOverrideMinSize() ?: return 0 + return Math.min(overrideMinSize.width, overrideMinSize.height) + } + + fun onConfigurationChanged() {} + + /** Dumps the internal state of the size spec */ + fun dump(pw: PrintWriter, prefix: String) {} +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index e8fa638bea31..d3fada37a685 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -722,10 +722,6 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange return bounds.width() > bounds.height(); } - public boolean isDensityChanged(int densityDpi) { - return mDensity != densityDpi; - } - /** * Return if this layout is landscape. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java index 16c39601e0bf..54be90197e47 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java @@ -30,6 +30,8 @@ import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TabletopModeController; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.common.pip.PhoneSizeSpecSource; +import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.dagger.WMShellBaseModule; import com.android.wm.shell.dagger.WMSingleton; import com.android.wm.shell.onehanded.OneHandedController; @@ -53,7 +55,6 @@ import com.android.wm.shell.pip.phone.PhonePipKeepClearAlgorithm; import com.android.wm.shell.pip.phone.PhonePipMenuController; import com.android.wm.shell.pip.phone.PipController; import com.android.wm.shell.pip.phone.PipMotionHelper; -import com.android.wm.shell.pip.phone.PipSizeSpecHandler; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellCommandHandler; @@ -87,7 +88,6 @@ public abstract class Pip1Module { PipBoundsAlgorithm pipBoundsAlgorithm, PhonePipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState, - PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState, PipMotionHelper pipMotionHelper, PipMediaController pipMediaController, @@ -110,8 +110,7 @@ public abstract class Pip1Module { context, shellInit, shellCommandHandler, shellController, displayController, pipAnimationController, pipAppOpsListener, pipBoundsAlgorithm, - pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler, - pipDisplayLayoutState, + pipKeepClearAlgorithm, pipBoundsState, pipDisplayLayoutState, pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController, windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder, @@ -123,8 +122,8 @@ public abstract class Pip1Module { @WMSingleton @Provides static PipBoundsState providePipBoundsState(Context context, - PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState) { - return new PipBoundsState(context, pipSizeSpecHandler, pipDisplayLayoutState); + SizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) { + return new PipBoundsState(context, sizeSpecSource, pipDisplayLayoutState); } @WMSingleton @@ -141,19 +140,12 @@ public abstract class Pip1Module { @WMSingleton @Provides - static PipSizeSpecHandler providePipSizeSpecHelper(Context context, - PipDisplayLayoutState pipDisplayLayoutState) { - return new PipSizeSpecHandler(context, pipDisplayLayoutState); - } - - @WMSingleton - @Provides static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context, PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm, PhonePipKeepClearAlgorithm pipKeepClearAlgorithm, - PipSizeSpecHandler pipSizeSpecHandler) { + PipDisplayLayoutState pipDisplayLayoutState, SizeSpecSource sizeSpecSource) { return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm, - pipKeepClearAlgorithm, pipSizeSpecHandler); + pipKeepClearAlgorithm, pipDisplayLayoutState, sizeSpecSource); } // Handler is used by Icon.loadDrawableAsync @@ -177,14 +169,14 @@ public abstract class Pip1Module { PhonePipMenuController menuPhoneController, PipBoundsAlgorithm pipBoundsAlgorithm, PipBoundsState pipBoundsState, - PipSizeSpecHandler pipSizeSpecHandler, + SizeSpecSource sizeSpecSource, PipTaskOrganizer pipTaskOrganizer, PipMotionHelper pipMotionHelper, FloatingContentCoordinator floatingContentCoordinator, PipUiEventLogger pipUiEventLogger, @ShellMainThread ShellExecutor mainExecutor) { return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm, - pipBoundsState, pipSizeSpecHandler, pipTaskOrganizer, pipMotionHelper, + pipBoundsState, sizeSpecSource, pipTaskOrganizer, pipMotionHelper, floatingContentCoordinator, pipUiEventLogger, mainExecutor); } @@ -243,6 +235,13 @@ public abstract class Pip1Module { @WMSingleton @Provides + static SizeSpecSource provideSizeSpecSource(Context context, + PipDisplayLayoutState pipDisplayLayoutState) { + return new PhoneSizeSpecSource(context, pipDisplayLayoutState); + } + + @WMSingleton + @Provides static PipAppOpsListener providePipAppOpsListener(Context context, PipTouchHandler pipTouchHandler, @ShellMainThread ShellExecutor mainExecutor) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java index 360bf8b70fca..52c6d2008a9d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java @@ -28,6 +28,8 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.common.pip.LegacySizeSpecSource; +import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.dagger.WMShellBaseModule; import com.android.wm.shell.dagger.WMSingleton; import com.android.wm.shell.pip.Pip; @@ -42,7 +44,6 @@ import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; import com.android.wm.shell.pip.PipUiEventLogger; -import com.android.wm.shell.pip.phone.PipSizeSpecHandler; import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm; import com.android.wm.shell.pip.tv.TvPipBoundsController; import com.android.wm.shell.pip.tv.TvPipBoundsState; @@ -138,23 +139,23 @@ public abstract class TvPipModule { @Provides static TvPipBoundsAlgorithm provideTvPipBoundsAlgorithm(Context context, TvPipBoundsState tvPipBoundsState, PipSnapAlgorithm pipSnapAlgorithm, - PipSizeSpecHandler pipSizeSpecHandler) { + PipDisplayLayoutState pipDisplayLayoutState, SizeSpecSource sizeSpecSource) { return new TvPipBoundsAlgorithm(context, tvPipBoundsState, pipSnapAlgorithm, - pipSizeSpecHandler); + pipDisplayLayoutState, sizeSpecSource); } @WMSingleton @Provides static TvPipBoundsState provideTvPipBoundsState(Context context, - PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState) { - return new TvPipBoundsState(context, pipSizeSpecHandler, pipDisplayLayoutState); + SizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) { + return new TvPipBoundsState(context, sizeSpecSource, pipDisplayLayoutState); } @WMSingleton @Provides - static PipSizeSpecHandler providePipSizeSpecHelper(Context context, + static SizeSpecSource provideSizeSpecSource(Context context, PipDisplayLayoutState pipDisplayLayoutState) { - return new PipSizeSpecHandler(context, pipDisplayLayoutState); + return new LegacySizeSpecSource(context, pipDisplayLayoutState); } // Handler needed for loadDrawableAsync() in PipControlsViewController diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java index 16b23935559c..024465b281b8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java @@ -241,7 +241,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition public void onAnimationEnd(Animator animation) { mDesktopModeWindowDecoration.hideResizeVeil(); mTransitions.getMainExecutor().execute( - () -> finishCallback.onTransitionFinished(null, null)); + () -> finishCallback.onTransitionFinished(null)); } }); animator.start(); @@ -271,8 +271,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition startT.apply(); - mTransitions.getMainExecutor().execute( - () -> finishCallback.onTransitionFinished(null, null)); + mTransitions.getMainExecutor().execute(() -> finishCallback.onTransitionFinished(null)); return true; } @@ -324,7 +323,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition mOnAnimationFinishedCallback.accept(finishT); } mTransitions.getMainExecutor().execute( - () -> finishCallback.onTransitionFinished(null, null)); + () -> finishCallback.onTransitionFinished(null)); } }); @@ -378,7 +377,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition mOnAnimationFinishedCallback.accept(finishT); } mTransitions.getMainExecutor().execute( - () -> finishCallback.onTransitionFinished(null, null)); + () -> finishCallback.onTransitionFinished(null)); } }); animator.start(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java index 3ad5edf0e604..7342bd1ae5de 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java @@ -168,7 +168,7 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH mOnAnimationFinishedCallback.accept(finishT); } mTransitions.getMainExecutor().execute( - () -> finishCallback.onTransitionFinished(null, null)); + () -> finishCallback.onTransitionFinished(null)); } }); animator.start(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt index 94788e45e2b6..b9cb5c75aaf0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt @@ -104,7 +104,7 @@ class ToggleResizeDesktopTaskTransitionHandler( .setWindowCrop(leash, endBounds.width(), endBounds.height()) .show(leash) windowDecor.hideResizeVeil() - finishCallback.onTransitionFinished(null, null) + finishCallback.onTransitionFinished(null) boundsAnimator = null } ) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java index 55e34fe3d836..84027753435b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java @@ -130,7 +130,7 @@ public class FreeformTaskTransitionHandler if (!animations.isEmpty()) return; mMainExecutor.execute(() -> { mAnimations.remove(transition); - finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + finishCallback.onTransitionFinished(null /* wct */); }); }; for (TransitionInfo.Change change : info.getChanges()) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java index 56bd188a0d7d..2ef92adb90f5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java @@ -19,7 +19,6 @@ package com.android.wm.shell.keyguard; import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; -import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_OCCLUDING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING; import static android.view.WindowManager.TRANSIT_SLEEP; @@ -169,7 +168,7 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler // Post our finish callback to let startAnimation finish first. mMainExecutor.executeDelayed(() -> { mStartedTransitions.remove(transition); - finishCallback.onTransitionFinished(wct, null); + finishCallback.onTransitionFinished(wct); }, 0); } }); @@ -206,7 +205,7 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler // implementing an AIDL interface. Log.wtf(TAG, "RemoteException thrown from KeyguardService transition", e); } - nextFinishCallback.onTransitionFinished(null, null); + nextFinishCallback.onTransitionFinished(null); } else if (nextInfo.getType() == TRANSIT_SLEEP) { // An empty SLEEP transition comes in as a signal to abort transitions whenever a sleep // token is held. In cases where keyguard is showing, we are running the animation for diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java index f51eb5299dd9..ac711eafe3ef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java @@ -28,7 +28,7 @@ import android.util.Size; import android.view.Gravity; import com.android.wm.shell.R; -import com.android.wm.shell.pip.phone.PipSizeSpecHandler; +import com.android.wm.shell.common.pip.SizeSpecSource; import java.io.PrintWriter; @@ -41,7 +41,8 @@ public class PipBoundsAlgorithm { private static final float INVALID_SNAP_FRACTION = -1f; @NonNull private final PipBoundsState mPipBoundsState; - @NonNull protected final PipSizeSpecHandler mPipSizeSpecHandler; + @NonNull protected final PipDisplayLayoutState mPipDisplayLayoutState; + @NonNull protected final SizeSpecSource mSizeSpecSource; private final PipSnapAlgorithm mSnapAlgorithm; private final PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm; @@ -53,11 +54,13 @@ public class PipBoundsAlgorithm { public PipBoundsAlgorithm(Context context, @NonNull PipBoundsState pipBoundsState, @NonNull PipSnapAlgorithm pipSnapAlgorithm, @NonNull PipKeepClearAlgorithmInterface pipKeepClearAlgorithm, - @NonNull PipSizeSpecHandler pipSizeSpecHandler) { + @NonNull PipDisplayLayoutState pipDisplayLayoutState, + @NonNull SizeSpecSource sizeSpecSource) { mPipBoundsState = pipBoundsState; mSnapAlgorithm = pipSnapAlgorithm; mPipKeepClearAlgorithm = pipKeepClearAlgorithm; - mPipSizeSpecHandler = pipSizeSpecHandler; + mPipDisplayLayoutState = pipDisplayLayoutState; + mSizeSpecSource = sizeSpecSource; reloadResources(context); // Initialize the aspect ratio to the default aspect ratio. Don't do this in reload // resources as it would clobber mAspectRatio when entering PiP from fullscreen which @@ -74,11 +77,6 @@ public class PipBoundsAlgorithm { R.dimen.config_pictureInPictureDefaultAspectRatio); mDefaultStackGravity = res.getInteger( R.integer.config_defaultPictureInPictureGravity); - final String screenEdgeInsetsDpString = res.getString( - R.string.config_defaultPictureInPictureScreenEdgeInsets); - final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty() - ? Size.parseSize(screenEdgeInsetsDpString) - : null; mMinAspectRatio = res.getFloat( com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio); mMaxAspectRatio = res.getFloat( @@ -160,8 +158,8 @@ public class PipBoundsAlgorithm { // If either dimension is smaller than the allowed minimum, adjust them // according to mOverridableMinSize return new Size( - Math.max(windowLayout.minWidth, mPipSizeSpecHandler.getOverrideMinEdgeSize()), - Math.max(windowLayout.minHeight, mPipSizeSpecHandler.getOverrideMinEdgeSize())); + Math.max(windowLayout.minWidth, getOverrideMinEdgeSize()), + Math.max(windowLayout.minHeight, getOverrideMinEdgeSize())); } return null; } @@ -255,10 +253,10 @@ public class PipBoundsAlgorithm { final Size size; if (useCurrentMinEdgeSize || useCurrentSize) { // Use the existing size but adjusted to the new aspect ratio. - size = mPipSizeSpecHandler.getSizeForAspectRatio( + size = mSizeSpecSource.getSizeForAspectRatio( new Size(stackBounds.width(), stackBounds.height()), aspectRatio); } else { - size = mPipSizeSpecHandler.getDefaultSize(aspectRatio); + size = mSizeSpecSource.getDefaultSize(aspectRatio); } final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f); @@ -287,7 +285,7 @@ public class PipBoundsAlgorithm { getInsetBounds(insetBounds); // Calculate the default size - defaultSize = mPipSizeSpecHandler.getDefaultSize(mDefaultAspectRatio); + defaultSize = mSizeSpecSource.getDefaultSize(mDefaultAspectRatio); // Now that we have the default size, apply the snap fraction if valid or position the // bounds using the default gravity. @@ -309,7 +307,11 @@ public class PipBoundsAlgorithm { * Populates the bounds on the screen that the PIP can be visible in. */ public void getInsetBounds(Rect outRect) { - outRect.set(mPipSizeSpecHandler.getInsetBounds()); + outRect.set(mPipDisplayLayoutState.getInsetBounds()); + } + + private int getOverrideMinEdgeSize() { + return mSizeSpecSource.getOverrideMinEdgeSize(); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java index 9a775dff1f69..279ffc50e07e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java @@ -36,7 +36,7 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.function.TriConsumer; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; -import com.android.wm.shell.pip.phone.PipSizeSpecHandler; +import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.io.PrintWriter; @@ -87,7 +87,7 @@ public class PipBoundsState { private int mStashOffset; private @Nullable PipReentryState mPipReentryState; private final LauncherState mLauncherState = new LauncherState(); - private final @Nullable PipSizeSpecHandler mPipSizeSpecHandler; + private final @NonNull SizeSpecSource mSizeSpecSource; private @Nullable ComponentName mLastPipComponentName; private final @NonNull MotionBoundsState mMotionBoundsState = new MotionBoundsState(); private boolean mIsImeShowing; @@ -127,17 +127,20 @@ public class PipBoundsState { private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback; private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>(); - public PipBoundsState(@NonNull Context context, PipSizeSpecHandler pipSizeSpecHandler, - PipDisplayLayoutState pipDisplayLayoutState) { + public PipBoundsState(@NonNull Context context, @NonNull SizeSpecSource sizeSpecSource, + @NonNull PipDisplayLayoutState pipDisplayLayoutState) { mContext = context; reloadResources(); - mPipSizeSpecHandler = pipSizeSpecHandler; + mSizeSpecSource = sizeSpecSource; mPipDisplayLayoutState = pipDisplayLayoutState; } /** Reloads the resources. */ public void onConfigurationChanged() { reloadResources(); + + // update the size spec resources upon config change too + mSizeSpecSource.onConfigurationChanged(); } private void reloadResources() { @@ -319,7 +322,7 @@ public class PipBoundsState { /** Sets the preferred size of PIP as specified by the activity in PIP mode. */ public void setOverrideMinSize(@Nullable Size overrideMinSize) { final boolean changed = !Objects.equals(overrideMinSize, getOverrideMinSize()); - mPipSizeSpecHandler.setOverrideMinSize(overrideMinSize); + mSizeSpecSource.setOverrideMinSize(overrideMinSize); if (changed && mOnMinimalSizeChangeCallback != null) { mOnMinimalSizeChangeCallback.run(); } @@ -328,12 +331,12 @@ public class PipBoundsState { /** Returns the preferred minimal size specified by the activity in PIP. */ @Nullable public Size getOverrideMinSize() { - return mPipSizeSpecHandler.getOverrideMinSize(); + return mSizeSpecSource.getOverrideMinSize(); } /** Returns the minimum edge size of the override minimum size, or 0 if not set. */ public int getOverrideMinEdgeSize() { - return mPipSizeSpecHandler.getOverrideMinEdgeSize(); + return mSizeSpecSource.getOverrideMinEdgeSize(); } /** Get the state of the bounds in motion. */ @@ -613,5 +616,6 @@ public class PipBoundsState { } mLauncherState.dump(pw, innerPrefix); mMotionBoundsState.dump(pw, innerPrefix); + mSizeSpecSource.dump(pw, innerPrefix); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java index 0f76af48199f..456f85b52da4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java @@ -16,12 +16,18 @@ package com.android.wm.shell.pip; +import static com.android.wm.shell.pip.PipUtils.dpToPx; + import android.content.Context; +import android.content.res.Resources; +import android.graphics.Point; import android.graphics.Rect; +import android.util.Size; import android.view.Surface; import androidx.annotation.NonNull; +import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.dagger.WMSingleton; @@ -40,13 +46,51 @@ public class PipDisplayLayoutState { private int mDisplayId; @NonNull private DisplayLayout mDisplayLayout; + private Point mScreenEdgeInsets = null; + @Inject public PipDisplayLayoutState(Context context) { mContext = context; mDisplayLayout = new DisplayLayout(); + reloadResources(); + } + + /** Responds to configuration change. */ + public void onConfigurationChanged() { + reloadResources(); + } + + private void reloadResources() { + Resources res = mContext.getResources(); + + final String screenEdgeInsetsDpString = res.getString( + R.string.config_defaultPictureInPictureScreenEdgeInsets); + final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty() + ? Size.parseSize(screenEdgeInsetsDpString) + : null; + mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point() + : new Point(dpToPx(screenEdgeInsetsDp.getWidth(), res.getDisplayMetrics()), + dpToPx(screenEdgeInsetsDp.getHeight(), res.getDisplayMetrics())); + } + + public Point getScreenEdgeInsets() { + return mScreenEdgeInsets; + } + + /** + * Returns the inset bounds the PIP window can be visible in. + */ + public Rect getInsetBounds() { + Rect insetBounds = new Rect(); + Rect insets = getDisplayLayout().stableInsets(); + insetBounds.set(insets.left + getScreenEdgeInsets().x, + insets.top + getScreenEdgeInsets().y, + getDisplayLayout().width() - insets.right - getScreenEdgeInsets().x, + getDisplayLayout().height() - insets.bottom - getScreenEdgeInsets().y); + return insetBounds; } - /** Update the display layout. */ + /** Set the display layout. */ public void setDisplayLayout(@NonNull DisplayLayout displayLayout) { mDisplayLayout.set(displayLayout); } @@ -87,5 +131,6 @@ public class PipDisplayLayoutState { pw.println(prefix + TAG); pw.println(innerPrefix + "mDisplayId=" + mDisplayId); pw.println(innerPrefix + "getDisplayBounds=" + getDisplayBounds()); + pw.println(innerPrefix + "mScreenEdgeInsets=" + mScreenEdgeInsets); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 208f9b7432bf..0d55018ba580 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -592,7 +592,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, SplitScreenController split = mSplitScreenOptional.get(); if (split.isTaskInSplitScreen(mTaskInfo.lastParentTaskIdBeforePip)) { split.prepareExitSplitScreen(wct, split.getStageOfTask( - mTaskInfo.lastParentTaskIdBeforePip)); + mTaskInfo.lastParentTaskIdBeforePip), + SplitScreenController.EXIT_REASON_APP_FINISHED); } } mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct, destinationBounds); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index e3d53fc415db..2563d984b793 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -447,7 +447,7 @@ public class PipTransition extends PipTransitionController { // handler if there is a pending PiP animation. final Transitions.TransitionFinishCallback finishCallback = mFinishCallback; mFinishCallback = null; - finishCallback.onTransitionFinished(wct, null /* callback */); + finishCallback.onTransitionFinished(wct); } @Override @@ -456,7 +456,7 @@ public class PipTransition extends PipTransitionController { // for example, when app crashes while in PiP and exit transition has not started mCurrentPipTaskToken = null; if (mFinishCallback == null) return; - mFinishCallback.onTransitionFinished(null /* wct */, null /* callback */); + mFinishCallback.onTransitionFinished(null /* wct */); mFinishCallback = null; mFinishTransaction = null; } @@ -586,7 +586,7 @@ public class PipTransition extends PipTransitionController { final boolean useLocalLeash = activitySc != null; final boolean toFullscreen = pipChange.getEndAbsBounds().equals( mPipBoundsState.getDisplayBounds()); - mFinishCallback = (wct, wctCB) -> { + mFinishCallback = (wct) -> { mPipOrganizer.onExitPipFinished(taskInfo); // TODO(b/286346098): remove the OPEN app flicker completely @@ -610,7 +610,7 @@ public class PipTransition extends PipTransitionController { mPipAnimationController.resetAnimatorState(); finishTransaction.remove(pipLeash); } - finishCallback.onTransitionFinished(wct, wctCB); + finishCallback.onTransitionFinished(wct); }; mFinishTransaction = finishTransaction; @@ -750,7 +750,7 @@ public class PipTransition extends PipTransitionController { finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(), mPipDisplayLayoutState.getDisplayBounds()); mPipOrganizer.onExitPipFinished(taskInfo); - finishCallback.onTransitionFinished(null, null); + finishCallback.onTransitionFinished(null); } /** Whether we should handle the given {@link TransitionInfo} animation as entering PIP. */ @@ -1045,7 +1045,7 @@ public class PipTransition extends PipTransitionController { startTransaction.apply(); mPipOrganizer.onExitPipFinished(taskInfo); - finishCallback.onTransitionFinished(null, null); + finishCallback.onTransitionFinished(null); } private void resetPrevPip(@NonNull TransitionInfo.Change prevPipTaskChange, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 7d82dc17ae72..f396f3fedf64 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -142,7 +142,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb private PipBoundsAlgorithm mPipBoundsAlgorithm; private PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm; private PipBoundsState mPipBoundsState; - private PipSizeSpecHandler mPipSizeSpecHandler; private PipDisplayLayoutState mPipDisplayLayoutState; private PipMotionHelper mPipMotionHelper; private PipTouchHandler mTouchHandler; @@ -406,7 +405,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb PipBoundsAlgorithm pipBoundsAlgorithm, PipKeepClearAlgorithmInterface pipKeepClearAlgorithm, PipBoundsState pipBoundsState, - PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState, PipMotionHelper pipMotionHelper, PipMediaController pipMediaController, @@ -430,7 +428,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb return new PipController(context, shellInit, shellCommandHandler, shellController, displayController, pipAnimationController, pipAppOpsListener, - pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler, + pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipDisplayLayoutState, pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController, windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder, @@ -448,7 +446,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb PipBoundsAlgorithm pipBoundsAlgorithm, PipKeepClearAlgorithmInterface pipKeepClearAlgorithm, @NonNull PipBoundsState pipBoundsState, - PipSizeSpecHandler pipSizeSpecHandler, @NonNull PipDisplayLayoutState pipDisplayLayoutState, PipMotionHelper pipMotionHelper, PipMediaController pipMediaController, @@ -474,7 +471,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipBoundsAlgorithm = pipBoundsAlgorithm; mPipKeepClearAlgorithm = pipKeepClearAlgorithm; mPipBoundsState = pipBoundsState; - mPipSizeSpecHandler = pipSizeSpecHandler; mPipDisplayLayoutState = pipDisplayLayoutState; mPipMotionHelper = pipMotionHelper; mPipTaskOrganizer = pipTaskOrganizer; @@ -711,7 +707,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb // Try to move the PiP window if we have entered PiP mode. if (mPipTransitionState.hasEnteredPip()) { final Rect pipBounds = mPipBoundsState.getBounds(); - final Point edgeInsets = mPipSizeSpecHandler.getScreenEdgeInsets(); + final Point edgeInsets = mPipDisplayLayoutState.getScreenEdgeInsets(); if ((pipBounds.height() + 2 * edgeInsets.y) > (displayBounds.height() / 2)) { // PiP bounds is too big to fit either half, bail early. return; @@ -770,7 +766,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipBoundsAlgorithm.onConfigurationChanged(mContext); mTouchHandler.onConfigurationChanged(); mPipBoundsState.onConfigurationChanged(); - mPipSizeSpecHandler.onConfigurationChanged(); + mPipDisplayLayoutState.onConfigurationChanged(); } @Override @@ -799,6 +795,14 @@ public class PipController implements PipTransitionController.PipTransitionCallb } private void onDisplayChangedUncheck(DisplayLayout layout, boolean saveRestoreSnapFraction) { + if (mPipTransitionState.getInSwipePipToHomeTransition()) { + // If orientation is changed when performing swipe-pip animation, DisplayLayout has + // been updated in startSwipePipToHome. So it is unnecessary to update again when + // receiving onDisplayConfigurationChanged. This also avoids TouchHandler.userResizeTo + // update surface position in different orientation by the intermediate state. The + // desired resize will be done by the end of transition. + return; + } Runnable updateDisplayLayout = () -> { final boolean fromRotation = Transitions.ENABLE_SHELL_TRANSITIONS && mPipDisplayLayoutState.getDisplayLayout().rotation() != layout.rotation(); @@ -1216,7 +1220,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipTaskOrganizer.dump(pw, innerPrefix); mPipBoundsState.dump(pw, innerPrefix); mPipInputConsumer.dump(pw, innerPrefix); - mPipSizeSpecHandler.dump(pw, innerPrefix); mPipDisplayLayoutState.dump(pw, innerPrefix); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java deleted file mode 100644 index c6e5cf25f844..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java +++ /dev/null @@ -1,536 +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.wm.shell.pip.phone; - -import static com.android.wm.shell.pip.PipUtils.dpToPx; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.graphics.Point; -import android.graphics.PointF; -import android.graphics.Rect; -import android.os.SystemProperties; -import android.util.Size; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.wm.shell.R; -import com.android.wm.shell.common.DisplayLayout; -import com.android.wm.shell.pip.PipDisplayLayoutState; - -import java.io.PrintWriter; - -/** - * Acts as a source of truth for appropriate size spec for PIP. - */ -public class PipSizeSpecHandler { - private static final String TAG = PipSizeSpecHandler.class.getSimpleName(); - - @NonNull private final PipDisplayLayoutState mPipDisplayLayoutState; - - private final SizeSpecSource mSizeSpecSourceImpl; - - /** The preferred minimum (and default minimum) size specified by apps. */ - @Nullable private Size mOverrideMinSize; - private int mOverridableMinSize; - - /** Used to store values obtained from resource files. */ - private Point mScreenEdgeInsets; - private float mMinAspectRatioForMinSize; - private float mMaxAspectRatioForMinSize; - private int mDefaultMinSize; - - @NonNull private final Context mContext; - - private interface SizeSpecSource { - /** Returns max size allowed for the PIP window */ - Size getMaxSize(float aspectRatio); - - /** Returns default size for the PIP window */ - Size getDefaultSize(float aspectRatio); - - /** Returns min size allowed for the PIP window */ - Size getMinSize(float aspectRatio); - - /** Returns the adjusted size based on current size and target aspect ratio */ - Size getSizeForAspectRatio(Size size, float aspectRatio); - - /** Updates internal resources on configuration changes */ - default void reloadResources() {} - } - - /** - * Determines PIP window size optimized for large screens and aspect ratios close to 1:1 - */ - private class SizeSpecLargeScreenOptimizedImpl implements SizeSpecSource { - private static final float DEFAULT_OPTIMIZED_ASPECT_RATIO = 9f / 16; - - /** Default and minimum percentages for the PIP size logic. */ - private final float mDefaultSizePercent; - private final float mMinimumSizePercent; - - /** Aspect ratio that the PIP size spec logic optimizes for. */ - private float mOptimizedAspectRatio; - - private SizeSpecLargeScreenOptimizedImpl() { - mDefaultSizePercent = Float.parseFloat(SystemProperties - .get("com.android.wm.shell.pip.phone.def_percentage", "0.6")); - mMinimumSizePercent = Float.parseFloat(SystemProperties - .get("com.android.wm.shell.pip.phone.min_percentage", "0.5")); - } - - @Override - public void reloadResources() { - final Resources res = mContext.getResources(); - - mOptimizedAspectRatio = res.getFloat(R.dimen.config_pipLargeScreenOptimizedAspectRatio); - // make sure the optimized aspect ratio is valid with a default value to fall back to - if (mOptimizedAspectRatio > 1) { - mOptimizedAspectRatio = DEFAULT_OPTIMIZED_ASPECT_RATIO; - } - } - - /** - * Calculates the max size of PIP. - * - * Optimizes for 16:9 aspect ratios, making them take full length of shortest display edge. - * As aspect ratio approaches values close to 1:1, the logic does not let PIP occupy the - * whole screen. A linear function is used to calculate these sizes. - * - * @param aspectRatio aspect ratio of the PIP window - * @return dimensions of the max size of the PIP - */ - @Override - public Size getMaxSize(float aspectRatio) { - final int totalHorizontalPadding = getInsetBounds().left - + (getDisplayBounds().width() - getInsetBounds().right); - final int totalVerticalPadding = getInsetBounds().top - + (getDisplayBounds().height() - getInsetBounds().bottom); - - final int shorterLength = Math.min(getDisplayBounds().width() - totalHorizontalPadding, - getDisplayBounds().height() - totalVerticalPadding); - - int maxWidth, maxHeight; - - // use the optimized max sizing logic only within a certain aspect ratio range - if (aspectRatio >= mOptimizedAspectRatio && aspectRatio <= 1 / mOptimizedAspectRatio) { - // this formula and its derivation is explained in b/198643358#comment16 - maxWidth = Math.round(mOptimizedAspectRatio * shorterLength - + shorterLength * (aspectRatio - mOptimizedAspectRatio) / (1 - + aspectRatio)); - // make sure the max width doesn't go beyond shorter screen length after rounding - maxWidth = Math.min(maxWidth, shorterLength); - maxHeight = Math.round(maxWidth / aspectRatio); - } else { - if (aspectRatio > 1f) { - maxWidth = shorterLength; - maxHeight = Math.round(maxWidth / aspectRatio); - } else { - maxHeight = shorterLength; - maxWidth = Math.round(maxHeight * aspectRatio); - } - } - - return new Size(maxWidth, maxHeight); - } - - /** - * Decreases the dimensions by a percentage relative to max size to get default size. - * - * @param aspectRatio aspect ratio of the PIP window - * @return dimensions of the default size of the PIP - */ - @Override - public Size getDefaultSize(float aspectRatio) { - Size minSize = this.getMinSize(aspectRatio); - - if (mOverrideMinSize != null) { - return minSize; - } - - Size maxSize = this.getMaxSize(aspectRatio); - - int defaultWidth = Math.max(Math.round(maxSize.getWidth() * mDefaultSizePercent), - minSize.getWidth()); - int defaultHeight = Math.round(defaultWidth / aspectRatio); - - return new Size(defaultWidth, defaultHeight); - } - - /** - * Decreases the dimensions by a certain percentage relative to max size to get min size. - * - * @param aspectRatio aspect ratio of the PIP window - * @return dimensions of the min size of the PIP - */ - @Override - public Size getMinSize(float aspectRatio) { - // if there is an overridden min size provided, return that - if (mOverrideMinSize != null) { - return adjustOverrideMinSizeToAspectRatio(aspectRatio); - } - - Size maxSize = this.getMaxSize(aspectRatio); - - int minWidth = Math.round(maxSize.getWidth() * mMinimumSizePercent); - int minHeight = Math.round(maxSize.getHeight() * mMinimumSizePercent); - - // make sure the calculated min size is not smaller than the allowed default min size - if (aspectRatio > 1f) { - minHeight = Math.max(minHeight, mDefaultMinSize); - minWidth = Math.round(minHeight * aspectRatio); - } else { - minWidth = Math.max(minWidth, mDefaultMinSize); - minHeight = Math.round(minWidth / aspectRatio); - } - return new Size(minWidth, minHeight); - } - - /** - * Returns the size for target aspect ratio making sure new size conforms with the rules. - * - * <p>Recalculates the dimensions such that the target aspect ratio is achieved, while - * maintaining the same maximum size to current size ratio. - * - * @param size current size - * @param aspectRatio target aspect ratio - */ - @Override - public Size getSizeForAspectRatio(Size size, float aspectRatio) { - float currAspectRatio = (float) size.getWidth() / size.getHeight(); - - // getting the percentage of the max size that current size takes - Size currentMaxSize = getMaxSize(currAspectRatio); - float currentPercent = (float) size.getWidth() / currentMaxSize.getWidth(); - - // getting the max size for the target aspect ratio - Size updatedMaxSize = getMaxSize(aspectRatio); - - int width = Math.round(updatedMaxSize.getWidth() * currentPercent); - int height = Math.round(updatedMaxSize.getHeight() * currentPercent); - - // adjust the dimensions if below allowed min edge size - if (width < getMinEdgeSize() && aspectRatio <= 1) { - width = getMinEdgeSize(); - height = Math.round(width / aspectRatio); - } else if (height < getMinEdgeSize() && aspectRatio > 1) { - height = getMinEdgeSize(); - width = Math.round(height * aspectRatio); - } - - // reduce the dimensions of the updated size to the calculated percentage - return new Size(width, height); - } - } - - private class SizeSpecDefaultImpl implements SizeSpecSource { - private float mDefaultSizePercent; - private float mMinimumSizePercent; - - @Override - public void reloadResources() { - final Resources res = mContext.getResources(); - - mMaxAspectRatioForMinSize = res.getFloat( - R.dimen.config_pictureInPictureAspectRatioLimitForMinSize); - mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize; - - mDefaultSizePercent = res.getFloat(R.dimen.config_pictureInPictureDefaultSizePercent); - mMinimumSizePercent = res.getFraction(R.fraction.config_pipShortestEdgePercent, 1, 1); - } - - @Override - public Size getMaxSize(float aspectRatio) { - final int shorterLength = Math.min(getDisplayBounds().width(), - getDisplayBounds().height()); - - final int totalHorizontalPadding = getInsetBounds().left - + (getDisplayBounds().width() - getInsetBounds().right); - final int totalVerticalPadding = getInsetBounds().top - + (getDisplayBounds().height() - getInsetBounds().bottom); - - final int maxWidth, maxHeight; - - if (aspectRatio > 1f) { - maxWidth = (int) Math.max(getDefaultSize(aspectRatio).getWidth(), - shorterLength - totalHorizontalPadding); - maxHeight = (int) (maxWidth / aspectRatio); - } else { - maxHeight = (int) Math.max(getDefaultSize(aspectRatio).getHeight(), - shorterLength - totalVerticalPadding); - maxWidth = (int) (maxHeight * aspectRatio); - } - - return new Size(maxWidth, maxHeight); - } - - @Override - public Size getDefaultSize(float aspectRatio) { - if (mOverrideMinSize != null) { - return this.getMinSize(aspectRatio); - } - - final int smallestDisplaySize = Math.min(getDisplayBounds().width(), - getDisplayBounds().height()); - final int minSize = (int) Math.max(getMinEdgeSize(), - smallestDisplaySize * mDefaultSizePercent); - - final int width; - final int height; - - if (aspectRatio <= mMinAspectRatioForMinSize - || aspectRatio > mMaxAspectRatioForMinSize) { - // Beyond these points, we can just use the min size as the shorter edge - if (aspectRatio <= 1) { - // Portrait, width is the minimum size - width = minSize; - height = Math.round(width / aspectRatio); - } else { - // Landscape, height is the minimum size - height = minSize; - width = Math.round(height * aspectRatio); - } - } else { - // Within these points, ensure that the bounds fit within the radius of the limits - // at the points - final float widthAtMaxAspectRatioForMinSize = mMaxAspectRatioForMinSize * minSize; - final float radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize); - height = (int) Math.round(Math.sqrt((radius * radius) - / (aspectRatio * aspectRatio + 1))); - width = Math.round(height * aspectRatio); - } - - return new Size(width, height); - } - - @Override - public Size getMinSize(float aspectRatio) { - if (mOverrideMinSize != null) { - return adjustOverrideMinSizeToAspectRatio(aspectRatio); - } - - final int shorterLength = Math.min(getDisplayBounds().width(), - getDisplayBounds().height()); - final int minWidth, minHeight; - - if (aspectRatio > 1f) { - minWidth = (int) Math.min(getDefaultSize(aspectRatio).getWidth(), - shorterLength * mMinimumSizePercent); - minHeight = (int) (minWidth / aspectRatio); - } else { - minHeight = (int) Math.min(getDefaultSize(aspectRatio).getHeight(), - shorterLength * mMinimumSizePercent); - minWidth = (int) (minHeight * aspectRatio); - } - - return new Size(minWidth, minHeight); - } - - @Override - public Size getSizeForAspectRatio(Size size, float aspectRatio) { - final int smallestSize = Math.min(size.getWidth(), size.getHeight()); - final int minSize = Math.max(getMinEdgeSize(), smallestSize); - - final int width; - final int height; - if (aspectRatio <= 1) { - // Portrait, width is the minimum size. - width = minSize; - height = Math.round(width / aspectRatio); - } else { - // Landscape, height is the minimum size - height = minSize; - width = Math.round(height * aspectRatio); - } - - return new Size(width, height); - } - } - - public PipSizeSpecHandler(Context context, PipDisplayLayoutState pipDisplayLayoutState) { - mContext = context; - mPipDisplayLayoutState = pipDisplayLayoutState; - - // choose between two implementations of size spec logic - if (supportsPipSizeLargeScreen()) { - mSizeSpecSourceImpl = new SizeSpecLargeScreenOptimizedImpl(); - } else { - mSizeSpecSourceImpl = new SizeSpecDefaultImpl(); - } - - reloadResources(); - } - - /** Reloads the resources */ - public void onConfigurationChanged() { - reloadResources(); - } - - private void reloadResources() { - final Resources res = mContext.getResources(); - - mDefaultMinSize = res.getDimensionPixelSize( - R.dimen.default_minimal_size_pip_resizable_task); - mOverridableMinSize = res.getDimensionPixelSize( - R.dimen.overridable_minimal_size_pip_resizable_task); - - final String screenEdgeInsetsDpString = res.getString( - R.string.config_defaultPictureInPictureScreenEdgeInsets); - final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty() - ? Size.parseSize(screenEdgeInsetsDpString) - : null; - mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point() - : new Point(dpToPx(screenEdgeInsetsDp.getWidth(), res.getDisplayMetrics()), - dpToPx(screenEdgeInsetsDp.getHeight(), res.getDisplayMetrics())); - - // update the internal resources of the size spec source's stub - mSizeSpecSourceImpl.reloadResources(); - } - - @NonNull - private Rect getDisplayBounds() { - return mPipDisplayLayoutState.getDisplayBounds(); - } - - public Point getScreenEdgeInsets() { - return mScreenEdgeInsets; - } - - /** - * Returns the inset bounds the PIP window can be visible in. - */ - public Rect getInsetBounds() { - Rect insetBounds = new Rect(); - DisplayLayout displayLayout = mPipDisplayLayoutState.getDisplayLayout(); - Rect insets = displayLayout.stableInsets(); - insetBounds.set(insets.left + mScreenEdgeInsets.x, - insets.top + mScreenEdgeInsets.y, - displayLayout.width() - insets.right - mScreenEdgeInsets.x, - displayLayout.height() - insets.bottom - mScreenEdgeInsets.y); - return insetBounds; - } - - /** Sets the preferred size of PIP as specified by the activity in PIP mode. */ - public void setOverrideMinSize(@Nullable Size overrideMinSize) { - mOverrideMinSize = overrideMinSize; - } - - /** Returns the preferred minimal size specified by the activity in PIP. */ - @Nullable - public Size getOverrideMinSize() { - if (mOverrideMinSize != null - && (mOverrideMinSize.getWidth() < mOverridableMinSize - || mOverrideMinSize.getHeight() < mOverridableMinSize)) { - return new Size(mOverridableMinSize, mOverridableMinSize); - } - - return mOverrideMinSize; - } - - /** Returns the minimum edge size of the override minimum size, or 0 if not set. */ - public int getOverrideMinEdgeSize() { - if (mOverrideMinSize == null) return 0; - return Math.min(getOverrideMinSize().getWidth(), getOverrideMinSize().getHeight()); - } - - public int getMinEdgeSize() { - return mOverrideMinSize == null ? mDefaultMinSize : getOverrideMinEdgeSize(); - } - - /** - * Returns the size for the max size spec. - */ - public Size getMaxSize(float aspectRatio) { - return mSizeSpecSourceImpl.getMaxSize(aspectRatio); - } - - /** - * Returns the size for the default size spec. - */ - public Size getDefaultSize(float aspectRatio) { - return mSizeSpecSourceImpl.getDefaultSize(aspectRatio); - } - - /** - * Returns the size for the min size spec. - */ - public Size getMinSize(float aspectRatio) { - return mSizeSpecSourceImpl.getMinSize(aspectRatio); - } - - /** - * Returns the adjusted size so that it conforms to the given aspectRatio. - * - * @param size current size - * @param aspectRatio target aspect ratio - */ - public Size getSizeForAspectRatio(@NonNull Size size, float aspectRatio) { - if (size.equals(mOverrideMinSize)) { - return adjustOverrideMinSizeToAspectRatio(aspectRatio); - } - - return mSizeSpecSourceImpl.getSizeForAspectRatio(size, aspectRatio); - } - - /** - * Returns the adjusted overridden min size if it is set; otherwise, returns null. - * - * <p>Overridden min size needs to be adjusted in its own way while making sure that the target - * aspect ratio is maintained - * - * @param aspectRatio target aspect ratio - */ - @Nullable - @VisibleForTesting - Size adjustOverrideMinSizeToAspectRatio(float aspectRatio) { - if (mOverrideMinSize == null) { - return null; - } - final Size size = getOverrideMinSize(); - final float sizeAspectRatio = size.getWidth() / (float) size.getHeight(); - if (sizeAspectRatio > aspectRatio) { - // Size is wider, fix the width and increase the height - return new Size(size.getWidth(), (int) (size.getWidth() / aspectRatio)); - } else { - // Size is taller, fix the height and adjust the width. - return new Size((int) (size.getHeight() * aspectRatio), size.getHeight()); - } - } - - @VisibleForTesting - boolean supportsPipSizeLargeScreen() { - // TODO(b/271468706): switch Tv to having a dedicated SizeSpecSource once the SizeSpecSource - // can be injected - return SystemProperties - .getBoolean("persist.wm.debug.enable_pip_size_large_screen", true) && !isTv(); - } - - private boolean isTv() { - return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); - } - - /** Dumps internal state. */ - public void dump(PrintWriter pw, String prefix) { - final String innerPrefix = prefix + " "; - pw.println(prefix + TAG); - pw.println(innerPrefix + "mSizeSpecSourceImpl=" + mSizeSpecSourceImpl); - pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize); - pw.println(innerPrefix + "mScreenEdgeInsets=" + mScreenEdgeInsets); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 372ade333218..3e95498a832b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -51,6 +51,7 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; @@ -85,7 +86,7 @@ public class PipTouchHandler { private final Context mContext; private final PipBoundsAlgorithm mPipBoundsAlgorithm; @NonNull private final PipBoundsState mPipBoundsState; - @NonNull private final PipSizeSpecHandler mPipSizeSpecHandler; + @NonNull private final SizeSpecSource mSizeSpecSource; private final PipUiEventLogger mPipUiEventLogger; private final PipDismissTargetHandler mPipDismissTargetHandler; private final PipTaskOrganizer mPipTaskOrganizer; @@ -179,7 +180,7 @@ public class PipTouchHandler { PhonePipMenuController menuController, PipBoundsAlgorithm pipBoundsAlgorithm, @NonNull PipBoundsState pipBoundsState, - @NonNull PipSizeSpecHandler pipSizeSpecHandler, + @NonNull SizeSpecSource sizeSpecSource, PipTaskOrganizer pipTaskOrganizer, PipMotionHelper pipMotionHelper, FloatingContentCoordinator floatingContentCoordinator, @@ -190,7 +191,7 @@ public class PipTouchHandler { mAccessibilityManager = context.getSystemService(AccessibilityManager.class); mPipBoundsAlgorithm = pipBoundsAlgorithm; mPipBoundsState = pipBoundsState; - mPipSizeSpecHandler = pipSizeSpecHandler; + mSizeSpecSource = sizeSpecSource; mPipTaskOrganizer = pipTaskOrganizer; mMenuController = menuController; mPipUiEventLogger = pipUiEventLogger; @@ -413,7 +414,7 @@ public class PipTouchHandler { // Calculate the expanded size float aspectRatio = (float) normalBounds.width() / normalBounds.height(); - Size expandedSize = mPipSizeSpecHandler.getDefaultSize(aspectRatio); + Size expandedSize = mSizeSpecSource.getDefaultSize(aspectRatio); mPipBoundsState.setExpandedBounds( new Rect(0, 0, expandedSize.getWidth(), expandedSize.getHeight())); Rect expandedMovementBounds = new Rect(); @@ -517,10 +518,10 @@ public class PipTouchHandler { private void updatePinchResizeSizeConstraints(float aspectRatio) { final int minWidth, minHeight, maxWidth, maxHeight; - minWidth = mPipSizeSpecHandler.getMinSize(aspectRatio).getWidth(); - minHeight = mPipSizeSpecHandler.getMinSize(aspectRatio).getHeight(); - maxWidth = mPipSizeSpecHandler.getMaxSize(aspectRatio).getWidth(); - maxHeight = mPipSizeSpecHandler.getMaxSize(aspectRatio).getHeight(); + minWidth = mSizeSpecSource.getMinSize(aspectRatio).getWidth(); + minHeight = mSizeSpecSource.getMinSize(aspectRatio).getHeight(); + maxWidth = mSizeSpecSource.getMaxSize(aspectRatio).getWidth(); + maxHeight = mSizeSpecSource.getMaxSize(aspectRatio).getHeight(); mPipResizeGestureHandler.updateMinSize(minWidth, minHeight); mPipResizeGestureHandler.updateMaxSize(maxWidth, maxHeight); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java index 825b96921a22..cd58ff4f5154 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java @@ -36,10 +36,11 @@ import androidx.annotation.NonNull; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.pip.PipBoundsAlgorithm; +import com.android.wm.shell.pip.PipDisplayLayoutState; import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface; import com.android.wm.shell.pip.PipSnapAlgorithm; -import com.android.wm.shell.pip.phone.PipSizeSpecHandler; import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement; import com.android.wm.shell.protolog.ShellProtoLogGroup; @@ -62,9 +63,10 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { public TvPipBoundsAlgorithm(Context context, @NonNull TvPipBoundsState tvPipBoundsState, @NonNull PipSnapAlgorithm pipSnapAlgorithm, - @NonNull PipSizeSpecHandler pipSizeSpecHandler) { + @NonNull PipDisplayLayoutState pipDisplayLayoutState, + @NonNull SizeSpecSource sizeSpecSource) { super(context, tvPipBoundsState, pipSnapAlgorithm, - new PipKeepClearAlgorithmInterface() {}, pipSizeSpecHandler); + new PipKeepClearAlgorithmInterface() {}, pipDisplayLayoutState, sizeSpecSource); this.mTvPipBoundsState = tvPipBoundsState; this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm(); reloadResources(context); @@ -291,7 +293,7 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { expandedSize = mTvPipBoundsState.getTvExpandedSize(); } else { int maxHeight = displayLayout.height() - - (2 * mPipSizeSpecHandler.getScreenEdgeInsets().y) + - (2 * mPipDisplayLayoutState.getScreenEdgeInsets().y) - pipDecorations.top - pipDecorations.bottom; float aspectRatioHeight = mFixedExpandedWidthInPx / expandedRatio; @@ -311,7 +313,7 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { expandedSize = mTvPipBoundsState.getTvExpandedSize(); } else { int maxWidth = displayLayout.width() - - (2 * mPipSizeSpecHandler.getScreenEdgeInsets().x) + - (2 * mPipDisplayLayoutState.getScreenEdgeInsets().x) - pipDecorations.left - pipDecorations.right; float aspectRatioWidth = mFixedExpandedHeightInPx * expandedRatio; if (maxWidth > aspectRatioWidth) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java index 3f3951a2d4f0..d11f4d59a41d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java @@ -29,10 +29,10 @@ import android.util.Size; import android.view.Gravity; import android.view.View; +import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipDisplayLayoutState; -import com.android.wm.shell.pip.phone.PipSizeSpecHandler; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -76,9 +76,9 @@ public class TvPipBoundsState extends PipBoundsState { private Insets mPipMenuTemporaryDecorInsets = Insets.NONE; public TvPipBoundsState(@NonNull Context context, - @NonNull PipSizeSpecHandler pipSizeSpecHandler, + @NonNull SizeSpecSource sizeSpecSource, @NonNull PipDisplayLayoutState pipDisplayLayoutState) { - super(context, pipSizeSpecHandler, pipDisplayLayoutState); + super(context, sizeSpecSource, pipDisplayLayoutState); mContext = context; updateDefaultGravity(); mTvPipGravity = mDefaultGravity; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 88a81fc291b2..a11d9528a170 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -771,7 +771,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { Slog.e(TAG, "Error sending appeared tasks to recents animation", e); } } - finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + finishCallback.onTransitionFinished(null /* wct */); } /** For now, just set-up a jump-cut to the new activity. */ @@ -937,7 +937,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } } cleanUp(); - finishCB.onTransitionFinished(wct.isEmpty() ? null : wct, null /* wctCB */); + finishCB.onTransitionFinished(wct.isEmpty() ? null : wct); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 7699b4bfd13a..5fa26542ee07 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -423,8 +423,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, * transition. */ public void prepareExitSplitScreen(WindowContainerTransaction wct, - @StageType int stageToTop) { + @StageType int stageToTop, @ExitReason int reason) { mStageCoordinator.prepareExitSplitScreen(stageToTop, wct); + mStageCoordinator.clearSplitPairedInRecents(reason); } public void enterSplitScreen(int taskId, boolean leftOrTop) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index d21f8a48e62a..99be5b8ee861 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -43,7 +43,6 @@ import android.window.RemoteTransition; import android.window.TransitionInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; -import android.window.WindowContainerTransactionCallback; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.TransactionPool; @@ -109,7 +108,7 @@ class SplitScreenTransitions { if (pendingTransition.mCanceled) { // The pending transition was canceled, so skip playing animation. startTransaction.apply(); - onFinish(null /* wct */, null /* wctCB */); + onFinish(null /* wct */); return; } @@ -211,7 +210,7 @@ class SplitScreenTransitions { } } t.apply(); - onFinish(null /* wct */, null /* wctCB */); + onFinish(null /* wct */); } /** Play animation for drag divider dismiss transition. */ @@ -238,7 +237,7 @@ class SplitScreenTransitions { mAnimations.remove(va); if (animated) { mTransitions.getMainExecutor().execute(() -> { - onFinish(null /* wct */, null /* wctCB */); + onFinish(null /* wct */); }); } }); @@ -250,7 +249,7 @@ class SplitScreenTransitions { } } startTransaction.apply(); - onFinish(null /* wct */, null /* wctCB */); + onFinish(null /* wct */); } /** Play animation for resize transition. */ @@ -283,7 +282,7 @@ class SplitScreenTransitions { mAnimations.remove(va); if (animated) { mTransitions.getMainExecutor().execute(() -> { - onFinish(null /* wct */, null /* wctCB */); + onFinish(null /* wct */); }); } }); @@ -291,7 +290,7 @@ class SplitScreenTransitions { } startTransaction.apply(); - onFinish(null /* wct */, null /* wctCB */); + onFinish(null /* wct */); } boolean isPendingTransition(IBinder transition) { @@ -391,7 +390,7 @@ class SplitScreenTransitions { if (mPendingResize != null) { mPendingResize.cancel(null); mAnimations.clear(); - onFinish(null /* wct */, null /* wctCB */); + onFinish(null /* wct */); } IBinder transition = mTransitions.startTransition(TRANSIT_CHANGE, wct, handler); @@ -450,7 +449,7 @@ class SplitScreenTransitions { } } - void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) { + void onFinish(WindowContainerTransaction wct) { if (!mAnimations.isEmpty()) return; if (wct == null) wct = new WindowContainerTransaction(); @@ -470,7 +469,7 @@ class SplitScreenTransitions { mOnFinish.run(); if (mFinishCallback != null) { - mFinishCallback.onTransitionFinished(wct /* wct */, wctCB /* wctCB */); + mFinishCallback.onTransitionFinished(wct /* wct */); mFinishCallback = null; } } @@ -495,7 +494,7 @@ class SplitScreenTransitions { mTransactionPool.release(transaction); mTransitions.getMainExecutor().execute(() -> { mAnimations.remove(va); - onFinish(null /* wct */, null /* wctCB */); + onFinish(null /* wct */); }); } }); 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 7d62f58014f0..3758b6890f48 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 @@ -83,7 +83,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; import android.content.pm.ShortcutInfo; -import android.content.res.Configuration; import android.graphics.Rect; import android.hardware.devicestate.DeviceStateManager; import android.os.Bundle; @@ -1494,7 +1493,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } - private void clearSplitPairedInRecents(@ExitReason int exitReason) { + void clearSplitPairedInRecents(@ExitReason int exitReason) { if (!shouldBreakPairedTaskInRecents(exitReason) || !mShouldUpdateRecents) return; mRecentTasks.ifPresent(recentTasks -> { @@ -1779,8 +1778,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRootTaskInfo = taskInfo; if (mSplitLayout != null && mSplitLayout.updateConfiguration(mRootTaskInfo.configuration) - && mMainStage.isActive() - && !ENABLE_SHELL_TRANSITIONS) { + && mMainStage.isActive()) { // Clear the divider remote animating flag as the divider will be re-rendered to apply // the new rotation config. mIsDividerRemoteAnimating = false; @@ -2218,20 +2216,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mDisplayController.addDisplayChangingController(this::onDisplayChange); } - @Override - public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { - if (displayId != DEFAULT_DISPLAY) { - return; - } - if (mSplitLayout != null && mSplitLayout.isDensityChanged(newConfig.densityDpi) - && mMainStage.isActive() - && mSplitLayout.updateConfiguration(newConfig) - && ENABLE_SHELL_TRANSITIONS) { - mSplitLayout.update(null /* t */); - onLayoutSizeChanged(mSplitLayout); - } - } - /** * Update surfaces of the split screen layout based on the current state * @param transaction to write the updates to @@ -2736,7 +2720,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) { // Open to side should only be used when split already active and foregorund. if (mainChild == null && sideChild == null) { - Log.w(TAG, "Launched a task in split, but didn't receive any task in transition."); + Log.w(TAG, splitFailureMessage("startPendingEnterAnimation", + "Launched a task in split, but didn't receive any task in transition.")); // This should happen when the target app is already on front, so just cancel. mSplitTransitions.mPendingEnter.cancel(null); return true; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java index a743e99d6954..adae21b20b3c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java @@ -94,9 +94,11 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { mShellExecutor = organizer.getExecutor(); mSyncQueue = syncQueue; mTaskViewTransitions = taskViewTransitions; - if (mTaskViewTransitions != null) { - mTaskViewTransitions.addTaskView(this); - } + mShellExecutor.execute(() -> { + if (mTaskViewTransitions != null) { + mTaskViewTransitions.addTaskView(this); + } + }); mGuard.open("release"); } @@ -225,10 +227,10 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { } private void performRelease() { - if (mTaskViewTransitions != null) { - mTaskViewTransitions.removeTaskView(this); - } mShellExecutor.execute(() -> { + if (mTaskViewTransitions != null) { + mTaskViewTransitions.removeTaskView(this); + } mTaskOrganizer.removeListener(this); resetTaskInfo(); }); @@ -410,9 +412,12 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { if (mTaskToken == null) { return; } - // Sync Transactions can't operate simultaneously with shell transition collection. + if (isUsingShellTransitions()) { - mTaskViewTransitions.setTaskBounds(this, boundsOnScreen); + mShellExecutor.execute(() -> { + // Sync Transactions can't operate simultaneously with shell transition collection. + mTaskViewTransitions.setTaskBounds(this, boundsOnScreen); + }); return; } @@ -430,9 +435,11 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { Slog.w(TAG, "Trying to remove a task that was never added? (no taskToken)"); return; } - WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.removeTask(mTaskToken); - mTaskViewTransitions.closeTaskView(wct, this); + mShellExecutor.execute(() -> { + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.removeTask(mTaskToken); + mTaskViewTransitions.closeTaskView(wct, this); + }); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java index daf8be60651a..e03f82526bdb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java @@ -390,7 +390,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { } // No animation, just show it immediately. startTransaction.apply(); - finishCallback.onTransitionFinished(wct, null /* wctCB */); + finishCallback.onTransitionFinished(wct); startNextTransition(); return true; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index 052af3af98cc..986560bd6053 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -40,7 +40,6 @@ import android.view.WindowManager; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; -import android.window.WindowContainerTransactionCallback; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.split.SplitScreenUtils; @@ -124,14 +123,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, mTransition = transition; } - void joinFinishArgs(WindowContainerTransaction wct, - WindowContainerTransactionCallback wctCB) { - if (wctCB != null) { - // Technically can probably support 1, but don't want to encourage CB usage since - // it creates instabliity, so just throw. - throw new IllegalArgumentException("Can't mix transitions that require finish" - + " sync callback"); - } + void joinFinishArgs(WindowContainerTransaction wct) { if (wct != null) { if (mFinishWCT == null) { mFinishWCT = wct; @@ -389,12 +381,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, info.getChanges().remove(i); } } - Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> { + Transitions.TransitionFinishCallback finishCB = (wct) -> { --mixed.mInFlightSubAnimations; - mixed.joinFinishArgs(wct, wctCB); + mixed.joinFinishArgs(wct); if (mixed.mInFlightSubAnimations > 0) return; mActiveTransitions.remove(mixed); - finishCallback.onTransitionFinished(mixed.mFinishWCT, wctCB); + finishCallback.onTransitionFinished(mixed.mFinishWCT); }; if (pipChange == null) { if (mixed.mLeftoversHandler != null) { @@ -461,15 +453,15 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, return false; } final boolean isGoingHome = homeIsOpening; - Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> { + Transitions.TransitionFinishCallback finishCB = (wct) -> { --mixed.mInFlightSubAnimations; - mixed.joinFinishArgs(wct, wctCB); + mixed.joinFinishArgs(wct); if (mixed.mInFlightSubAnimations > 0) return; mActiveTransitions.remove(mixed); if (isGoingHome) { mSplitHandler.onTransitionAnimationComplete(); } - finishCallback.onTransitionFinished(mixed.mFinishWCT, wctCB); + finishCallback.onTransitionFinished(mixed.mFinishWCT); }; if (isGoingHome || mSplitHandler.getSplitItemPosition(pipChange.getLastParent()) != SPLIT_POSITION_UNDEFINED) { @@ -586,12 +578,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, // We need to split the transition into 2 parts: the split part and the display part. mixed.mInFlightSubAnimations = 2; - Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> { + Transitions.TransitionFinishCallback finishCB = (wct) -> { --mixed.mInFlightSubAnimations; - mixed.joinFinishArgs(wct, wctCB); + mixed.joinFinishArgs(wct); if (mixed.mInFlightSubAnimations > 0) return; mActiveTransitions.remove(mixed); - finishCallback.onTransitionFinished(mixed.mFinishWCT, null /* wctCB */); + finishCallback.onTransitionFinished(mixed.mFinishWCT); }; // Dispatch the display change. This will most-likely be taken by the default handler. @@ -614,7 +606,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, @NonNull Transitions.TransitionFinishCallback finishCallback) { // Split-screen is only interested in the recents transition finishing (and merging), so // just wrap finish and start recents animation directly. - Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> { + Transitions.TransitionFinishCallback finishCB = (wct) -> { mixed.mInFlightSubAnimations = 0; mActiveTransitions.remove(mixed); // If pair-to-pair switching, the post-recents clean-up isn't needed. @@ -626,7 +618,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, mSplitHandler.onRecentsPairToPairAnimationFinish(wct); } mSplitHandler.onTransitionAnimationComplete(); - finishCallback.onTransitionFinished(wct, wctCB); + finishCallback.onTransitionFinished(wct); }; mixed.mInFlightSubAnimations = 1; mSplitHandler.onRecentsInSplitAnimationStart(info); @@ -644,11 +636,11 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - final Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> { + final Transitions.TransitionFinishCallback finishCB = (wct) -> { mixed.mInFlightSubAnimations--; if (mixed.mInFlightSubAnimations == 0) { mActiveTransitions.remove(mixed); - finishCallback.onTransitionFinished(wct, wctCB); + finishCallback.onTransitionFinished(wct); } }; mixed.mInFlightSubAnimations++; @@ -693,11 +685,11 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - final Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> { + final Transitions.TransitionFinishCallback finishCB = (wct) -> { mixed.mInFlightSubAnimations--; if (mixed.mInFlightSubAnimations > 0) return; mActiveTransitions.remove(mixed); - finishCallback.onTransitionFinished(wct, wctCB); + finishCallback.onTransitionFinished(wct); }; mixed.mInFlightSubAnimations = 1; // Sync pip state. 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 dc78c9b139f9..7df658e6c9db 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 @@ -300,7 +300,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { // immediately finishes since there is no animation for screen-wake. if (info.getType() == WindowManager.TRANSIT_WAKE && !info.isKeyguardGoingAway()) { startTransaction.apply(); - finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + finishCallback.onTransitionFinished(null /* wct */); return true; } @@ -309,7 +309,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { || (info.getFlags() & WindowManager.TRANSIT_FLAG_INVISIBLE) != 0) { startTransaction.apply(); finishTransaction.apply(); - finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + finishCallback.onTransitionFinished(null /* wct */); return true; } @@ -323,7 +323,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { final Runnable onAnimFinish = () -> { if (!animations.isEmpty()) return; mAnimations.remove(transition); - finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + finishCallback.onTransitionFinished(null /* wct */); }; final List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks = diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java index 4e3d220f1ea2..fab2dd2bf3e1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java @@ -68,7 +68,7 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { final IBinder.DeathRecipient remoteDied = () -> { Log.e(Transitions.TAG, "Remote transition died, finishing"); mMainExecutor.execute( - () -> finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */)); + () -> finishCallback.onTransitionFinished(null /* wct */)); }; IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() { @Override @@ -81,7 +81,7 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { finishTransaction.merge(sct); } mMainExecutor.execute(() -> { - finishCallback.onTransitionFinished(wct, null /* wctCB */); + finishCallback.onTransitionFinished(wct); }); } }; @@ -104,7 +104,7 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { if (mRemote.asBinder() != null) { mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */); } - finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + finishCallback.onTransitionFinished(null /* wct */); } return true; } @@ -122,8 +122,7 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { // remote applied the transaction, but applying twice will break surfaceflinger // so just assume the worst-case and clear the local transaction. t.clear(); - mMainExecutor.execute( - () -> finishCallback.onTransitionFinished(wct, null /* wctCB */)); + mMainExecutor.execute(() -> finishCallback.onTransitionFinished(wct)); } }; try { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java index c22cc6fbea8f..bbf67a6155d7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java @@ -133,7 +133,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { } mMainExecutor.execute(() -> { mRequestedRemotes.remove(transition); - finishCallback.onTransitionFinished(wct, null /* wctCB */); + finishCallback.onTransitionFinished(wct); }); } }; @@ -153,8 +153,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { Log.e(Transitions.TAG, "Error running remote transition.", e); unhandleDeath(remote.asBinder(), finishCallback); mRequestedRemotes.remove(transition); - mMainExecutor.execute( - () -> finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */)); + mMainExecutor.execute(() -> finishCallback.onTransitionFinished(null /* wct */)); } return true; } @@ -210,7 +209,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { + "that the mergeTarget's RemoteTransition impl erroneously " + "accepted/ran the merge request after finishing the mergeTarget"); } - finishCallback.onTransitionFinished(wct, null /* wctCB */); + finishCallback.onTransitionFinished(wct); }); } }; @@ -316,8 +315,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { } } for (int i = mPendingFinishCallbacks.size() - 1; i >= 0; --i) { - mPendingFinishCallbacks.get(i).onTransitionFinished( - null /* wct */, null /* wctCB */); + mPendingFinishCallbacks.get(i).onTransitionFinished(null /* wct */); } mPendingFinishCallbacks.clear(); }); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java index d2795959494a..87c438a5b37d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java @@ -43,7 +43,7 @@ class SleepHandler implements Transitions.TransitionHandler { @NonNull Transitions.TransitionFinishCallback finishCallback) { mSleepTransitions.remove(transition); startTransaction.apply(); - finishCallback.onTransitionFinished(null, null); + finishCallback.onTransitionFinished(null); return true; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index e2dce88d5958..b4d0a31dc8c1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -62,7 +62,6 @@ import android.window.TransitionInfo; import android.window.TransitionMetrics; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; -import android.window.WindowContainerTransactionCallback; import android.window.WindowOrganizer; import androidx.annotation.BinderThread; @@ -829,7 +828,7 @@ public class Transitions implements RemoteCallable<Transitions>, ready.mStartT.apply(); } // finish now since there's nothing to animate. Calls back into processReadyQueue - onFinish(ready, null, null); + onFinish(ready, null); return; } playTransition(ready); @@ -849,7 +848,7 @@ public class Transitions implements RemoteCallable<Transitions>, + " in case they can be merged", ready, playing); mTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId()); playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT, - playing.mToken, (wct, cb) -> onMerged(playing, ready)); + playing.mToken, (wct) -> onMerged(playing, ready)); } private void onMerged(@NonNull ActiveTransition playing, @NonNull ActiveTransition merged) { @@ -899,7 +898,7 @@ public class Transitions implements RemoteCallable<Transitions>, ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s", active.mHandler); boolean consumed = active.mHandler.startAnimation(active.mToken, active.mInfo, - active.mStartT, active.mFinishT, (wct, cb) -> onFinish(active, wct, cb)); + active.mStartT, active.mFinishT, (wct) -> onFinish(active, wct)); if (consumed) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler"); mTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler); @@ -908,7 +907,7 @@ public class Transitions implements RemoteCallable<Transitions>, } // Otherwise give every other handler a chance active.mHandler = dispatchTransition(active.mToken, active.mInfo, active.mStartT, - active.mFinishT, (wct, cb) -> onFinish(active, wct, cb), active.mHandler); + active.mFinishT, (wct) -> onFinish(active, wct), active.mHandler); } /** @@ -985,8 +984,7 @@ public class Transitions implements RemoteCallable<Transitions>, } private void onFinish(ActiveTransition active, - @Nullable WindowContainerTransaction wct, - @Nullable WindowContainerTransactionCallback wctCB) { + @Nullable WindowContainerTransaction wct) { final Track track = mTracks.get(active.getTrack()); if (track.mActiveTransition != active) { Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or " @@ -1035,11 +1033,11 @@ public class Transitions implements RemoteCallable<Transitions>, // Now perform all the finish callbacks (starting with the playing one and then all the // transitions merged into it). releaseSurfaces(active.mInfo); - mOrganizer.finishTransition(active.mToken, wct, wctCB); + mOrganizer.finishTransition(active.mToken, wct); if (active.mMerged != null) { for (int iM = 0; iM < active.mMerged.size(); ++iM) { ActiveTransition merged = active.mMerged.get(iM); - mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */); + mOrganizer.finishTransition(merged.mToken, null /* wct */); releaseSurfaces(merged.mInfo); } active.mMerged.clear(); @@ -1178,7 +1176,7 @@ public class Transitions implements RemoteCallable<Transitions>, forceFinish.mHandler.onTransitionConsumed( forceFinish.mToken, true /* aborted */, null /* finishTransaction */); } - onFinish(forceFinish, null, null); + onFinish(forceFinish, null); } } if (track.isIdle() || mReadyDuringSync.isEmpty()) { @@ -1198,7 +1196,7 @@ public class Transitions implements RemoteCallable<Transitions>, ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt to merge sync %s" + " into %s via a SLEEP proxy", nextSync, playing); playing.mHandler.mergeAnimation(nextSync.mToken, dummyInfo, dummyT, - playing.mToken, (wct, cb) -> {}); + playing.mToken, (wct) -> {}); // it's possible to complete immediately. If that happens, just repeat the signal // loop until we either finish everything or start playing an animation that isn't // finishing immediately. @@ -1226,11 +1224,8 @@ public class Transitions implements RemoteCallable<Transitions>, * The transition must not touch the surfaces after this has been called. * * @param wct A WindowContainerTransaction to run along with the transition clean-up. - * @param wctCB A sync callback that will be run when the transition clean-up is done and - * wct has been applied. */ - void onTransitionFinished(@Nullable WindowContainerTransaction wct, - @Nullable WindowContainerTransactionCallback wctCB); + void onTransitionFinished(@Nullable WindowContainerTransaction wct); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java index f148412205bf..2eb6e71456db 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java @@ -169,7 +169,7 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene animator.stop(); } - mFinishCallback.onTransitionFinished(null, null); + mFinishCallback.onTransitionFinished(null); mFinishCallback = null; mTransition = null; } @@ -193,7 +193,7 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene } // Apply changes happening during the unfold animation immediately t.apply(); - finishCallback.onTransitionFinished(null, null); + finishCallback.onTransitionFinished(null); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java index fb05c696af82..c9c58de6e82a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java @@ -168,7 +168,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, startTransaction.apply(); mDesktopWindowDecoration.hideResizeVeil(); mCtrlType = CTRL_TYPE_UNDEFINED; - finishCallback.onTransitionFinished(null, null); + finishCallback.onTransitionFinished(null); return true; } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt index de64f78a31eb..cb5a60d9746b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.pip import android.graphics.Rect +import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -34,6 +35,7 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** Test the snapping of a PIP window via dragging, releasing, and checking its final location. */ +@FlakyTest(bugId = 294993100) @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java index 4fca8b46a069..2d9304705738 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java @@ -92,7 +92,7 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim .build(); final Animator animator = mAnimRunner.createAnimator( info, mStartTransaction, mFinishTransaction, - () -> mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */), + () -> mFinishCallback.onTransitionFinished(null /* wct */), new ArrayList()); // The animation should be empty when it is behind starting window. diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java index ab1ccd4599a2..0b2265d4ce9c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java @@ -75,7 +75,7 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase { assertNotNull(mAnimRunner); mAnimSpec = mAnimRunner.mAnimationSpec; assertNotNull(mAnimSpec); - mFinishCallback = (wct, wctCB) -> {}; + mFinishCallback = (wct) -> {}; spyOn(mController); spyOn(mAnimRunner); spyOn(mAnimSpec); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java index ba34f1f74cd3..270dbc49835f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java @@ -217,12 +217,10 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation doReturn(animator).when(mAnimRunner).createAnimator(any(), any(), any(), any(), any()); mController.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction, mFinishCallback); - verify(mFinishCallback, never()).onTransitionFinished(any(), any()); + verify(mFinishCallback, never()).onTransitionFinished(any()); mController.mergeAnimation(mTransition, info, new SurfaceControl.Transaction(), - mTransition, - (wct, cb) -> { - }); - verify(mFinishCallback).onTransitionFinished(any(), any()); + mTransition, (wct) -> {}); + verify(mFinishCallback).onTransitionFinished(any()); } @Test @@ -238,9 +236,9 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation mController.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction, mFinishCallback); - verify(mFinishCallback, never()).onTransitionFinished(any(), any()); + verify(mFinishCallback, never()).onTransitionFinished(any()); mController.onAnimationFinished(mTransition); - verify(mFinishCallback).onTransitionFinished(any(), any()); + verify(mFinishCallback).onTransitionFinished(any()); // Should not call finish when the finish has already been called. assertThrows(IllegalStateException.class, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java new file mode 100644 index 000000000000..139724f709c7 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.bubbles; + +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.view.View.LAYOUT_DIRECTION_LTR; +import static android.view.View.LAYOUT_DIRECTION_RTL; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.res.Configuration; +import android.graphics.Insets; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.RectF; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.testing.TestableResources; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.WindowMetrics; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.R; +import com.android.wm.shell.ShellTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests operations and the resulting state managed by {@link BubblePositioner}. + */ +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class BubblePositionerTest extends ShellTestCase { + + private static final int MIN_WIDTH_FOR_TABLET = 600; + + private BubblePositioner mPositioner; + private Configuration mConfiguration; + + @Mock + private WindowManager mWindowManager; + @Mock + private WindowMetrics mWindowMetrics; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mConfiguration = spy(new Configuration()); + TestableResources testableResources = mContext.getOrCreateTestableResources(); + testableResources.overrideConfiguration(mConfiguration); + + mPositioner = new BubblePositioner(mContext, mWindowManager); + } + + @Test + public void testUpdate() { + Insets insets = Insets.of(10, 20, 5, 15); + Rect screenBounds = new Rect(0, 0, 1000, 1200); + Rect availableRect = new Rect(screenBounds); + availableRect.inset(insets); + + new WindowManagerConfig() + .setInsets(insets) + .setScreenBounds(screenBounds) + .setUpConfig(); + mPositioner.update(); + + assertThat(mPositioner.getAvailableRect()).isEqualTo(availableRect); + assertThat(mPositioner.isLandscape()).isFalse(); + assertThat(mPositioner.isLargeScreen()).isFalse(); + assertThat(mPositioner.getInsets()).isEqualTo(insets); + } + + @Test + public void testShowBubblesVertically_phonePortrait() { + new WindowManagerConfig().setOrientation(ORIENTATION_PORTRAIT).setUpConfig(); + mPositioner.update(); + + assertThat(mPositioner.showBubblesVertically()).isFalse(); + } + + @Test + public void testShowBubblesVertically_phoneLandscape() { + new WindowManagerConfig().setOrientation(ORIENTATION_LANDSCAPE).setUpConfig(); + mPositioner.update(); + + assertThat(mPositioner.isLandscape()).isTrue(); + assertThat(mPositioner.showBubblesVertically()).isTrue(); + } + + @Test + public void testShowBubblesVertically_tablet() { + new WindowManagerConfig().setLargeScreen().setUpConfig(); + mPositioner.update(); + + assertThat(mPositioner.showBubblesVertically()).isTrue(); + } + + /** If a resting position hasn't been set, calling it will return the default position. */ + @Test + public void testGetRestingPosition_returnsDefaultPosition() { + new WindowManagerConfig().setUpConfig(); + mPositioner.update(); + + PointF restingPosition = mPositioner.getRestingPosition(); + PointF defaultPosition = mPositioner.getDefaultStartPosition(); + + assertThat(restingPosition).isEqualTo(defaultPosition); + } + + /** If a resting position has been set, it'll return that instead of the default position. */ + @Test + public void testGetRestingPosition_returnsRestingPosition() { + new WindowManagerConfig().setUpConfig(); + mPositioner.update(); + + PointF restingPosition = new PointF(100, 100); + mPositioner.setRestingPosition(restingPosition); + + assertThat(mPositioner.getRestingPosition()).isEqualTo(restingPosition); + } + + /** Test that the default resting position on phone is in upper left. */ + @Test + public void testGetRestingPosition_bubble_onPhone() { + new WindowManagerConfig().setUpConfig(); + mPositioner.update(); + + RectF allowableStackRegion = + mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); + PointF restingPosition = mPositioner.getRestingPosition(); + + assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left); + assertThat(restingPosition.y).isEqualTo(getDefaultYPosition()); + } + + @Test + public void testGetRestingPosition_bubble_onPhone_RTL() { + new WindowManagerConfig().setLayoutDirection(LAYOUT_DIRECTION_RTL).setUpConfig(); + mPositioner.update(); + + RectF allowableStackRegion = + mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); + PointF restingPosition = mPositioner.getRestingPosition(); + + assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right); + assertThat(restingPosition.y).isEqualTo(getDefaultYPosition()); + } + + /** Test that the default resting position on tablet is middle left. */ + @Test + public void testGetRestingPosition_chatBubble_onTablet() { + new WindowManagerConfig().setLargeScreen().setUpConfig(); + mPositioner.update(); + + RectF allowableStackRegion = + mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); + PointF restingPosition = mPositioner.getRestingPosition(); + + assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left); + assertThat(restingPosition.y).isEqualTo(getDefaultYPosition()); + } + + @Test + public void testGetRestingPosition_chatBubble_onTablet_RTL() { + new WindowManagerConfig().setLargeScreen().setLayoutDirection( + LAYOUT_DIRECTION_RTL).setUpConfig(); + mPositioner.update(); + + RectF allowableStackRegion = + mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); + PointF restingPosition = mPositioner.getRestingPosition(); + + assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right); + assertThat(restingPosition.y).isEqualTo(getDefaultYPosition()); + } + + /** + * Calculates the Y position bubbles should be placed based on the config. Based on + * the calculations in {@link BubblePositioner#getDefaultStartPosition()} and + * {@link BubbleStackView.RelativeStackPosition}. + */ + private float getDefaultYPosition() { + final boolean isTablet = mPositioner.isLargeScreen(); + + // On tablet the position is centered, on phone it is an offset from the top. + final float desiredY = isTablet + ? mPositioner.getScreenRect().height() / 2f - (mPositioner.getBubbleSize() / 2f) + : mContext.getResources().getDimensionPixelOffset( + R.dimen.bubble_stack_starting_offset_y); + // Since we're visually centering the bubbles on tablet, use total screen height rather + // than the available height. + final float height = isTablet + ? mPositioner.getScreenRect().height() + : mPositioner.getAvailableRect().height(); + float offsetPercent = desiredY / height; + offsetPercent = Math.max(0f, Math.min(1f, offsetPercent)); + final RectF allowableStackRegion = + mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); + return allowableStackRegion.top + allowableStackRegion.height() * offsetPercent; + } + + /** + * Sets up window manager to return config values based on what you need for the test. + * By default it sets up a portrait phone without any insets. + */ + private class WindowManagerConfig { + private Rect mScreenBounds = new Rect(0, 0, 1000, 2000); + private boolean mIsLargeScreen = false; + private int mOrientation = ORIENTATION_PORTRAIT; + private int mLayoutDirection = LAYOUT_DIRECTION_LTR; + private Insets mInsets = Insets.of(0, 0, 0, 0); + + public WindowManagerConfig setScreenBounds(Rect screenBounds) { + mScreenBounds = screenBounds; + return this; + } + + public WindowManagerConfig setLargeScreen() { + mIsLargeScreen = true; + return this; + } + + public WindowManagerConfig setOrientation(int orientation) { + mOrientation = orientation; + return this; + } + + public WindowManagerConfig setLayoutDirection(int layoutDirection) { + mLayoutDirection = layoutDirection; + return this; + } + + public WindowManagerConfig setInsets(Insets insets) { + mInsets = insets; + return this; + } + + public void setUpConfig() { + mConfiguration.smallestScreenWidthDp = mIsLargeScreen + ? MIN_WIDTH_FOR_TABLET + : MIN_WIDTH_FOR_TABLET - 1; + mConfiguration.orientation = mOrientation; + + when(mConfiguration.getLayoutDirection()).thenReturn(mLayoutDirection); + WindowInsets windowInsets = mock(WindowInsets.class); + when(windowInsets.getInsetsIgnoringVisibility(anyInt())).thenReturn(mInsets); + when(mWindowMetrics.getWindowInsets()).thenReturn(windowInsets); + when(mWindowMetrics.getBounds()).thenReturn(mScreenBounds); + when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics); + } + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java index addc2338144f..bf1b7f9f86ed 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java @@ -32,7 +32,8 @@ import androidx.test.filters.SmallTest; import com.android.wm.shell.R; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; -import com.android.wm.shell.pip.phone.PipSizeSpecHandler; +import com.android.wm.shell.common.pip.PhoneSizeSpecSource; +import com.android.wm.shell.common.pip.SizeSpecSource; import org.junit.Before; import org.junit.Test; @@ -60,7 +61,8 @@ public class PipBoundsAlgorithmTest extends ShellTestCase { private PipBoundsAlgorithm mPipBoundsAlgorithm; private DisplayInfo mDefaultDisplayInfo; - private PipBoundsState mPipBoundsState; private PipSizeSpecHandler mPipSizeSpecHandler; + private PipBoundsState mPipBoundsState; + private SizeSpecSource mSizeSpecSource; private PipDisplayLayoutState mPipDisplayLayoutState; @@ -68,11 +70,12 @@ public class PipBoundsAlgorithmTest extends ShellTestCase { public void setUp() throws Exception { initializeMockResources(); mPipDisplayLayoutState = new PipDisplayLayoutState(mContext); - mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState); - mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState); + + mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState); + mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState); mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {}, - mPipSizeSpecHandler); + mPipDisplayLayoutState, mSizeSpecSource); DisplayLayout layout = new DisplayLayout(mDefaultDisplayInfo, mContext.getResources(), true, true); @@ -132,7 +135,7 @@ public class PipBoundsAlgorithmTest extends ShellTestCase { @Test public void getDefaultBounds_noOverrideMinSize_matchesDefaultSizeAndAspectRatio() { - final Size defaultSize = mPipSizeSpecHandler.getDefaultSize(DEFAULT_ASPECT_RATIO); + final Size defaultSize = mSizeSpecSource.getDefaultSize(DEFAULT_ASPECT_RATIO); mPipBoundsState.setOverrideMinSize(null); final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java index f32000445ca9..4341c4c8c8aa 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java @@ -35,7 +35,8 @@ import androidx.test.filters.SmallTest; import com.android.internal.util.function.TriConsumer; import com.android.wm.shell.R; import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.pip.phone.PipSizeSpecHandler; +import com.android.wm.shell.common.pip.PhoneSizeSpecSource; +import com.android.wm.shell.common.pip.SizeSpecSource; import org.junit.Before; import org.junit.Test; @@ -58,6 +59,7 @@ public class PipBoundsStateTest extends ShellTestCase { private static final int OVERRIDABLE_MIN_SIZE = 40; private PipBoundsState mPipBoundsState; + private SizeSpecSource mSizeSpecSource; private ComponentName mTestComponentName1; private ComponentName mTestComponentName2; @@ -69,8 +71,8 @@ public class PipBoundsStateTest extends ShellTestCase { OVERRIDABLE_MIN_SIZE); PipDisplayLayoutState pipDisplayLayoutState = new PipDisplayLayoutState(mContext); - mPipBoundsState = new PipBoundsState(mContext, - new PipSizeSpecHandler(mContext, pipDisplayLayoutState), pipDisplayLayoutState); + mSizeSpecSource = new PhoneSizeSpecSource(mContext, pipDisplayLayoutState); + mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, pipDisplayLayoutState); mTestComponentName1 = new ComponentName(mContext, "component1"); mTestComponentName2 = new ComponentName(mContext, "component2"); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java index 842c699fa42d..1e3fe421140a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java @@ -52,8 +52,9 @@ import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.common.pip.PhoneSizeSpecSource; +import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.pip.phone.PhonePipMenuController; -import com.android.wm.shell.pip.phone.PipSizeSpecHandler; import com.android.wm.shell.splitscreen.SplitScreenController; import org.junit.Before; @@ -87,7 +88,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { private PipBoundsState mPipBoundsState; private PipTransitionState mPipTransitionState; private PipBoundsAlgorithm mPipBoundsAlgorithm; - private PipSizeSpecHandler mPipSizeSpecHandler; + private SizeSpecSource mSizeSpecSource; private PipDisplayLayoutState mPipDisplayLayoutState; private ComponentName mComponent1; @@ -99,12 +100,12 @@ public class PipTaskOrganizerTest extends ShellTestCase { mComponent1 = new ComponentName(mContext, "component1"); mComponent2 = new ComponentName(mContext, "component2"); mPipDisplayLayoutState = new PipDisplayLayoutState(mContext); - mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState); - mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState); + mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState); + mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState); mPipTransitionState = new PipTransitionState(); mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {}, - mPipSizeSpecHandler); + mPipDisplayLayoutState, mSizeSpecSource); mMainExecutor = new TestShellExecutor(); mPipTaskOrganizer = new PipTaskOrganizer(mContext, mMockSyncTransactionQueue, mPipTransitionState, mPipBoundsState, mPipDisplayLayoutState, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java index 528c23cd8115..024cba319ffc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java @@ -32,6 +32,8 @@ import android.view.DisplayInfo; import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.pip.PhoneSizeSpecSource; +import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.pip.PipDisplayLayoutState; import org.junit.After; @@ -47,10 +49,10 @@ import java.util.Map; import java.util.function.Function; /** - * Unit test against {@link PipSizeSpecHandler} with feature flag on. + * Unit test against {@link PhoneSizeSpecSource} */ @RunWith(AndroidTestingRunner.class) -public class PipSizeSpecHandlerTest extends ShellTestCase { +public class PhoneSizeSpecSourceTest extends ShellTestCase { /** A sample overridden min edge size. */ private static final int OVERRIDE_MIN_EDGE_SIZE = 40; /** A sample default min edge size */ @@ -75,7 +77,7 @@ public class PipSizeSpecHandlerTest extends ShellTestCase { @Mock private Resources mResources; private PipDisplayLayoutState mPipDisplayLayoutState; - private TestPipSizeSpecHandler mPipSizeSpecHandler; + private SizeSpecSource mSizeSpecSource; /** * Sets up static Mockito session for SystemProperties and mocks necessary static methods. @@ -158,10 +160,10 @@ public class PipSizeSpecHandlerTest extends ShellTestCase { mPipDisplayLayoutState.setDisplayLayout(displayLayout); setUpStaticSystemPropertiesSession(); - mPipSizeSpecHandler = new TestPipSizeSpecHandler(mContext, mPipDisplayLayoutState); + mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState); // no overridden min edge size by default - mPipSizeSpecHandler.setOverrideMinSize(null); + mSizeSpecSource.setOverrideMinSize(null); } @After @@ -172,19 +174,19 @@ public class PipSizeSpecHandlerTest extends ShellTestCase { @Test public void testGetMaxSize() { forEveryTestCaseCheck(sExpectedMaxSizes, - (aspectRatio) -> mPipSizeSpecHandler.getMaxSize(aspectRatio)); + (aspectRatio) -> mSizeSpecSource.getMaxSize(aspectRatio)); } @Test public void testGetDefaultSize() { forEveryTestCaseCheck(sExpectedDefaultSizes, - (aspectRatio) -> mPipSizeSpecHandler.getDefaultSize(aspectRatio)); + (aspectRatio) -> mSizeSpecSource.getDefaultSize(aspectRatio)); } @Test public void testGetMinSize() { forEveryTestCaseCheck(sExpectedMinSizes, - (aspectRatio) -> mPipSizeSpecHandler.getMinSize(aspectRatio)); + (aspectRatio) -> mSizeSpecSource.getMinSize(aspectRatio)); } @Test @@ -193,7 +195,7 @@ public class PipSizeSpecHandlerTest extends ShellTestCase { Size initSize = new Size(600, 337); Size expectedSize = new Size(338, 601); - Size actualSize = mPipSizeSpecHandler.getSizeForAspectRatio(initSize, 9f / 16); + Size actualSize = mSizeSpecSource.getSizeForAspectRatio(initSize, 9f / 16); Assert.assertEquals(expectedSize, actualSize); } @@ -201,26 +203,12 @@ public class PipSizeSpecHandlerTest extends ShellTestCase { @Test public void testGetSizeForAspectRatio_withOverrideMinSize() { // an initial size with a 1:1 aspect ratio - mPipSizeSpecHandler.setOverrideMinSize(new Size(OVERRIDE_MIN_EDGE_SIZE, - OVERRIDE_MIN_EDGE_SIZE)); - // make sure initial size is same as override min size - Size initSize = mPipSizeSpecHandler.getOverrideMinSize(); + Size initSize = new Size(OVERRIDE_MIN_EDGE_SIZE, OVERRIDE_MIN_EDGE_SIZE); + mSizeSpecSource.setOverrideMinSize(initSize); Size expectedSize = new Size(40, 71); - Size actualSize = mPipSizeSpecHandler.getSizeForAspectRatio(initSize, 9f / 16); + Size actualSize = mSizeSpecSource.getSizeForAspectRatio(initSize, 9f / 16); Assert.assertEquals(expectedSize, actualSize); } - - static class TestPipSizeSpecHandler extends PipSizeSpecHandler { - - TestPipSizeSpecHandler(Context context, PipDisplayLayoutState displayLayoutState) { - super(context, displayLayoutState); - } - - @Override - boolean supportsPipSizeLargeScreen() { - return true; - } - } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index 85167cb97501..2cc28acd0b17 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -109,7 +109,6 @@ public class PipControllerTest extends ShellTestCase { @Mock private PipMotionHelper mMockPipMotionHelper; @Mock private WindowManagerShellWrapper mMockWindowManagerShellWrapper; @Mock private PipBoundsState mMockPipBoundsState; - @Mock private PipSizeSpecHandler mMockPipSizeSpecHandler; @Mock private PipDisplayLayoutState mMockPipDisplayLayoutState; @Mock private TaskStackListenerImpl mMockTaskStackListener; @Mock private ShellExecutor mMockExecutor; @@ -134,7 +133,7 @@ public class PipControllerTest extends ShellTestCase { mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler, mShellController, mMockDisplayController, mMockPipAnimationController, mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm, - mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipDisplayLayoutState, + mMockPipBoundsState, mMockPipDisplayLayoutState, mMockPipMotionHelper, mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper, @@ -226,7 +225,7 @@ public class PipControllerTest extends ShellTestCase { assertNull(PipController.create(spyContext, shellInit, mMockShellCommandHandler, mShellController, mMockDisplayController, mMockPipAnimationController, mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm, - mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipDisplayLayoutState, + mMockPipBoundsState, mMockPipDisplayLayoutState, mMockPipMotionHelper, mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java index 1dfdbf6514ba..689b5c5a708c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java @@ -36,6 +36,8 @@ import androidx.test.filters.SmallTest; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.pip.PhoneSizeSpecSource; +import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipDisplayLayoutState; @@ -87,7 +89,7 @@ public class PipResizeGestureHandlerTest extends ShellTestCase { private PipBoundsState mPipBoundsState; - private PipSizeSpecHandler mPipSizeSpecHandler; + private SizeSpecSource mSizeSpecSource; private PipDisplayLayoutState mPipDisplayLayoutState; @@ -97,13 +99,14 @@ public class PipResizeGestureHandlerTest extends ShellTestCase { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mPipDisplayLayoutState = new PipDisplayLayoutState(mContext); - mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState); - mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState); + mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState); + mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState); final PipSnapAlgorithm pipSnapAlgorithm = new PipSnapAlgorithm(); final PipKeepClearAlgorithmInterface pipKeepClearAlgorithm = new PipKeepClearAlgorithmInterface() {}; final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, - mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipSizeSpecHandler); + mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipDisplayLayoutState, + mSizeSpecSource); final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm, mMockPipTransitionController, mFloatingContentCoordinator); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java index 10b1ddf1b868..852183cbcbac 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java @@ -33,6 +33,8 @@ import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.pip.PhoneSizeSpecSource; +import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipDisplayLayoutState; @@ -92,7 +94,7 @@ public class PipTouchHandlerTest extends ShellTestCase { private PipSnapAlgorithm mPipSnapAlgorithm; private PipMotionHelper mMotionHelper; private PipResizeGestureHandler mPipResizeGestureHandler; - private PipSizeSpecHandler mPipSizeSpecHandler; + private SizeSpecSource mSizeSpecSource; private PipDisplayLayoutState mPipDisplayLayoutState; private DisplayLayout mDisplayLayout; @@ -108,16 +110,16 @@ public class PipTouchHandlerTest extends ShellTestCase { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mPipDisplayLayoutState = new PipDisplayLayoutState(mContext); - mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState); - mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState); + mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState); + mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState); mPipSnapAlgorithm = new PipSnapAlgorithm(); mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm, - new PipKeepClearAlgorithmInterface() {}, mPipSizeSpecHandler); + new PipKeepClearAlgorithmInterface() {}, mPipDisplayLayoutState, mSizeSpecSource); PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm, mMockPipTransitionController, mFloatingContentCoordinator); mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController, - mPipBoundsAlgorithm, mPipBoundsState, mPipSizeSpecHandler, mPipTaskOrganizer, + mPipBoundsAlgorithm, mPipBoundsState, mSizeSpecSource, mPipTaskOrganizer, pipMotionHelper, mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor); // We aren't actually using ShellInit, so just call init directly mPipTouchHandler.onInit(); @@ -162,8 +164,8 @@ public class PipTouchHandlerTest extends ShellTestCase { // getting the expected min and max size float aspectRatio = (float) mPipBounds.width() / mPipBounds.height(); - Size expectedMinSize = mPipSizeSpecHandler.getMinSize(aspectRatio); - Size expectedMaxSize = mPipSizeSpecHandler.getMaxSize(aspectRatio); + Size expectedMinSize = mSizeSpecSource.getMinSize(aspectRatio); + Size expectedMaxSize = mSizeSpecSource.getMaxSize(aspectRatio); assertEquals(expectedMovementBounds, mPipBoundsState.getNormalMovementBounds()); verify(mPipResizeGestureHandler, times(1)) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java index 91ff3cbf3a63..256610b857a1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java @@ -26,9 +26,10 @@ import static org.junit.Assert.assertEquals; import android.view.Gravity; import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.common.pip.LegacySizeSpecSource; +import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.pip.PipDisplayLayoutState; import com.android.wm.shell.pip.PipSnapAlgorithm; -import com.android.wm.shell.pip.phone.PipSizeSpecHandler; import org.junit.Before; import org.junit.Test; @@ -47,7 +48,7 @@ public class TvPipGravityTest extends ShellTestCase { private TvPipBoundsState mTvPipBoundsState; private TvPipBoundsAlgorithm mTvPipBoundsAlgorithm; - private PipSizeSpecHandler mPipSizeSpecHandler; + private SizeSpecSource mSizeSpecSource; private PipDisplayLayoutState mPipDisplayLayoutState; @Before @@ -56,11 +57,11 @@ public class TvPipGravityTest extends ShellTestCase { MockitoAnnotations.initMocks(this); mPipDisplayLayoutState = new PipDisplayLayoutState(mContext); - mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState); - mTvPipBoundsState = new TvPipBoundsState(mContext, mPipSizeSpecHandler, + mSizeSpecSource = new LegacySizeSpecSource(mContext, mPipDisplayLayoutState); + mTvPipBoundsState = new TvPipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState); mTvPipBoundsAlgorithm = new TvPipBoundsAlgorithm(mContext, mTvPipBoundsState, - mMockPipSnapAlgorithm, mPipSizeSpecHandler); + mMockPipSnapAlgorithm, mPipDisplayLayoutState, mSizeSpecSource); setRTL(false); } 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 ff380e92322d..99a1ac663286 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 @@ -176,7 +176,7 @@ public class ShellTransitionTests extends ShellTestCase { assertEquals(1, mDefaultHandler.activeCount()); mDefaultHandler.finishAll(); mMainExecutor.flushAll(); - verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any(), any()); + verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any()); } @Test @@ -299,7 +299,7 @@ public class ShellTransitionTests extends ShellTestCase { assertTrue(remoteCalled[0]); mDefaultHandler.finishAll(); mMainExecutor.flushAll(); - verify(mOrganizer, times(1)).finishTransition(eq(transitToken), eq(remoteFinishWCT), any()); + verify(mOrganizer, times(1)).finishTransition(eq(transitToken), eq(remoteFinishWCT)); } @Test @@ -449,7 +449,7 @@ public class ShellTransitionTests extends ShellTestCase { assertTrue(remoteCalled[0]); mDefaultHandler.finishAll(); mMainExecutor.flushAll(); - verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any(), any()); + verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any()); } @Test @@ -524,20 +524,20 @@ public class ShellTransitionTests extends ShellTestCase { // default handler doesn't merge by default, so it shouldn't increment active count. assertEquals(1, mDefaultHandler.activeCount()); assertEquals(0, mDefaultHandler.mergeCount()); - verify(mOrganizer, times(0)).finishTransition(eq(transitToken1), any(), any()); - verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any(), any()); + verify(mOrganizer, times(0)).finishTransition(eq(transitToken1), any()); + verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any()); mDefaultHandler.finishAll(); mMainExecutor.flushAll(); // first transition finished - verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any(), any()); - verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any(), any()); + verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any()); + verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any()); // But now the "queued" transition is running assertEquals(1, mDefaultHandler.activeCount()); mDefaultHandler.finishAll(); mMainExecutor.flushAll(); - verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any(), any()); + verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any()); } @Test @@ -565,15 +565,15 @@ public class ShellTransitionTests extends ShellTestCase { // it should still only have 1 active, but then show 1 merged assertEquals(1, mDefaultHandler.activeCount()); assertEquals(1, mDefaultHandler.mergeCount()); - verify(mOrganizer, times(0)).finishTransition(eq(transitToken1), any(), any()); + verify(mOrganizer, times(0)).finishTransition(eq(transitToken1), any()); // We don't tell organizer it is finished yet (since we still want to maintain ordering) - verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any(), any()); + verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any()); mDefaultHandler.finishAll(); mMainExecutor.flushAll(); // transition + merged all finished. - verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any(), any()); - verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any(), any()); + verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any()); + verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any()); // Make sure nothing was queued assertEquals(0, mDefaultHandler.activeCount()); } @@ -599,8 +599,7 @@ public class ShellTransitionTests extends ShellTestCase { requestStartTransition(transitions, transitTokenNotReady); mDefaultHandler.setSimulateMerge(true); - mDefaultHandler.mFinishes.get(0).second.onTransitionFinished( - null /* wct */, null /* wctCB */); + mDefaultHandler.mFinishes.get(0).second.onTransitionFinished(null /* wct */); // Make sure that the non-ready transition is not merged. assertEquals(0, mDefaultHandler.mergeCount()); @@ -823,8 +822,8 @@ public class ShellTransitionTests extends ShellTestCase { mDefaultHandler.finishAll(); mMainExecutor.flushAll(); // first transition finished - verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any(), any()); - verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any(), any()); + verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any()); + verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any()); // But now the "queued" transition is running assertEquals(1, mDefaultHandler.activeCount()); @@ -835,7 +834,7 @@ public class ShellTransitionTests extends ShellTestCase { mDefaultHandler.finishAll(); mMainExecutor.flushAll(); - verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any(), any()); + verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any()); // runnable2 and runnable3 are executed after the second transition finishes because there // are no other active transitions, runnable1 isn't executed again. @@ -1449,13 +1448,13 @@ public class ShellTransitionTests extends ShellTestCase { if (mFinishOnSync && info.getType() == TRANSIT_SLEEP) { for (int i = 0; i < mFinishes.size(); ++i) { if (mFinishes.get(i).first != mergeTarget) continue; - mFinishes.remove(i).second.onTransitionFinished(null, null); + mFinishes.remove(i).second.onTransitionFinished(null); return; } } if (!(mSimulateMerge || mShouldMerge.contains(transition))) return; mMerged.add(transition); - finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + finishCallback.onTransitionFinished(null /* wct */); } @Nullable @@ -1478,7 +1477,7 @@ public class ShellTransitionTests extends ShellTestCase { mFinishes; mFinishes = new ArrayList<>(); for (int i = finishes.size() - 1; i >= 0; --i) { - finishes.get(i).second.onTransitionFinished(null /* wct */, null /* wctCB */); + finishes.get(i).second.onTransitionFinished(null /* wct */); } mShouldMerge.clear(); } @@ -1486,7 +1485,7 @@ public class ShellTransitionTests extends ShellTestCase { void finishOne() { Pair<IBinder, Transitions.TransitionFinishCallback> fin = mFinishes.remove(0); mMerged.clear(); - fin.second.onTransitionFinished(null /* wct */, null /* wctCB */); + fin.second.onTransitionFinished(null /* wct */); } int activeCount() { diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index 52666ab8d1d5..fdb355192676 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -1872,6 +1872,8 @@ struct FabricatedOverlayEntryParameters { DataValue data_value; std::string data_string_value; std::optional<android::base::borrowed_fd> data_binary_value; + off64_t binary_data_offset; + size_t binary_data_size; std::string configuration; }; diff --git a/libs/hwui/OWNERS b/libs/hwui/OWNERS index bb93e66968be..6ca991d8b294 100644 --- a/libs/hwui/OWNERS +++ b/libs/hwui/OWNERS @@ -1,3 +1,5 @@ +# Bug component: 1075005 + alecmouri@google.com djsollen@google.com jreck@google.com diff --git a/libs/hwui/Tonemapper.cpp b/libs/hwui/Tonemapper.cpp index 974a5d05aa84..ae29edf535a2 100644 --- a/libs/hwui/Tonemapper.cpp +++ b/libs/hwui/Tonemapper.cpp @@ -20,6 +20,7 @@ #include <log/log.h> // libshaders only exists on Android devices #ifdef __ANDROID__ +#include <renderthread/CanvasContext.h> #include <shaders/shaders.h> #endif @@ -53,8 +54,17 @@ static sk_sp<SkColorFilter> createLinearEffectColorFilter(const shaders::LinearE ColorFilterRuntimeEffectBuilder effectBuilder(std::move(runtimeEffect)); + auto colorTransform = android::mat4(); + const auto* context = renderthread::CanvasContext::getActiveContext(); + if (context) { + const auto ratio = context->targetSdrHdrRatio(); + if (ratio > 1.0f) { + colorTransform = android::mat4::scale(vec4(ratio, ratio, ratio, 1.f)); + } + } + const auto uniforms = - shaders::buildLinearEffectUniforms(linearEffect, android::mat4(), maxDisplayLuminance, + shaders::buildLinearEffectUniforms(linearEffect, colorTransform, maxDisplayLuminance, currentDisplayLuminanceNits, maxLuminance); for (const auto& uniform : uniforms) { diff --git a/location/TEST_MAPPING b/location/TEST_MAPPING index 214d2f3f5646..f5deb2ba3e07 100644 --- a/location/TEST_MAPPING +++ b/location/TEST_MAPPING @@ -1,7 +1,13 @@ { "presubmit": [ { - "name": "CtsLocationFineTestCases" + "name": "CtsLocationFineTestCases", + "options": [ + { + // TODO: Wait for test to deflake - b/293934372 + "exclude-filter":"android.location.cts.fine.ScanningSettingsTest" + } + ] }, { "name": "CtsLocationCoarseTestCases" @@ -16,4 +22,4 @@ }] } ] -}
\ No newline at end of file +} diff --git a/location/java/android/location/LocationManagerInternal.java b/location/java/android/location/LocationManagerInternal.java index d59756d02348..a48cc19995c9 100644 --- a/location/java/android/location/LocationManagerInternal.java +++ b/location/java/android/location/LocationManagerInternal.java @@ -87,12 +87,6 @@ public abstract class LocationManagerInternal { public abstract boolean isProvider(@Nullable String provider, @NonNull CallerIdentity identity); /** - * Should only be used by GNSS code. - */ - // TODO: there is no reason for this to exist as part of any API. move all the logic into gnss - public abstract void sendNiResponse(int notifId, int userResponse); - - /** * Returns the GNSS provided time. * * @return LocationTime object that includes the current time, according to the GNSS location diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java index fba4249260ef..ee2510ff9695 100644 --- a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java +++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java @@ -18,28 +18,14 @@ package com.android.internal.location; import android.Manifest; import android.annotation.RequiresPermission; -import android.app.Notification; -import android.app.NotificationManager; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.location.INetInitiatedListener; import android.location.LocationManager; -import android.os.RemoteException; import android.os.SystemClock; -import android.os.UserHandle; import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; import android.telephony.emergency.EmergencyNumber; import android.util.Log; -import com.android.internal.R; -import com.android.internal.notification.SystemNotificationChannels; -import com.android.internal.telephony.GsmAlphabet; - -import java.io.UnsupportedEncodingException; import java.util.concurrent.TimeUnit; /** @@ -53,95 +39,20 @@ public class GpsNetInitiatedHandler { private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - // string constants for defining data fields in NI Intent - public static final String NI_INTENT_KEY_NOTIF_ID = "notif_id"; - public static final String NI_INTENT_KEY_TITLE = "title"; - public static final String NI_INTENT_KEY_MESSAGE = "message"; - public static final String NI_INTENT_KEY_TIMEOUT = "timeout"; - public static final String NI_INTENT_KEY_DEFAULT_RESPONSE = "default_resp"; - - // the extra command to send NI response to GnssLocationProvider - public static final String NI_RESPONSE_EXTRA_CMD = "send_ni_response"; - - // the extra command parameter names in the Bundle - public static final String NI_EXTRA_CMD_NOTIF_ID = "notif_id"; - public static final String NI_EXTRA_CMD_RESPONSE = "response"; - - // these need to match GpsNiType constants in gps_ni.h - public static final int GPS_NI_TYPE_VOICE = 1; - public static final int GPS_NI_TYPE_UMTS_SUPL = 2; - public static final int GPS_NI_TYPE_UMTS_CTRL_PLANE = 3; - public static final int GPS_NI_TYPE_EMERGENCY_SUPL = 4; - - // these need to match GpsUserResponseType constants in gps_ni.h - public static final int GPS_NI_RESPONSE_ACCEPT = 1; - public static final int GPS_NI_RESPONSE_DENY = 2; - public static final int GPS_NI_RESPONSE_NORESP = 3; - public static final int GPS_NI_RESPONSE_IGNORE = 4; - - // these need to match GpsNiNotifyFlags constants in gps_ni.h - public static final int GPS_NI_NEED_NOTIFY = 0x0001; - public static final int GPS_NI_NEED_VERIFY = 0x0002; - public static final int GPS_NI_PRIVACY_OVERRIDE = 0x0004; - - // these need to match GpsNiEncodingType in gps_ni.h - public static final int GPS_ENC_NONE = 0; - public static final int GPS_ENC_SUPL_GSM_DEFAULT = 1; - public static final int GPS_ENC_SUPL_UTF8 = 2; - public static final int GPS_ENC_SUPL_UCS2 = 3; - public static final int GPS_ENC_UNKNOWN = -1; - private final Context mContext; private final TelephonyManager mTelephonyManager; // parent gps location provider private final LocationManager mLocationManager; - // configuration of notificaiton behavior - private boolean mPlaySounds = false; - private boolean mPopupImmediately = true; - - // read the SUPL_ES form gps.conf - private volatile boolean mIsSuplEsEnabled; - // Set to true if the phone is having emergency call. private volatile boolean mIsInEmergencyCall; - // If Location function is enabled. - private volatile boolean mIsLocationEnabled = false; - - private final INetInitiatedListener mNetInitiatedListener; - - // Set to true if string from HAL is encoded as Hex, e.g., "3F0039" - @UnsupportedAppUsage - static private boolean mIsHexInput = true; // End time of emergency call, and extension, if set private volatile long mCallEndElapsedRealtimeMillis = 0; private volatile long mEmergencyExtensionMillis = 0; - public static class GpsNiNotification - { - @android.compat.annotation.UnsupportedAppUsage - public GpsNiNotification() { - } - public int notificationId; - public int niType; - public boolean needNotify; - public boolean needVerify; - public boolean privacyOverride; - public int timeout; - public int defaultResponse; - @UnsupportedAppUsage - public String requestorId; - @UnsupportedAppUsage - public String text; - @UnsupportedAppUsage - public int requestorIdEncoding; - @UnsupportedAppUsage - public int textEncoding; - } - /** Callbacks for Emergency call events. */ public interface EmergencyCallCallback { /** Callback invoked when an emergency call starts */ @@ -182,72 +93,20 @@ public class GpsNetInitiatedHandler { // reference here. private final EmergencyCallListener mEmergencyCallListener = new EmergencyCallListener(); - /** - * The notification that is shown when a network-initiated notification - * (and verification) event is received. - * <p> - * This is lazily created, so use {@link #setNINotification()}. - */ - private Notification.Builder mNiNotificationBuilder; - private final EmergencyCallCallback mEmergencyCallCallback; public GpsNetInitiatedHandler(Context context, - INetInitiatedListener netInitiatedListener, EmergencyCallCallback emergencyCallCallback, boolean isSuplEsEnabled) { mContext = context; - - if (netInitiatedListener == null) { - throw new IllegalArgumentException("netInitiatedListener is null"); - } else { - mNetInitiatedListener = netInitiatedListener; - } mEmergencyCallCallback = emergencyCallCallback; - setSuplEsEnabled(isSuplEsEnabled); mLocationManager = (LocationManager)context.getSystemService(Context.LOCATION_SERVICE); - updateLocationMode(); mTelephonyManager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); mTelephonyManager.registerTelephonyCallback(mContext.getMainExecutor(), mEmergencyCallListener); - BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action.equals(LocationManager.MODE_CHANGED_ACTION)) { - updateLocationMode(); - if (DEBUG) Log.d(TAG, "location enabled :" + getLocationEnabled()); - } - } - }; - mContext.registerReceiver(broadcastReceiver, - new IntentFilter(LocationManager.MODE_CHANGED_ACTION)); - } - - public void setSuplEsEnabled(boolean isEnabled) { - mIsSuplEsEnabled = isEnabled; - } - - public boolean getSuplEsEnabled() { - return mIsSuplEsEnabled; - } - - /** - * Updates Location enabler based on location setting. - */ - public void updateLocationMode() { - mIsLocationEnabled = mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); - } - - /** - * Checks if user agreed to use location. - */ - public boolean getLocationEnabled() { - return mIsLocationEnabled; } /** @@ -289,346 +148,4 @@ public class GpsNetInitiatedHandler { public void setEmergencyExtensionSeconds(int emergencyExtensionSeconds) { mEmergencyExtensionMillis = TimeUnit.SECONDS.toMillis(emergencyExtensionSeconds); } - - // Handles NI events from HAL - @UnsupportedAppUsage - public void handleNiNotification(GpsNiNotification notif) { - if (DEBUG) Log.d(TAG, "in handleNiNotification () :" - + " notificationId: " + notif.notificationId - + " requestorId: " + notif.requestorId - + " text: " + notif.text - + " mIsSuplEsEnabled" + getSuplEsEnabled() - + " mIsLocationEnabled" + getLocationEnabled()); - - if (getSuplEsEnabled()) { - handleNiInEs(notif); - } else { - handleNi(notif); - } - - ////////////////////////////////////////////////////////////////////////// - // A note about timeout - // According to the protocol, in the need_notify and need_verify case, - // a default response should be sent when time out. - // - // In some GPS hardware, the GPS driver (under HAL) can handle the timeout case - // and this class GpsNetInitiatedHandler does not need to do anything. - // - // However, the UI should at least close the dialog when timeout. Further, - // for more general handling, timeout response should be added to the Handler here. - // - } - - // handle NI form HAL when SUPL_ES is disabled. - private void handleNi(GpsNiNotification notif) { - if (DEBUG) Log.d(TAG, "in handleNi () :" - + " needNotify: " + notif.needNotify - + " needVerify: " + notif.needVerify - + " privacyOverride: " + notif.privacyOverride - + " mPopupImmediately: " + mPopupImmediately - + " mInEmergency: " + getInEmergency()); - - if (!getLocationEnabled() && !getInEmergency()) { - // Location is currently disabled, ignore all NI requests. - try { - mNetInitiatedListener.sendNiResponse(notif.notificationId, - GPS_NI_RESPONSE_IGNORE); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in sendNiResponse"); - } - } - if (notif.needNotify) { - // If NI does not need verify or the dialog is not requested - // to pop up immediately, the dialog box will not pop up. - if (notif.needVerify && mPopupImmediately) { - // Popup the dialog box now - openNiDialog(notif); - } else { - // Show the notification - setNiNotification(notif); - } - } - // ACCEPT cases: 1. Notify, no verify; 2. no notify, no verify; - // 3. privacy override. - if (!notif.needVerify || notif.privacyOverride) { - try { - mNetInitiatedListener.sendNiResponse(notif.notificationId, - GPS_NI_RESPONSE_ACCEPT); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in sendNiResponse"); - } - } - } - - // handle NI from HAL when the SUPL_ES is enabled - private void handleNiInEs(GpsNiNotification notif) { - - if (DEBUG) Log.d(TAG, "in handleNiInEs () :" - + " niType: " + notif.niType - + " notificationId: " + notif.notificationId); - - // UE is in emergency mode when in emergency call mode or in emergency call back mode - /* - 1. When SUPL ES bit is off and UE is not in emergency mode: - Call handleNi() to do legacy behaviour. - 2. When SUPL ES bit is on and UE is in emergency mode: - Call handleNi() to do acceptance behaviour. - 3. When SUPL ES bit is off but UE is in emergency mode: - Ignore the emergency SUPL INIT. - 4. When SUPL ES bit is on but UE is not in emergency mode: - Ignore the emergency SUPL INIT. - */ - boolean isNiTypeES = (notif.niType == GPS_NI_TYPE_EMERGENCY_SUPL); - if (isNiTypeES != getInEmergency()) { - try { - mNetInitiatedListener.sendNiResponse(notif.notificationId, - GPS_NI_RESPONSE_IGNORE); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in sendNiResponse"); - } - } else { - handleNi(notif); - } - } - - /** - * Posts a notification in the status bar using the contents in {@code notif} object. - */ - private synchronized void setNiNotification(GpsNiNotification notif) { - NotificationManager notificationManager = (NotificationManager) mContext - .getSystemService(Context.NOTIFICATION_SERVICE); - if (notificationManager == null) { - return; - } - - String title = getNotifTitle(notif, mContext); - String message = getNotifMessage(notif, mContext); - - if (DEBUG) Log.d(TAG, "setNiNotification, notifyId: " + notif.notificationId + - ", title: " + title + - ", message: " + message); - - // Construct Notification - if (mNiNotificationBuilder == null) { - mNiNotificationBuilder = new Notification.Builder(mContext, - SystemNotificationChannels.NETWORK_ALERTS) - .setSmallIcon(com.android.internal.R.drawable.stat_sys_gps_on) - .setWhen(0) - .setOngoing(true) - .setAutoCancel(true) - .setColor(mContext.getColor( - com.android.internal.R.color.system_notification_accent_color)); - } - - if (mPlaySounds) { - mNiNotificationBuilder.setDefaults(Notification.DEFAULT_SOUND); - } else { - mNiNotificationBuilder.setDefaults(0); - } - - mNiNotificationBuilder.setTicker(getNotifTicker(notif, mContext)) - .setContentTitle(title) - .setContentText(message); - - notificationManager.notifyAsUser(null, notif.notificationId, mNiNotificationBuilder.build(), - UserHandle.ALL); - } - - // Opens the notification dialog and waits for user input - private void openNiDialog(GpsNiNotification notif) - { - Intent intent = getDlgIntent(notif); - - if (DEBUG) Log.d(TAG, "openNiDialog, notifyId: " + notif.notificationId + - ", requestorId: " + notif.requestorId + - ", text: " + notif.text); - - mContext.startActivity(intent); - } - - // Construct the intent for bringing up the dialog activity, which shows the - // notification and takes user input - private Intent getDlgIntent(GpsNiNotification notif) - { - Intent intent = new Intent(); - String title = getDialogTitle(notif, mContext); - String message = getDialogMessage(notif, mContext); - - // directly bring up the NI activity - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - intent.setClass(mContext, com.android.internal.app.NetInitiatedActivity.class); - - // put data in the intent - intent.putExtra(NI_INTENT_KEY_NOTIF_ID, notif.notificationId); - intent.putExtra(NI_INTENT_KEY_TITLE, title); - intent.putExtra(NI_INTENT_KEY_MESSAGE, message); - intent.putExtra(NI_INTENT_KEY_TIMEOUT, notif.timeout); - intent.putExtra(NI_INTENT_KEY_DEFAULT_RESPONSE, notif.defaultResponse); - - if (DEBUG) Log.d(TAG, "generateIntent, title: " + title + ", message: " + message + - ", timeout: " + notif.timeout); - - return intent; - } - - // Converts a string (or Hex string) to a char array - static byte[] stringToByteArray(String original, boolean isHex) - { - int length = isHex ? original.length() / 2 : original.length(); - byte[] output = new byte[length]; - int i; - - if (isHex) - { - for (i = 0; i < length; i++) - { - output[i] = (byte) Integer.parseInt(original.substring(i*2, i*2+2), 16); - } - } - else { - for (i = 0; i < length; i++) - { - output[i] = (byte) original.charAt(i); - } - } - - return output; - } - - /** - * Unpacks an byte array containing 7-bit packed characters into a String. - * - * @param input a 7-bit packed char array - * @return the unpacked String - */ - static String decodeGSMPackedString(byte[] input) - { - final char PADDING_CHAR = 0x00; - int lengthBytes = input.length; - int lengthSeptets = (lengthBytes * 8) / 7; - String decoded; - - /* Special case where the last 7 bits in the last byte could hold a valid - * 7-bit character or a padding character. Drop the last 7-bit character - * if it is a padding character. - */ - if (lengthBytes % 7 == 0) { - if (lengthBytes > 0) { - if ((input[lengthBytes - 1] >> 1) == PADDING_CHAR) { - lengthSeptets = lengthSeptets - 1; - } - } - } - - decoded = GsmAlphabet.gsm7BitPackedToString(input, 0, lengthSeptets); - - // Return "" if decoding of GSM packed string fails - if (null == decoded) { - Log.e(TAG, "Decoding of GSM packed string failed"); - decoded = ""; - } - - return decoded; - } - - static String decodeUTF8String(byte[] input) - { - String decoded = ""; - try { - decoded = new String(input, "UTF-8"); - } - catch (UnsupportedEncodingException e) - { - throw new AssertionError(); - } - return decoded; - } - - static String decodeUCS2String(byte[] input) - { - String decoded = ""; - try { - decoded = new String(input, "UTF-16"); - } - catch (UnsupportedEncodingException e) - { - throw new AssertionError(); - } - return decoded; - } - - /** Decode NI string - * - * @param original The text string to be decoded - * @param isHex Specifies whether the content of the string has been encoded as a Hex string. Encoding - * a string as Hex can allow zeros inside the coded text. - * @param coding Specifies the coding scheme of the string, such as GSM, UTF8, UCS2, etc. This coding scheme - * needs to match those used passed to HAL from the native GPS driver. Decoding is done according - * to the <code> coding </code>, after a Hex string is decoded. Generally, if the - * notification strings don't need further decoding, <code> coding </code> encoding can be - * set to -1, and <code> isHex </code> can be false. - * @return the decoded string - */ - @UnsupportedAppUsage - static private String decodeString(String original, boolean isHex, int coding) - { - if (coding == GPS_ENC_NONE || coding == GPS_ENC_UNKNOWN) { - return original; - } - - byte[] input = stringToByteArray(original, isHex); - - switch (coding) { - case GPS_ENC_SUPL_GSM_DEFAULT: - return decodeGSMPackedString(input); - - case GPS_ENC_SUPL_UTF8: - return decodeUTF8String(input); - - case GPS_ENC_SUPL_UCS2: - return decodeUCS2String(input); - - default: - Log.e(TAG, "Unknown encoding " + coding + " for NI text " + original); - return original; - } - } - - // change this to configure notification display - static private String getNotifTicker(GpsNiNotification notif, Context context) - { - String ticker = String.format(context.getString(R.string.gpsNotifTicker), - decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding), - decodeString(notif.text, mIsHexInput, notif.textEncoding)); - return ticker; - } - - // change this to configure notification display - static private String getNotifTitle(GpsNiNotification notif, Context context) - { - String title = String.format(context.getString(R.string.gpsNotifTitle)); - return title; - } - - // change this to configure notification display - static private String getNotifMessage(GpsNiNotification notif, Context context) - { - String message = String.format(context.getString(R.string.gpsNotifMessage), - decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding), - decodeString(notif.text, mIsHexInput, notif.textEncoding)); - return message; - } - - // change this to configure dialog display (for verification) - static public String getDialogTitle(GpsNiNotification notif, Context context) - { - return getNotifTitle(notif, context); - } - - // change this to configure dialog display (for verification) - static private String getDialogMessage(GpsNiNotification notif, Context context) - { - return getNotifMessage(notif, context); - } - } diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 4759689335e9..e8c9d0dbd884 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -6929,6 +6929,114 @@ public class AudioManager { /** * @hide + * Describes an audio device that has not been categorized with a specific + * audio type. + */ + public static final int AUDIO_DEVICE_CATEGORY_UNKNOWN = 0; + + /** + * @hide + * Describes an audio device which is categorized as something different. + */ + public static final int AUDIO_DEVICE_CATEGORY_OTHER = 1; + + /** + * @hide + * Describes an audio device which was categorized as speakers. + */ + public static final int AUDIO_DEVICE_CATEGORY_SPEAKER = 2; + + /** + * @hide + * Describes an audio device which was categorized as headphones. + */ + public static final int AUDIO_DEVICE_CATEGORY_HEADPHONES = 3; + + /** + * @hide + * Describes an audio device which was categorized as car-kit. + */ + public static final int AUDIO_DEVICE_CATEGORY_CARKIT = 4; + + /** + * @hide + * Describes an audio device which was categorized as watch. + */ + public static final int AUDIO_DEVICE_CATEGORY_WATCH = 5; + + /** + * @hide + * Describes an audio device which was categorized as hearing aid. + */ + public static final int AUDIO_DEVICE_CATEGORY_HEARING_AID = 6; + + /** + * @hide + * Describes an audio device which was categorized as receiver. + */ + public static final int AUDIO_DEVICE_CATEGORY_RECEIVER = 7; + + /** @hide */ + @IntDef(flag = false, prefix = "AUDIO_DEVICE_CATEGORY", value = { + AUDIO_DEVICE_CATEGORY_UNKNOWN, + AUDIO_DEVICE_CATEGORY_OTHER, + AUDIO_DEVICE_CATEGORY_SPEAKER, + AUDIO_DEVICE_CATEGORY_HEADPHONES, + AUDIO_DEVICE_CATEGORY_CARKIT, + AUDIO_DEVICE_CATEGORY_WATCH, + AUDIO_DEVICE_CATEGORY_HEARING_AID, + AUDIO_DEVICE_CATEGORY_RECEIVER } + ) + @Retention(RetentionPolicy.SOURCE) + public @interface AudioDeviceCategory {} + + /** @hide */ + public static String audioDeviceCategoryToString(int audioDeviceCategory) { + switch (audioDeviceCategory) { + case AUDIO_DEVICE_CATEGORY_UNKNOWN: return "AUDIO_DEVICE_CATEGORY_UNKNOWN"; + case AUDIO_DEVICE_CATEGORY_OTHER: return "AUDIO_DEVICE_CATEGORY_OTHER"; + case AUDIO_DEVICE_CATEGORY_SPEAKER: return "AUDIO_DEVICE_CATEGORY_SPEAKER"; + case AUDIO_DEVICE_CATEGORY_HEADPHONES: return "AUDIO_DEVICE_CATEGORY_HEADPHONES"; + case AUDIO_DEVICE_CATEGORY_CARKIT: return "AUDIO_DEVICE_CATEGORY_CARKIT"; + case AUDIO_DEVICE_CATEGORY_WATCH: return "AUDIO_DEVICE_CATEGORY_WATCH"; + case AUDIO_DEVICE_CATEGORY_HEARING_AID: return "AUDIO_DEVICE_CATEGORY_HEARING_AID"; + case AUDIO_DEVICE_CATEGORY_RECEIVER: return "AUDIO_DEVICE_CATEGORY_RECEIVER"; + default: + return new StringBuilder("unknown AudioDeviceCategory ").append( + audioDeviceCategory).toString(); + } + } + + /** + * @hide + * Sets the audio device type of a Bluetooth device given its MAC address + */ + @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + public void setBluetoothAudioDeviceCategory(@NonNull String address, boolean isBle, + @AudioDeviceCategory int btAudioDeviceType) { + try { + getService().setBluetoothAudioDeviceCategory(address, isBle, btAudioDeviceType); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + * Gets the audio device type of a Bluetooth device given its MAC address + */ + @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + @AudioDeviceCategory + public int getBluetoothAudioDeviceCategory(@NonNull String address, boolean isBle) { + try { + return getService().getBluetoothAudioDeviceCategory(address, isBle); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide * Sound dose warning at every 100% of dose during integration window */ public static final int CSD_WARNING_DOSE_REACHED_1X = 1; diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 02f765a3dab9..b2466e990b8f 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -324,6 +324,12 @@ interface IAudioService { @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED") boolean isCsdEnabled(); + @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED") + oneway void setBluetoothAudioDeviceCategory(in String address, boolean isBle, int deviceType); + + @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED") + int getBluetoothAudioDeviceCategory(in String address, boolean isBle); + int setHdmiSystemAudioSupported(boolean on); boolean isHdmiSystemAudioSupported(); diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index f664fdc949de..1d6e38d2a510 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -450,6 +450,7 @@ public final class MediaSession { * but it must be released if your activity or service is being destroyed. */ public void release() { + setCallback(null); try { mBinder.destroySession(); } catch (RemoteException e) { diff --git a/media/tests/mediatestutils/Android.bp b/media/tests/mediatestutils/Android.bp new file mode 100644 index 000000000000..15bc1774d4d4 --- /dev/null +++ b/media/tests/mediatestutils/Android.bp @@ -0,0 +1,53 @@ +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"], +} + +// TODO audio build defaults +java_library { + name: "mediatestutils_host", + host_supported: true, + srcs: [ + "java/com/android/media/mediatestutils/CancelAllFuturesRule.java", + ], + static_libs: [ + "junit", + ], + visibility: [ + ":__subpackages__", + ], +} + +java_library { + name: "mediatestutils", + srcs: [ + "java/com/android/media/mediatestutils/TestUtils.java", + ], + static_libs: [ + "androidx.concurrent_concurrent-futures", + "guava", + "mediatestutils_host", + ], + visibility: [ + "//cts/tests/tests/media:__subpackages__", + ":__subpackages__", + ], +} + +java_test_host { + name: "mediatestutilshosttests", + srcs: ["javatests/**/*.java"], + static_libs: [ + "mediatestutils_host", + "junit", + "truth", + ], + test_suites: ["general-tests"], + test_options: { + unit_test: true, + }, +} diff --git a/media/tests/mediatestutils/OWNERS b/media/tests/mediatestutils/OWNERS new file mode 100644 index 000000000000..b9eb1f82663f --- /dev/null +++ b/media/tests/mediatestutils/OWNERS @@ -0,0 +1,4 @@ +# Bug component: 48436 +atneya@google.com +jmtrivi@google.com +elaurent@google.com diff --git a/media/tests/mediatestutils/TEST_MAPPING b/media/tests/mediatestutils/TEST_MAPPING new file mode 100644 index 000000000000..6dd09ae9c501 --- /dev/null +++ b/media/tests/mediatestutils/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "mediatestutilstests" + } + ] +} diff --git a/media/tests/mediatestutils/java/com/android/media/mediatestutils/CancelAllFuturesRule.java b/media/tests/mediatestutils/java/com/android/media/mediatestutils/CancelAllFuturesRule.java new file mode 100644 index 000000000000..14e261c40d9f --- /dev/null +++ b/media/tests/mediatestutils/java/com/android/media/mediatestutils/CancelAllFuturesRule.java @@ -0,0 +1,51 @@ +/* + * Copyright 2023 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.media.mediatestutils; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Future; + +/** + * + */ +public class CancelAllFuturesRule implements TestRule { + private List<Future> mRegisteredFutures = new ArrayList<>(); + + public <T extends Future<?>> T registerFuture(T future) { + mRegisteredFutures.add(future); + return future; + } + + @Override + public Statement apply(Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + try { + base.evaluate(); + } finally { + mRegisteredFutures.forEach(f -> f.cancel(false /* shouldInterrupt */)); + } + } + }; + } +} diff --git a/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java b/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java new file mode 100644 index 000000000000..69d836d24664 --- /dev/null +++ b/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java @@ -0,0 +1,119 @@ +/* + * Copyright 2023 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.media.mediatestutils; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +import androidx.concurrent.futures.CallbackToFutureAdapter; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; + +import java.lang.ref.WeakReference; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; + +/** Utils for audio tests. */ +public class TestUtils { + /** + * Return a future for an intent delivered by a broadcast receiver which matches an + * action and predicate. + * @param context - Context to register the receiver with + * @param action - String representing action to register receiver for + * @param pred - Predicate which sets the future if evaluates to true, otherwise, leaves + * the future unset. If the predicate throws, the future is set exceptionally + * @return - The future representing intent delivery matching predicate. + */ + public static ListenableFuture<Intent> getFutureForIntent( + Context context, String action, Predicate<Intent> pred) { + // These are evaluated async + Objects.requireNonNull(action); + Objects.requireNonNull(pred); + return getFutureForListener( + (recv) -> + context.registerReceiver( + recv, new IntentFilter(action), Context.RECEIVER_NOT_EXPORTED), + (recv) -> { + try { + context.unregisterReceiver(recv); + } catch (IllegalArgumentException e) { + // Thrown when receiver is already unregistered, nothing to do + } + }, + (completer) -> + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + try { + if (action.equals(intent.getAction()) && pred.test(intent)) { + completer.set(intent); + } + } catch (Exception e) { + completer.setException(e); + } + } + }, + "Intent receiver future for action: " + action); + } + + /** + * Return a future for a callback registered to a listener interface. + * @param registerFunc - Function which consumes the callback object for registration + * @param unregisterFunc - Function which consumes the callback object for unregistration + * This function is called when the future is completed or cancelled + * @param instantiateCallback - Factory function for the callback object, provided a completer + * object (see {@code CallbackToFutureAdapter.Completer<T>}), which is a logical reference + * to the future returned by this function + * @param debug - Debug string contained in future {@code toString} representation. + */ + public static <T, V> ListenableFuture<T> getFutureForListener( + Consumer<V> registerFunc, + Consumer<V> unregisterFunc, + Function<CallbackToFutureAdapter.Completer<T>, V> instantiateCallback, + String debug) { + // Doesn't need to be thread safe since the resolver is called inline + final WeakReference<V> wrapper[] = new WeakReference[1]; + ListenableFuture<T> future = + CallbackToFutureAdapter.getFuture( + completer -> { + final var cb = instantiateCallback.apply(completer); + wrapper[0] = new WeakReference(cb); + registerFunc.accept(cb); + return debug; + }); + if (wrapper[0] == null) { + throw new AssertionError("Resolver should be called inline"); + } + final var weakref = wrapper[0]; + future.addListener( + () -> { + var cb = weakref.get(); + // If there is no reference left, the receiver has already been unregistered + if (cb != null) { + unregisterFunc.accept(cb); + return; + } + }, + MoreExecutors.directExecutor()); // Direct executor is fine since lightweight + return future; + } +} diff --git a/media/tests/mediatestutils/javatests/com/android/media/mediatestutils/CancelAllFuturesRuleTest.java b/media/tests/mediatestutils/javatests/com/android/media/mediatestutils/CancelAllFuturesRuleTest.java new file mode 100644 index 000000000000..94fa3d7847eb --- /dev/null +++ b/media/tests/mediatestutils/javatests/com/android/media/mediatestutils/CancelAllFuturesRuleTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2023 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.media.mediatestutils; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.rules.ExpectedException; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; + + +@RunWith(JUnit4.class) +public class CancelAllFuturesRuleTest { + + public static class TestException extends Throwable { } + + public static class CheckFutureStatusRule implements TestRule { + private final List<CompletableFuture> mFutures = Arrays.asList(new CompletableFuture<>(), + new CompletableFuture<>()); + + private boolean mCompleted; + + @Override + public Statement apply(Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + try { + base.evaluate(); + } finally { + // Intentionally suppresses original exception + if (mCompleted) { + assertThat(mFutures.get(0).isDone()) + .isTrue(); + assertThat(mFutures.get(0).isCancelled()) + .isFalse(); + } else { + assertThat(mFutures.get(0).isCancelled()) + .isTrue(); + } + assertThat(mFutures.get(1).isCancelled()) + .isTrue(); + } + } + }; + } + + Future getFuture(int idx) { + return mFutures.get(idx); + } + + void completeFirstFuture(boolean exceptionally) { + assertThat(mFutures.get(0).complete(null)) + .isTrue(); + mCompleted = true; + } + } + + @Rule(order = 0) + public ExpectedException mExpectedThrownRule = ExpectedException.none(); + + @Rule(order = 1) + public CheckFutureStatusRule mRuleVerifyerRule = new CheckFutureStatusRule(); + + @Rule(order = 2) + public CancelAllFuturesRule mCancelRule = new CancelAllFuturesRule(); + + @Test + public void testRuleCancelsFutures_whenFinishesNormally() { + mCancelRule.registerFuture(mRuleVerifyerRule.getFuture(0)); + mCancelRule.registerFuture(mRuleVerifyerRule.getFuture(1)); + // return normally + } + + @Test + public void testRuleCancelsFutures_whenFinishesExceptionally() throws TestException { + mExpectedThrownRule.expect(TestException.class); + mCancelRule.registerFuture(mRuleVerifyerRule.getFuture(0)); + mCancelRule.registerFuture(mRuleVerifyerRule.getFuture(1)); + throw new TestException(); + } + + @Test + public void testRuleDoesNotThrow_whenCompletesNormally() { + mCancelRule.registerFuture(mRuleVerifyerRule.getFuture(0)); + mCancelRule.registerFuture(mRuleVerifyerRule.getFuture(1)); + mRuleVerifyerRule.completeFirstFuture(false); + } + + @Test + public void testRuleDoesNotThrow_whenCompletesExceptionally() { + mCancelRule.registerFuture(mRuleVerifyerRule.getFuture(0)); + mCancelRule.registerFuture(mRuleVerifyerRule.getFuture(1)); + mRuleVerifyerRule.completeFirstFuture(false); + } +} diff --git a/media/tests/mediatestutils/tests/Android.bp b/media/tests/mediatestutils/tests/Android.bp new file mode 100644 index 000000000000..24a8360d1187 --- /dev/null +++ b/media/tests/mediatestutils/tests/Android.bp @@ -0,0 +1,22 @@ +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: "mediatestutilstests", + srcs: ["src/**/*.java"], + static_libs: [ + "mockito-target-minus-junit4", + "androidx.test.runner", + "androidx.test.core", + "mediatestutils", + "junit", + "truth", + ], + test_suites: ["general-tests"], +} diff --git a/media/tests/mediatestutils/tests/AndroidManifest.xml b/media/tests/mediatestutils/tests/AndroidManifest.xml new file mode 100644 index 000000000000..662bc45abc5d --- /dev/null +++ b/media/tests/mediatestutils/tests/AndroidManifest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.media.mediatestutils"> + + <application android:testOnly="false" + android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.media.mediatestutils" + android:label="mediatestutils tests" /> + +</manifest> diff --git a/media/tests/mediatestutils/tests/src/java/com/android/media/mediatestutils/GetFutureForIntentTest.java b/media/tests/mediatestutils/tests/src/java/com/android/media/mediatestutils/GetFutureForIntentTest.java new file mode 100644 index 000000000000..a4266a33f522 --- /dev/null +++ b/media/tests/mediatestutils/tests/src/java/com/android/media/mediatestutils/GetFutureForIntentTest.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2023 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.media.mediatestutils; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; + +import static com.android.media.mediatestutils.TestUtils.getFutureForIntent; +import static com.android.media.mediatestutils.TestUtils.getFutureForListener; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.fail; + +import android.content.Context; +import android.content.Intent; +import android.os.SystemClock; + +import androidx.test.runner.AndroidJUnit4; + +import com.google.common.util.concurrent.ListenableFuture; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.ExecutionException; +import java.util.function.Predicate; + +@RunWith(AndroidJUnit4.class) +public class GetFutureForIntentTest { + public static final String INTENT_ACTION = "com.android.media.mediatestutils.TEST_ACTION"; + public static final String INTENT_EXTRA = "com.android.media.mediatestutils.TEST_EXTRA"; + public static final int MAGIC_VALUE = 7; + + public final Context mContext = getApplicationContext(); + public final Predicate<Intent> mPred = + i -> (i != null) && (i.getIntExtra(INTENT_EXTRA, -1) == MAGIC_VALUE); + + @Test + public void futureCompletes_afterBroadcastFiresPredicatePasses() throws Exception { + final var future = getFutureForIntent(mContext, INTENT_ACTION, mPred); + sendIntent(true); + var intent = future.get(); + assertThat(intent.getAction()).isEqualTo(INTENT_ACTION); + assertThat(intent.getIntExtra(INTENT_EXTRA, -1)).isEqualTo(MAGIC_VALUE); + } + + @Test + public void futureDoesNotComplete_afterBroadcastFiresPredicateFails() throws Exception { + final var future = getFutureForIntent(mContext, INTENT_ACTION, mPred); + sendIntent(false); + + // Wait a bit, and ensure the future hasn't completed + SystemClock.sleep(100); + assertThat(future.isDone()).isFalse(); + + // Future should still respond to subsequent passing intent + sendIntent(true); + var intent = future.get(); + assertThat(intent.getAction()).isEqualTo(INTENT_ACTION); + assertThat(intent.getIntExtra(INTENT_EXTRA, -1)).isEqualTo(MAGIC_VALUE); + } + + @Test + public void futureCompletesExceptionally_afterBroadcastFiresPredicateThrows() throws Exception { + final var future = + getFutureForIntent( + mContext, + INTENT_ACTION, + i -> { + throw new IllegalStateException(); + }); + + sendIntent(true); + try { + var intent = future.get(); + fail("Exception expected if predicate throws"); + } catch (ExecutionException e) { + assertThat(e.getCause().getClass()).isEqualTo(IllegalStateException.class); + } + } + + @Test + public void doesNotThrow_whenDoubleSet() throws Exception { + final var future = getFutureForIntent(mContext, INTENT_ACTION, mPred); + sendIntent(true); + sendIntent(true); + var intent = future.get(); + assertThat(intent.getAction()).isEqualTo(INTENT_ACTION); + assertThat(intent.getIntExtra(INTENT_EXTRA, -1)).isEqualTo(MAGIC_VALUE); + } + + @Test + public void unregisterListener_whenComplete() throws Exception { + final var service = new FakeService(); + final ListenableFuture<Void> future = + getFutureForListener( + service::registerListener, + service::unregisterListener, + completer -> + () -> { + completer.set(null); + }, + "FakeService listener future"); + service.mRunnable.run(); + assertThat(service.mRunnable).isNull(); + } + + @Test + public void unregisterListener_whenCancel() throws Exception { + final var service = new FakeService(); + final ListenableFuture<Void> future = + getFutureForListener( + service::registerListener, + service::unregisterListener, + completer -> + () -> { + completer.set(null); + }, + "FakeService listener future"); + future.cancel(false); + assertThat(service.mRunnable).isNull(); + } + + private static class FakeService { + Runnable mRunnable; + + void registerListener(Runnable r) { + mRunnable = r; + } + + void unregisterListener(Runnable r) { + assertThat(r).isEqualTo(mRunnable); + mRunnable = null; + } + } + + private void sendIntent(boolean correctValue) { + final Intent intent = new Intent(INTENT_ACTION).setPackage(mContext.getPackageName()); + intent.putExtra(INTENT_EXTRA, correctValue ? MAGIC_VALUE : MAGIC_VALUE + 1); + mContext.sendBroadcast(intent); + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java index b66679af3fe3..df91d98b8360 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -16,9 +16,7 @@ */ package com.android.packageinstaller; -import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.content.Intent.FLAG_ACTIVITY_NO_HISTORY; -import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import android.Manifest; @@ -808,8 +806,12 @@ public class PackageInstallerActivity extends AlertActivity { } new Handler(Looper.getMainLooper()).postDelayed(() -> { if (!isDestroyed()) { - startActivity(getIntent().addFlags( - FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP)); + startActivity(getIntent()); + // The start flag (FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP) doesn't + // work for the multiple user case, i.e. the caller task user and started + // Activity user are not the same. To avoid having multiple PIAs in the task, + // finish the current PackageInstallerActivity + finish(); } }, 500); diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt index e2ff7b0bc2f8..a370ebfe21d5 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt @@ -22,7 +22,6 @@ import android.os.UserHandle import android.os.UserManager import androidx.compose.runtime.Composable import androidx.compose.runtime.State -import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.settingslib.RestrictedLockUtils import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin @@ -90,7 +89,6 @@ internal class RestrictionsProviderImpl( emit(getRestrictedMode()) }.flowOn(Dispatchers.IO) - @OptIn(ExperimentalLifecycleComposeApi::class) @Composable override fun restrictedModeState() = restrictedMode.collectAsStateWithLifecycle(initialValue = null) diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt index 6831e56596cc..ab34f6820014 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt @@ -23,10 +23,12 @@ import android.content.pm.PackageManager import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.hasText import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithText import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.testutils.waitUntilExists import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -51,7 +53,7 @@ class AppInfoTest { } } - composeTestRule.onNodeWithText(LABEL).assertIsDisplayed() + composeTestRule.waitUntilExists(hasText(LABEL)) } @Test diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt index 241a134b6a76..124ced6f88db 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt @@ -21,14 +21,16 @@ import android.content.pm.ApplicationInfo import android.icu.text.CollationKey import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.hasText import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.unit.dp import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.android.settingslib.spa.framework.compose.toState +import com.android.settingslib.spa.framework.compose.stateOf import com.android.settingslib.spa.widget.ui.SpinnerOption import com.android.settingslib.spaprivileged.R import com.android.settingslib.spaprivileged.model.app.AppEntry @@ -42,6 +44,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +@OptIn(ExperimentalTestApi::class) @RunWith(AndroidJUnit4::class) class AppListTest { @get:Rule @@ -53,7 +56,10 @@ class AppListTest { fun whenHasOptions_firstOptionDisplayed() { setContent(options = listOf(OPTION_0, OPTION_1)) - composeTestRule.onNodeWithText(OPTION_0).assertIsDisplayed() + composeTestRule.waitUntilExactlyOneExists( + matcher = hasText(OPTION_0), + timeoutMillis = 5_000, + ) composeTestRule.onNodeWithText(OPTION_1).assertDoesNotExist() } @@ -61,6 +67,10 @@ class AppListTest { fun whenHasOptions_couldSwitchOption() { setContent(options = listOf(OPTION_0, OPTION_1)) + composeTestRule.waitUntilExactlyOneExists( + matcher = hasText(OPTION_0), + timeoutMillis = 5_000, + ) composeTestRule.onNodeWithText(OPTION_0).performClick() composeTestRule.onNodeWithText(OPTION_1).performClick() @@ -72,22 +82,30 @@ class AppListTest { fun whenNoApps() { setContent(appEntries = emptyList()) - composeTestRule.onNodeWithText(context.getString(R.string.no_applications)) - .assertIsDisplayed() + composeTestRule.waitUntilExactlyOneExists( + matcher = hasText(context.getString(R.string.no_applications)), + timeoutMillis = 5_000, + ) } @Test fun couldShowAppItem() { setContent(appEntries = listOf(APP_ENTRY_A)) - composeTestRule.onNodeWithText(APP_ENTRY_A.label).assertIsDisplayed() + composeTestRule.waitUntilExactlyOneExists( + matcher = hasText(APP_ENTRY_A.label), + timeoutMillis = 5_000, + ) } @Test fun couldShowHeader() { setContent(appEntries = listOf(APP_ENTRY_A), header = { Text(HEADER) }) - composeTestRule.onNodeWithText(HEADER).assertIsDisplayed() + composeTestRule.waitUntilExactlyOneExists( + matcher = hasText(HEADER), + timeoutMillis = 5_000, + ) } @Test @@ -102,7 +120,10 @@ class AppListTest { fun whenGrouped_groupTitleDisplayed() { setContent(appEntries = listOf(APP_ENTRY_A, APP_ENTRY_B), enableGrouping = true) - composeTestRule.onNodeWithText(GROUP_A).assertIsDisplayed() + composeTestRule.waitUntilExactlyOneExists( + matcher = hasText(GROUP_A), + timeoutMillis = 5_000, + ) composeTestRule.onNodeWithText(GROUP_B).assertIsDisplayed() } @@ -112,29 +133,26 @@ class AppListTest { header: @Composable () -> Unit = {}, enableGrouping: Boolean = false, ) { + val appListInput = AppListInput( + config = AppListConfig( + userIds = listOf(USER_ID), + showInstantApps = false, + matchAnyUserForAdmin = false, + ), + listModel = TestAppListModel(enableGrouping = enableGrouping), + state = AppListState(showSystem = stateOf(false), searchQuery = stateOf("")), + header = header, + bottomPadding = 0.dp, + ) + val listViewModel = object : IAppListViewModel<TestAppRecord> { + override val optionFlow = MutableStateFlow<Int?>(null) + override val spinnerOptionsFlow = flowOf(options.mapIndexed { index, option -> + SpinnerOption(id = index, text = option) + }) + override val appListDataFlow = flowOf(AppListData(appEntries, option = 0)) + } composeTestRule.setContent { - AppListInput( - config = AppListConfig( - userIds = listOf(USER_ID), - showInstantApps = false, - matchAnyUserForAdmin = false, - ), - listModel = TestAppListModel(enableGrouping = enableGrouping), - state = AppListState( - showSystem = false.toState(), - searchQuery = "".toState(), - ), - header = header, - bottomPadding = 0.dp, - ).AppListImpl { - object : IAppListViewModel<TestAppRecord> { - override val optionFlow: MutableStateFlow<Int?> = MutableStateFlow(null) - override val spinnerOptionsFlow = flowOf(options.mapIndexed { index, option -> - SpinnerOption(id = index, text = option) - }) - override val appListDataFlow = flowOf(AppListData(appEntries, option = 0)) - } - } + appListInput.AppListImpl { listViewModel } } } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index 46e73d096c0b..58106c05affa 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -442,5 +442,6 @@ public class GlobalSettingsValidators { })); VALIDATORS.put(Global.Wearable.PHONE_SWITCHING_SUPPORTED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.Wearable.WEAR_LAUNCHER_UI_MODE, NON_NEGATIVE_INTEGER_VALIDATOR); + VALIDATORS.put(Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED, BOOLEAN_VALIDATOR); } } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 9c9dd8a6c39e..61afb5b84247 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -689,7 +689,8 @@ public class SettingsBackupTest { Settings.Global.Wearable.TETHER_CONFIG_STATE, Settings.Global.Wearable.PHONE_SWITCHING_SUPPORTED, Settings.Global.Wearable.WEAR_MEDIA_CONTROLS_PACKAGE, - Settings.Global.Wearable.WEAR_MEDIA_SESSIONS_PACKAGE); + Settings.Global.Wearable.WEAR_MEDIA_SESSIONS_PACKAGE, + Settings.Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED); private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS = newHashSet( diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 0474849778d9..29999c240d4c 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -132,6 +132,17 @@ android_library { manifest: "AndroidManifest-res.xml", } +aconfig_declarations { + name: "systemui_aconfig_flags", + package: "com.android.systemui.aconfig", + srcs: ["src/com/android/systemui/aconfig/systemui.aconfig"], +} + +java_aconfig_library { + name: "systemui_aconfig_flags_lib", + aconfig_declarations: "systemui_aconfig_flags", +} + android_library { name: "SystemUI-core", defaults: [ @@ -166,6 +177,7 @@ android_library { "SystemUISharedLib", "SystemUI-statsd", "SettingsLib", + "systemui_aconfig_flags_lib", "androidx.core_core-ktx", "androidx.viewpager2_viewpager2", "androidx.legacy_legacy-support-v4", @@ -201,7 +213,6 @@ android_library { "lottie", "LowLightDreamLib", "motion_tool_lib", - "IntentResolver-core", ], manifest: "AndroidManifest.xml", @@ -344,6 +355,7 @@ android_library { "SystemUICustomizationLib", "SystemUI-statsd", "SettingsLib", + "systemui_aconfig_flags_lib", "androidx.viewpager2_viewpager2", "androidx.legacy_legacy-support-v4", "androidx.recyclerview_recyclerview", @@ -385,7 +397,6 @@ android_library { "motion_tool_lib", "androidx.core_core-animation-testing-nodeps", "androidx.compose.ui_ui", - "IntentResolver-core", ], } @@ -408,6 +419,7 @@ android_library { static_libs: [ "SystemUI-tests-base", "androidx.test.uiautomator_uiautomator", + "androidx.core_core-animation-testing", "mockito-target-extended-minus-junit4", "androidx.test.ext.junit", "androidx.test.ext.truth", @@ -477,6 +489,7 @@ android_robolectric_test { ], static_libs: [ "androidx.test.uiautomator_uiautomator", + "androidx.core_core-animation-testing", "androidx.test.ext.junit", "inline-mockito-robolectric-prebuilt", ], diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 58c9f777de33..6778d5a08506 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -562,7 +562,7 @@ android:exported="true" android:launchMode="singleTop" android:permission="android.permission.MANAGE_SENSOR_PRIVACY" - android:theme="@style/Theme.SystemUI.Dialog.Alert" + android:theme="@style/Theme.SystemUI.Dialog.Alert.SensorPrivacy" android:finishOnCloseSystemDialogs="true" android:showForAllUsers="true"> </activity> diff --git a/packages/SystemUI/compose/core/OWNERS b/packages/SystemUI/compose/core/OWNERS new file mode 100644 index 000000000000..7e37f4fba538 --- /dev/null +++ b/packages/SystemUI/compose/core/OWNERS @@ -0,0 +1,12 @@ +set noparent + +# Bug component: 1184816 + +jdemeulenaere@google.com +nijamkin@google.com + +# Don't send reviews here. +dsandler@android.com +cinek@google.com +juliacr@google.com +pixel@google.com diff --git a/packages/SystemUI/compose/core/src/com/android/compose/activity/EdgeToEdgeActivitContent.kt b/packages/SystemUI/compose/core/src/com/android/compose/activity/EdgeToEdgeActivitContent.kt new file mode 100644 index 000000000000..97c8076e910b --- /dev/null +++ b/packages/SystemUI/compose/core/src/com/android/compose/activity/EdgeToEdgeActivitContent.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2023 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.compose.activity + +import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.contentColorFor +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.DisposableEffect +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import com.android.compose.rememberSystemUiController +import com.android.compose.theme.PlatformTheme + +/** Scaffolding for an edge-to-edge activity content. */ +@Composable +fun EdgeToEdgeActivityContent( + modifier: Modifier = Modifier, + content: @Composable () -> Unit, +) { + // Make the status and navigation bars transparent, ensuring that the status bar icons are dark + // when the theme is light and vice-versa. + val systemUiController = rememberSystemUiController() + val isDarkTheme = isSystemInDarkTheme() + val useDarkIcons = !isDarkTheme + DisposableEffect(systemUiController, useDarkIcons) { + systemUiController.setSystemBarsColor( + color = Color.Transparent, + darkIcons = useDarkIcons, + ) + onDispose {} + } + + PlatformTheme(isDarkTheme) { + val backgroundColor = MaterialTheme.colorScheme.background + Box(modifier.fillMaxSize().background(backgroundColor)) { + CompositionLocalProvider(LocalContentColor provides contentColorFor(backgroundColor)) { + content() + } + } + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt b/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt deleted file mode 100644 index c58c16259abe..000000000000 --- a/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt +++ /dev/null @@ -1,94 +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 - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.BoxWithConstraints -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import kotlin.math.roundToInt - -/** - * This is an example Compose feature, which shows a text and a count that is incremented when - * clicked. We also show the max width available to this component, which is displayed either next - * to or below the text depending on that max width. - */ -@Composable -fun ExampleFeature(text: String, modifier: Modifier = Modifier) { - BoxWithConstraints(modifier) { - val maxWidth = maxWidth - if (maxWidth < 600.dp) { - Column { - CounterTile(text) - Spacer(Modifier.size(16.dp)) - MaxWidthTile(maxWidth) - } - } else { - Row { - CounterTile(text) - Spacer(Modifier.size(16.dp)) - MaxWidthTile(maxWidth) - } - } - } -} - -@Composable -private fun CounterTile(text: String, modifier: Modifier = Modifier) { - Surface( - modifier, - color = MaterialTheme.colorScheme.primaryContainer, - shape = RoundedCornerShape(28.dp), - ) { - var count by remember { mutableStateOf(0) } - Column( - Modifier.clickable { count++ }.padding(16.dp), - ) { - Text(text) - Text("I was clicked $count times.") - } - } -} - -@Composable -private fun MaxWidthTile(maxWidth: Dp, modifier: Modifier = Modifier) { - Surface( - modifier, - color = MaterialTheme.colorScheme.tertiaryContainer, - shape = RoundedCornerShape(28.dp), - ) { - Text( - "The max available width to me is: ${maxWidth.value.roundToInt()}dp", - Modifier.padding(16.dp) - ) - } -} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt index ca7352ef2501..da48762e1960 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt @@ -63,7 +63,7 @@ constructor( .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, - initialValue = destinationScenes(up = viewModel.upDestinationSceneKey.value) + initialValue = destinationScenes(up = null) ) @Composable @@ -77,12 +77,12 @@ constructor( } private fun destinationScenes( - up: SceneKey, + up: SceneKey?, ): Map<UserAction, SceneModel> { - return mapOf( - UserAction.Swipe(Direction.UP) to SceneModel(up), - UserAction.Swipe(Direction.DOWN) to SceneModel(SceneKey.Shade) - ) + return buildMap { + up?.let { this[UserAction.Swipe(Direction.UP)] = SceneModel(up) } + this[UserAction.Swipe(Direction.DOWN)] = SceneModel(SceneKey.Shade) + } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt index d84e67620177..68f010e1c50d 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt @@ -42,13 +42,10 @@ import androidx.compose.runtime.key import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.asImageBitmap -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.repeatOnLifecycle import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.R import com.android.systemui.compose.modifiers.sysuiResTag @@ -70,15 +67,6 @@ fun PeopleScreen( val priorityTiles by viewModel.priorityTiles.collectAsState() val recentTiles by viewModel.recentTiles.collectAsState() - // Make sure to refresh the tiles/conversations when the lifecycle is resumed, so that it - // updates them when going back to the Activity after leaving it. - val lifecycleOwner = LocalLifecycleOwner.current - LaunchedEffect(lifecycleOwner, viewModel) { - lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { - viewModel.onTileRefreshRequested() - } - } - // Call [onResult] this activity when the ViewModel tells us so. LaunchedEffect(viewModel.result) { viewModel.result.collect { result -> diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt index 3dfdbbaaee77..f91baf298347 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt @@ -77,7 +77,7 @@ fun SceneContainer( SceneTransitionLayout( currentScene = currentSceneKey.toTransitionSceneKey(), - onChangeScene = { sceneKey -> viewModel.setCurrentScene(sceneKey.toModel()) }, + onChangeScene = viewModel::onSceneChanged, transitions = transitions {}, state = state, modifier = modifier.fillMaxSize(), @@ -154,3 +154,7 @@ private fun UserAction.toTransitionUserAction(): SceneTransitionUserAction { is UserAction.Back -> Back } } + +private fun SceneContainerViewModel.onSceneChanged(sceneKey: SceneTransitionSceneKey) { + onSceneChanged(sceneKey.toModel()) +} diff --git a/packages/SystemUI/compose/features/tests/src/com/android/systemui/ExampleFeatureTest.kt b/packages/SystemUI/compose/features/tests/src/com/android/systemui/ExampleFeatureTest.kt deleted file mode 100644 index 1c2e8fab0337..000000000000 --- a/packages/SystemUI/compose/features/tests/src/com/android/systemui/ExampleFeatureTest.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 - -import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.junit4.createComposeRule -import androidx.compose.ui.test.onNodeWithText -import androidx.compose.ui.test.performClick -import androidx.test.ext.junit.runners.AndroidJUnit4 -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class ExampleFeatureTest { - @get:Rule val composeRule = createComposeRule() - - @Test - fun testProvidedTextIsDisplayed() { - composeRule.setContent { ExampleFeature("foo") } - - composeRule.onNodeWithText("foo").assertIsDisplayed() - } - - @Test - fun testCountIsIncreasedWhenClicking() { - composeRule.setContent { ExampleFeature("foo") } - - composeRule.onNodeWithText("I was clicked 0 times.").assertIsDisplayed().performClick() - composeRule.onNodeWithText("I was clicked 1 times.").assertIsDisplayed() - } -} diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml index 8f1323db299d..a1e2dc36278b 100644 --- a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml +++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml @@ -54,7 +54,7 @@ </FrameLayout> <ImageView android:id="@+id/mobile_type" - android:layout_height="@dimen/status_bar_mobile_signal_size" + android:layout_height="@dimen/status_bar_mobile_type_size" android:layout_width="wrap_content" android:layout_gravity="center_vertical" android:adjustViewBounds="true" @@ -74,13 +74,15 @@ <com.android.systemui.statusbar.AnimatedImageView android:id="@+id/mobile_signal" android:layout_height="@dimen/status_bar_mobile_signal_size" - android:layout_width="@dimen/status_bar_mobile_signal_size" + android:layout_width="wrap_content" + android:adjustViewBounds="true" systemui:hasOverlappingRendering="false" /> <ImageView android:id="@+id/mobile_roaming" - android:layout_width="@dimen/status_bar_mobile_signal_size" - android:layout_height="@dimen/status_bar_mobile_signal_size" + android:layout_width="wrap_content" + android:layout_height="@dimen/status_bar_mobile_roam_size" + android:adjustViewBounds="true" android:layout_gravity="top|start" android:src="@drawable/stat_sys_roaming" android:contentDescription="@string/data_connection_roaming" diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml index f3a6bbeaaf0e..12f13e9a2138 100644 --- a/packages/SystemUI/res/layout/combined_qs_header.xml +++ b/packages/SystemUI/res/layout/combined_qs_header.xml @@ -133,7 +133,8 @@ frame when animating QS <-> QQS transition <com.android.systemui.statusbar.phone.StatusIconContainer android:id="@+id/statusIcons" - android:layout_width="wrap_content" + android:layout_width="0dp" + android:layout_weight="1" android:layout_height="wrap_content" android:paddingEnd="@dimen/signal_cluster_battery_padding" /> diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml index 66c57fc2a9ac..36f7b96a267c 100644 --- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml +++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml @@ -22,42 +22,6 @@ android:layout_width="match_parent" android:outlineProvider="none" > - <LinearLayout - android:id="@id/keyguard_indication_area" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/keyguard_indication_margin_bottom" - android:layout_gravity="bottom|center_horizontal" - android:orientation="vertical"> - - <com.android.systemui.statusbar.phone.KeyguardIndicationTextView - android:id="@id/keyguard_indication_text" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="center" - android:paddingStart="@dimen/keyguard_indication_text_padding" - android:paddingEnd="@dimen/keyguard_indication_text_padding" - android:textAppearance="@style/TextAppearance.Keyguard.BottomArea" - android:accessibilityLiveRegion="polite"/> - - <com.android.systemui.statusbar.phone.KeyguardIndicationTextView - android:id="@id/keyguard_indication_text_bottom" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:gravity="center" - android:minHeight="@dimen/keyguard_indication_text_min_height" - android:layout_gravity="center_horizontal" - android:paddingStart="@dimen/keyguard_indication_text_padding" - android:paddingEnd="@dimen/keyguard_indication_text_padding" - android:textAppearance="@style/TextAppearance.Keyguard.BottomArea" - android:maxLines="2" - android:ellipsize="end" - android:alpha=".8" - android:accessibilityLiveRegion="polite" - android:visibility="gone"/> - - </LinearLayout> - <com.android.systemui.animation.view.LaunchableImageView android:id="@+id/start_button" android:layout_height="@dimen/keyguard_affordance_fixed_height" diff --git a/packages/SystemUI/res/layout/log_access_user_consent_dialog_permission.xml b/packages/SystemUI/res/layout/log_access_user_consent_dialog_permission.xml index 89e36ac93387..d647c9b9f2ee 100644 --- a/packages/SystemUI/res/layout/log_access_user_consent_dialog_permission.xml +++ b/packages/SystemUI/res/layout/log_access_user_consent_dialog_permission.xml @@ -16,22 +16,29 @@ ** limitations under the License. */ --> -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="380dp" - android:layout_height="match_parent" - android:clipToPadding="false"> - <LinearLayout +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="380dp" + android:layout_height="match_parent" + android:clipToPadding="false" + android:orientation="vertical"> + + <ScrollView + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1"> + + <LinearLayout android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_height="wrap_content" android:orientation="vertical" - android:gravity="center" android:paddingLeft="24dp" android:paddingRight="24dp" android:paddingTop="24dp" - android:paddingBottom="24dp"> + android:paddingBottom="24dp" + android:gravity="center"> - <ImageView + <ImageView android:id="@+id/log_access_image_view" android:layout_width="32dp" android:layout_height="32dp" @@ -41,7 +48,7 @@ tools:layout_editor_absoluteY="35dp" android:gravity="center" /> - <TextView + <TextView android:id="@+id/log_access_dialog_title" android:layout_height="wrap_content" android:layout_width="wrap_content" @@ -51,7 +58,7 @@ android:textColor="?android:attr/textColorPrimary" android:gravity="center" /> - <TextView + <TextView android:id="@+id/log_access_dialog_body" android:layout_height="wrap_content" android:layout_width="wrap_content" @@ -60,42 +67,37 @@ android:textAppearance="@style/PrimaryAllowLogAccess" android:textColor="?android:attr/textColorPrimary" android:gravity="center" /> + </LinearLayout> + </ScrollView> - <Space - android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" /> + <LinearLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingLeft="24dp" + android:paddingRight="24dp" + android:paddingTop="24dp" + android:paddingBottom="24dp"> - <LinearLayout - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - <Button - android:id="@+id/log_access_dialog_allow_button" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/log_access_confirmation_allow" - style="?permissionGrantButtonTopStyle" - android:textAppearance="@style/PermissionGrantButtonTextAppearance" - android:layout_marginBottom="5dp" - android:layout_centerHorizontal="true" - android:layout_alignParentTop="true" - android:layout_alignParentBottom="true" - android:clipToOutline="true" - android:gravity="center" /> + <Button + android:id="@+id/log_access_dialog_allow_button" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/log_access_confirmation_allow" + style="?permissionGrantButtonTopStyle" + android:textAppearance="@style/PermissionGrantButtonTextAppearance" + android:layout_marginBottom="5dp" + android:clipToOutline="true" + android:gravity="center" /> - <Button - android:id="@+id/log_access_dialog_deny_button" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/log_access_confirmation_deny" - style="?permissionGrantButtonBottomStyle" - android:textAppearance="@style/PermissionGrantButtonTextAppearance" - android:layout_centerHorizontal="true" - android:layout_alignParentTop="true" - android:layout_alignParentBottom="true" - android:clipToOutline="true" - android:gravity="center" /> - </LinearLayout> + <Button + android:id="@+id/log_access_dialog_deny_button" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/log_access_confirmation_deny" + style="?permissionGrantButtonBottomStyle" + android:textAppearance="@style/PermissionGrantButtonTextAppearance" + android:clipToOutline="true" + android:gravity="center" /> </LinearLayout> -</ScrollView> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/media_projection_app_selector.xml b/packages/SystemUI/res/layout/media_projection_app_selector.xml index 5404cfad25c5..e4749381243a 100644 --- a/packages/SystemUI/res/layout/media_projection_app_selector.xml +++ b/packages/SystemUI/res/layout/media_projection_app_selector.xml @@ -14,7 +14,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<com.android.intentresolver.widget.ResolverDrawerLayout +<com.android.internal.widget.ResolverDrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:layout_width="match_parent" @@ -84,7 +84,7 @@ android:id="@*android:id/tabcontent" android:layout_width="match_parent" android:layout_height="wrap_content"> - <com.android.intentresolver.ResolverViewPager + <com.android.internal.app.ResolverViewPager android:id="@*android:id/profile_pager" android:layout_width="match_parent" android:layout_height="wrap_content"/> @@ -92,4 +92,4 @@ </LinearLayout> </TabHost> -</com.android.intentresolver.widget.ResolverDrawerLayout> +</com.android.internal.widget.ResolverDrawerLayout> diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml index 909f19ffa519..59fcf0ea366c 100644 --- a/packages/SystemUI/res/layout/status_bar.xml +++ b/packages/SystemUI/res/layout/status_bar.xml @@ -67,6 +67,7 @@ android:id="@+id/status_bar_start_side_content" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_gravity="center_vertical|start" android:clipChildren="false"> <include layout="@layout/heads_up_status_bar_layout" /> @@ -88,7 +89,8 @@ <com.android.systemui.statusbar.policy.Clock android:id="@+id/clock" android:layout_width="wrap_content" - android:layout_height="match_parent" + android:layout_height="@dimen/status_bar_system_icons_height" + android:layout_gravity="center_vertical" android:textAppearance="@style/TextAppearance.StatusBar.Clock" android:singleLine="true" android:paddingStart="@dimen/status_bar_left_clock_starting_padding" @@ -146,7 +148,10 @@ android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin" layout="@layout/status_bar_user_chip_container" /> - <include layout="@layout/system_icons" /> + <include layout="@layout/system_icons" + android:layout_gravity="center_vertical" + android:layout_width="wrap_content" + android:layout_height="@dimen/status_bar_system_icons_height" /> </com.android.keyguard.AlphaOptimizedLinearLayout> </FrameLayout> </LinearLayout> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 34f8f2d89be2..572a7fe7b8fe 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -121,6 +121,9 @@ <dimen name="navigation_edge_cancelled_arrow_height">0dp</dimen> <dimen name="navigation_edge_cancelled_edge_corners">6dp</dimen> + <!-- Height of the system icons container view in the status bar --> + <dimen name="status_bar_system_icons_height">@dimen/status_bar_icon_size_sp</dimen> + <!-- New sp height of notification icons in the status bar --> <dimen name="status_bar_icon_size_sp">@*android:dimen/status_bar_icon_size_sp</dimen> <!-- Original dp height of notification icons in the status bar --> @@ -162,13 +165,21 @@ <!-- Size of the view displaying the wifi inout icon in the status bar. --> <dimen name="status_bar_wifi_inout_container_size">17sp</dimen> - <!-- Size of the view displaying the wifi signal icon in the status bar. --> - <dimen name="status_bar_wifi_signal_size">13sp</dimen> + <!-- Size of the view displaying the wifi signal icon in the status bar. This value should + match the core/status_bar_system_icon_size and change to sp unit --> + <dimen name="status_bar_wifi_signal_size">15sp</dimen> <!-- Size of the view displaying the mobile inout icon in the status bar. --> <dimen name="status_bar_mobile_inout_container_size">17sp</dimen> - <!-- Size of the view displaying the mobile signal icon in the status bar. --> - <dimen name="status_bar_mobile_signal_size">13sp</dimen> + <!-- Size of the view displaying the mobile signal icon in the status bar. This value should + match the core/status_bar_system_icon_size and change to sp unit --> + <dimen name="status_bar_mobile_signal_size">15sp</dimen> + <!-- Size of the view displaying the mobile signal icon in the status bar. This value should + match the viewport height of mobile signal drawables such as ic_lte_mobiledata --> + <dimen name="status_bar_mobile_type_size">16sp</dimen> + <!-- Size of the view displaying the mobile roam icon in the status bar. This value should + match the viewport size of drawable stat_sys_roaming --> + <dimen name="status_bar_mobile_roam_size">8sp</dimen> <!-- Spacing before the airplane mode icon if there are any icons preceding it. --> <dimen name="status_bar_airplane_spacer_width">4sp</dimen> @@ -343,8 +354,8 @@ <dimen name="status_bar_icons_padding_start">11dp</dimen> <dimen name="status_bar_icons_padding_end">0dp</dimen> - <dimen name="status_bar_icons_padding_bottom">8dp</dimen> - <dimen name="status_bar_icons_padding_top">8dp</dimen> + <dimen name="status_bar_icons_padding_bottom">0dp</dimen> + <dimen name="status_bar_icons_padding_top">0dp</dimen> <!-- gap on either side of status bar notification icons --> <dimen name="status_bar_icon_horizontal_margin">0sp</dimen> diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml index 763930db1d55..c2dba6c41d12 100644 --- a/packages/SystemUI/res/values/flags.xml +++ b/packages/SystemUI/res/values/flags.xml @@ -38,4 +38,6 @@ protected. --> <bool name="flag_battery_shield_icon">false</bool> + <!-- Whether face auth will immediately stop when the display state is OFF --> + <bool name="flag_stop_face_auth_on_display_off">false</bool> </resources> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 15ca9d48c62a..d2cb475ad2b0 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -212,6 +212,7 @@ <item type="id" name="keyguard_indication_text" /> <item type="id" name="keyguard_indication_text_bottom" /> <item type="id" name="nssl_guideline" /> + <item type="id" name="split_shade_guideline" /> <item type="id" name="lock_icon" /> <item type="id" name="lock_icon_bg" /> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index ee9b132facbe..0befb3b82196 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3053,13 +3053,6 @@ <string name="wallet_quick_affordance_unavailable_configure_the_app">To add the Wallet app as a shortcut, make sure at least one card has been added</string> <!-- - Requirement for the QR code scanner functionality to be available for the user to use. This is - shown as part of a bulleted list of requirements. When all requirements are met, the piece of - functionality can be accessed through a shortcut button on the lock screen. [CHAR LIMIT=NONE]. - --> - <string name="qr_scanner_quick_affordance_unavailable_explanation">To add the QR code scanner as a shortcut, make sure a camera app is installed</string> - - <!-- Explains that the lock screen shortcut for the "home" app is not available because the app isn't installed. This is shown as part of a dialog that explains to the user why they cannot select this shortcut for their lock screen right now. [CHAR LIMIT=NONE]. diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index d520670ec012..10340c6847f7 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -451,6 +451,11 @@ <style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert" /> + <style name="Theme.SystemUI.Dialog.Alert.SensorPrivacy" parent="Theme.SystemUI.Dialog.Alert"> + <item name="android:windowNoTitle">true</item> + <item name="android:windowContentOverlay">@null</item> + </style> + <style name="Theme.SystemUI.Dialog.GlobalActions" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar.Fullscreen"> <item name="android:colorError">@*android:color/error_color_material_dark</item> <item name="android:windowIsFloating">true</item> diff --git a/packages/SystemUI/res/xml/qs_header.xml b/packages/SystemUI/res/xml/qs_header.xml index 7b4282f049b8..427fd87c640a 100644 --- a/packages/SystemUI/res/xml/qs_header.xml +++ b/packages/SystemUI/res/xml/qs_header.xml @@ -83,6 +83,7 @@ android:layout_width="0dp" android:layout_height="@dimen/new_qs_header_non_clickable_element_height" app:layout_constraintWidth_default="wrap" + app:layout_constraintStart_toEndOf="@id/date" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@id/date" app:layout_constraintBottom_toBottomOf="@id/date" diff --git a/packages/SystemUI/shared/res/values-my/bools.xml b/packages/SystemUI/shared/res/values-my/bools.xml new file mode 100644 index 000000000000..27cbe80ae54c --- /dev/null +++ b/packages/SystemUI/shared/res/values-my/bools.xml @@ -0,0 +1,25 @@ +<?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. +--> + +<!-- Formatting note: terminate all comments with a period, to avoid breaking + the documentation output. To suppress comment lines from the documentation + output, insert an eat-comment element after the comment lines. +--> + +<resources> + <!-- Whether to add padding at the bottom of the complication clock --> + <bool name="dream_overlay_complication_clock_bottom_padding">true</bool> +</resources>
\ No newline at end of file diff --git a/packages/SystemUI/shared/res/values/bools.xml b/packages/SystemUI/shared/res/values/bools.xml new file mode 100644 index 000000000000..f22dac4b9fb4 --- /dev/null +++ b/packages/SystemUI/shared/res/values/bools.xml @@ -0,0 +1,25 @@ +<?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. +--> + +<!-- Formatting note: terminate all comments with a period, to avoid breaking + the documentation output. To suppress comment lines from the documentation + output, insert an eat-comment element after the comment lines. +--> + +<resources> + <!-- Whether to add padding at the bottom of the complication clock --> + <bool name="dream_overlay_complication_clock_bottom_padding">false</bool> +</resources>
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt index b6aae69ebad6..f1a40077f3f9 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt @@ -24,14 +24,12 @@ import android.os.Parcelable /** * Base interface for flags that can change value on a running device. - * @property id unique id to help identify this flag. Must be unique. This will be removed soon. * @property teamfood Set to true to include this flag as part of the teamfood flag. This will * be removed soon. * @property name Used for server-side flagging where appropriate. Also used for display. No spaces. * @property namespace The server-side namespace that this flag lives under. */ interface Flag<T> { - val id: Int val teamfood: Boolean val name: String val namespace: String @@ -58,7 +56,6 @@ interface SysPropFlag<T> : Flag<T> { */ // Consider using the "parcelize" kotlin library. abstract class BooleanFlag constructor( - override val id: Int, override val name: String, override val namespace: String, override val default: Boolean = false, @@ -74,8 +71,17 @@ abstract class BooleanFlag constructor( } } + private constructor( + id: Int, + name: String, + namespace: String, + default: Boolean, + teamfood: Boolean, + overridden: Boolean, + ) : this(name, namespace, default, teamfood, overridden) + private constructor(parcel: Parcel) : this( - id = parcel.readInt(), + parcel.readInt(), name = parcel.readString() ?: "", namespace = parcel.readString() ?: "", default = parcel.readBoolean(), @@ -84,7 +90,7 @@ abstract class BooleanFlag constructor( ) override fun writeToParcel(parcel: Parcel, flags: Int) { - parcel.writeInt(id) + parcel.writeInt(0) parcel.writeString(name) parcel.writeString(namespace) parcel.writeBoolean(default) @@ -99,12 +105,11 @@ abstract class BooleanFlag constructor( * It can be changed or overridden in debug builds but not in release builds. */ data class UnreleasedFlag constructor( - override val id: Int, override val name: String, override val namespace: String, override val teamfood: Boolean = false, override val overridden: Boolean = false -) : BooleanFlag(id, name, namespace, false, teamfood, overridden) +) : BooleanFlag(name, namespace, false, teamfood, overridden) /** * A Flag that is true by default. @@ -112,12 +117,11 @@ data class UnreleasedFlag constructor( * It can be changed or overridden in any build, meaning it can be turned off if needed. */ data class ReleasedFlag constructor( - override val id: Int, override val name: String, override val namespace: String, override val teamfood: Boolean = false, override val overridden: Boolean = false -) : BooleanFlag(id, name, namespace, true, teamfood, overridden) +) : BooleanFlag(name, namespace, true, teamfood, overridden) /** * A Flag that reads its default values from a resource overlay instead of code. @@ -125,7 +129,6 @@ data class ReleasedFlag constructor( * Prefer [UnreleasedFlag] and [ReleasedFlag]. */ data class ResourceBooleanFlag constructor( - override val id: Int, override val name: String, override val namespace: String, @BoolRes override val resourceId: Int, @@ -140,7 +143,6 @@ data class ResourceBooleanFlag constructor( * Prefer [UnreleasedFlag] and [ReleasedFlag]. */ data class SysPropBooleanFlag constructor( - override val id: Int, override val name: String, override val namespace: String, override val default: Boolean = false, @@ -150,7 +152,6 @@ data class SysPropBooleanFlag constructor( } data class StringFlag constructor( - override val id: Int, override val name: String, override val namespace: String, override val default: String = "", @@ -165,15 +166,21 @@ data class StringFlag constructor( } } + private constructor(id: Int, name: String, namespace: String, default: String) : this( + name, + namespace, + default + ) + private constructor(parcel: Parcel) : this( - id = parcel.readInt(), + parcel.readInt(), name = parcel.readString() ?: "", namespace = parcel.readString() ?: "", default = parcel.readString() ?: "" ) override fun writeToParcel(parcel: Parcel, flags: Int) { - parcel.writeInt(id) + parcel.writeInt(0) parcel.writeString(name) parcel.writeString(namespace) parcel.writeString(default) @@ -181,7 +188,6 @@ data class StringFlag constructor( } data class ResourceStringFlag constructor( - override val id: Int, override val name: String, override val namespace: String, @StringRes override val resourceId: Int, @@ -189,7 +195,6 @@ data class ResourceStringFlag constructor( ) : ResourceFlag<String> data class IntFlag constructor( - override val id: Int, override val name: String, override val namespace: String, override val default: Int = 0, @@ -205,15 +210,21 @@ data class IntFlag constructor( } } + private constructor(id: Int, name: String, namespace: String, default: Int) : this( + name, + namespace, + default + ) + private constructor(parcel: Parcel) : this( - id = parcel.readInt(), + parcel.readInt(), name = parcel.readString() ?: "", namespace = parcel.readString() ?: "", default = parcel.readInt() ) override fun writeToParcel(parcel: Parcel, flags: Int) { - parcel.writeInt(id) + parcel.writeInt(0) parcel.writeString(name) parcel.writeString(namespace) parcel.writeInt(default) @@ -221,7 +232,6 @@ data class IntFlag constructor( } data class ResourceIntFlag constructor( - override val id: Int, override val name: String, override val namespace: String, @IntegerRes override val resourceId: Int, @@ -229,11 +239,10 @@ data class ResourceIntFlag constructor( ) : ResourceFlag<Int> data class LongFlag constructor( - override val id: Int, - override val default: Long = 0, - override val teamfood: Boolean = false, override val name: String, override val namespace: String, + override val default: Long = 0, + override val teamfood: Boolean = false, override val overridden: Boolean = false ) : ParcelableFlag<Long> { @@ -245,15 +254,21 @@ data class LongFlag constructor( } } + private constructor(id: Int, name: String, namespace: String, default: Long) : this( + name, + namespace, + default + ) + private constructor(parcel: Parcel) : this( - id = parcel.readInt(), + parcel.readInt(), name = parcel.readString() ?: "", namespace = parcel.readString() ?: "", default = parcel.readLong() ) override fun writeToParcel(parcel: Parcel, flags: Int) { - parcel.writeInt(id) + parcel.writeInt(0) parcel.writeString(name) parcel.writeString(namespace) parcel.writeLong(default) @@ -261,7 +276,6 @@ data class LongFlag constructor( } data class FloatFlag constructor( - override val id: Int, override val name: String, override val namespace: String, override val default: Float = 0f, @@ -277,15 +291,21 @@ data class FloatFlag constructor( } } + private constructor(id: Int, name: String, namespace: String, default: Float) : this( + name, + namespace, + default + ) + private constructor(parcel: Parcel) : this( - id = parcel.readInt(), + parcel.readInt(), name = parcel.readString() ?: "", namespace = parcel.readString() ?: "", default = parcel.readFloat() ) override fun writeToParcel(parcel: Parcel, flags: Int) { - parcel.writeInt(id) + parcel.writeInt(0) parcel.writeString(name) parcel.writeString(namespace) parcel.writeFloat(default) @@ -293,7 +313,6 @@ data class FloatFlag constructor( } data class ResourceFloatFlag constructor( - override val id: Int, override val name: String, override val namespace: String, override val resourceId: Int, @@ -301,7 +320,6 @@ data class ResourceFloatFlag constructor( ) : ResourceFlag<Int> data class DoubleFlag constructor( - override val id: Int, override val name: String, override val namespace: String, override val default: Double = 0.0, @@ -317,15 +335,21 @@ data class DoubleFlag constructor( } } + private constructor(id: Int, name: String, namespace: String, default: Double) : this( + name, + namespace, + default + ) + private constructor(parcel: Parcel) : this( - id = parcel.readInt(), + parcel.readInt(), name = parcel.readString() ?: "", namespace = parcel.readString() ?: "", default = parcel.readDouble() ) override fun writeToParcel(parcel: Parcel, flags: Int) { - parcel.writeInt(id) + parcel.writeInt(0) parcel.writeString(name) parcel.writeString(namespace) parcel.writeDouble(default) diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt index da1641c326de..1366226a4e24 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt @@ -116,13 +116,6 @@ class FlagManager constructor( } /** Returns the stored value or null if not set. */ - // TODO(b/265188950): Remove method this once ids are fully deprecated. - fun <T> readFlagValue(id: Int, serializer: FlagSerializer<T>): T? { - val data = settings.getStringFromSecure(idToSettingsKey(id)) - return serializer.fromSettingsData(data) - } - - /** Returns the stored value or null if not set. */ fun <T> readFlagValue(name: String, serializer: FlagSerializer<T>): T? { val data = settings.getString(nameToSettingsKey(name)) return serializer.fromSettingsData(data) @@ -158,11 +151,6 @@ class FlagManager constructor( return intent } - // TODO(b/265188950): Remove method this once ids are fully deprecated. - fun idToSettingsKey(id: Int): String { - return "$SETTINGS_PREFIX/$id" - } - fun nameToSettingsKey(name: String): String { return "$SETTINGS_PREFIX/$name" } diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt index 6beb8518ab67..e0a7feaf671c 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt @@ -22,7 +22,6 @@ import android.provider.Settings class FlagSettingsHelper(private val contentResolver: ContentResolver) { - // TODO(b/265188950): Remove method this once ids are fully deprecated. fun getStringFromSecure(key: String): String? = Settings.Secure.getString(contentResolver, key) fun getString(key: String): String? = Settings.Global.getString(contentResolver, key) diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java index fac2f910a789..3605ac2bfc66 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java @@ -336,6 +336,14 @@ public class Task { @Override public boolean equals(Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof Task)) { + return false; + } + // Check that the id matches Task t = (Task) o; return key.equals(t.key); diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt index 5a6f1840bc95..4b602cb643a6 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt @@ -16,6 +16,8 @@ package com.android.systemui.shared.shadow import android.content.Context +import android.content.res.Resources +import android.content.res.TypedArray import android.graphics.Canvas import android.util.AttributeSet import android.widget.TextClock @@ -31,19 +33,40 @@ constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, - defStyleRes: Int = 0 + defStyleRes: Int = 0, ) : TextClock(context, attrs, defStyleAttr, defStyleRes) { - private val mAmbientShadowInfo: ShadowInfo - private val mKeyShadowInfo: ShadowInfo + private lateinit var mAmbientShadowInfo: ShadowInfo + private lateinit var mKeyShadowInfo: ShadowInfo + private var attributesInput: TypedArray? = null + private var resources: Resources? = null + constructor( + resources: Resources, + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + defStyleRes: Int = 0, + attributesInput: TypedArray? = null + ) : this(context, attrs, defStyleAttr, defStyleRes) { + this.attributesInput = attributesInput + this.resources = resources + this.initializeAttributes(attrs, defStyleAttr, defStyleRes) + } init { - val attributes = - context.obtainStyledAttributes( - attrs, - R.styleable.DoubleShadowTextClock, - defStyleAttr, - defStyleRes - ) + initializeAttributes(attrs, defStyleAttr, defStyleRes) + } + + private fun initializeAttributes(attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) { + var attributes: TypedArray = + this.attributesInput + ?: context.obtainStyledAttributes( + attrs, + R.styleable.DoubleShadowTextClock, + defStyleAttr, + defStyleRes + ) + + var resource: Resources = this.resources ?: context.resources try { val keyShadowBlur = attributes.getDimensionPixelSize(R.styleable.DoubleShadowTextClock_keyShadowBlur, 0) @@ -98,18 +121,27 @@ constructor( 0 ) if (removeTextDescent) { - setPaddingRelative( - 0, - 0, - 0, - textDescentExtraPadding - floor(paint.fontMetrics.descent.toDouble()).toInt() - ) + val addBottomPaddingToClock = + resource.getBoolean(R.bool.dream_overlay_complication_clock_bottom_padding) + val metrics = paint.fontMetrics + val padding = + if (addBottomPaddingToClock) { + textDescentExtraPadding + + floor(metrics.descent.toDouble()).toInt() / paddingDividedOffset + } else { + textDescentExtraPadding - floor(metrics.descent.toDouble()).toInt() + } + setPaddingRelative(0, 0, 0, padding) } } finally { attributes.recycle() } } + companion object { + private val paddingDividedOffset = 2 + } + public override fun onDraw(canvas: Canvas) { applyShadows(mKeyShadowInfo, mAmbientShadowInfo, this, canvas) { super.onDraw(canvas) } } diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt index c22d689338a8..3360c967a182 100644 --- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt +++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt @@ -29,35 +29,31 @@ object FlagsFactory { } fun unreleasedFlag( - id: Int, name: String, namespace: String = "systemui", teamfood: Boolean = false ): UnreleasedFlag { - val flag = UnreleasedFlag(id = id, name = name, namespace = namespace, teamfood = teamfood) + val flag = UnreleasedFlag(name = name, namespace = namespace, teamfood = teamfood) checkForDupesAndAdd(flag) return flag } fun releasedFlag( - id: Int, name: String, namespace: String = "systemui", ): ReleasedFlag { - val flag = ReleasedFlag(id = id, name = name, namespace = namespace, teamfood = false) + val flag = ReleasedFlag(name = name, namespace = namespace, teamfood = false) checkForDupesAndAdd(flag) return flag } fun resourceBooleanFlag( - id: Int, @BoolRes resourceId: Int, name: String, namespace: String = "systemui", ): ResourceBooleanFlag { val flag = ResourceBooleanFlag( - id = id, name = name, namespace = namespace, resourceId = resourceId, @@ -68,13 +64,11 @@ object FlagsFactory { } fun sysPropBooleanFlag( - id: Int, name: String, namespace: String = "systemui", default: Boolean = false ): SysPropBooleanFlag { - val flag = - SysPropBooleanFlag(id = id, name = name, namespace = "systemui", default = default) + val flag = SysPropBooleanFlag(name = name, namespace = "systemui", default = default) checkForDupesAndAdd(flag) return flag } diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt index 5502da146bba..75465c27724d 100644 --- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt +++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt @@ -29,35 +29,31 @@ object FlagsFactory { } fun unreleasedFlag( - id: Int, name: String, namespace: String = "systemui", teamfood: Boolean = false ): UnreleasedFlag { // Unreleased flags are always false in this build. - val flag = UnreleasedFlag(id = id, name = "", namespace = "", teamfood = false) + val flag = UnreleasedFlag(name = name, namespace = namespace, teamfood = false) return flag } fun releasedFlag( - id: Int, name: String, namespace: String = "systemui", ): ReleasedFlag { - val flag = ReleasedFlag(id = id, name = name, namespace = namespace, teamfood = false) + val flag = ReleasedFlag(name = name, namespace = namespace, teamfood = false) flagMap[name] = flag return flag } fun resourceBooleanFlag( - id: Int, @BoolRes resourceId: Int, name: String, namespace: String = "systemui", ): ResourceBooleanFlag { val flag = ResourceBooleanFlag( - id = id, name = name, namespace = namespace, resourceId = resourceId, @@ -68,13 +64,11 @@ object FlagsFactory { } fun sysPropBooleanFlag( - id: Int, name: String, namespace: String = "systemui", default: Boolean = false ): SysPropBooleanFlag { - val flag = - SysPropBooleanFlag(id = id, name = name, namespace = namespace, default = default) + val flag = SysPropBooleanFlag(name = name, namespace = namespace, default = default) flagMap[name] = flag return flag } diff --git a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt index 22cdb30376d0..2abb7a41ef08 100644 --- a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt +++ b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt @@ -33,6 +33,7 @@ import com.android.keyguard.InternalFaceAuthReasons.AUTH_REQUEST_DURING_CANCELLA import com.android.keyguard.InternalFaceAuthReasons.BIOMETRIC_ENABLED import com.android.keyguard.InternalFaceAuthReasons.CAMERA_LAUNCHED import com.android.keyguard.InternalFaceAuthReasons.DEVICE_WOKEN_UP_ON_REACH_GESTURE +import com.android.keyguard.InternalFaceAuthReasons.DISPLAY_OFF import com.android.keyguard.InternalFaceAuthReasons.DREAM_STARTED import com.android.keyguard.InternalFaceAuthReasons.DREAM_STOPPED import com.android.keyguard.InternalFaceAuthReasons.ENROLLMENTS_CHANGED @@ -131,6 +132,7 @@ private object InternalFaceAuthReasons { const val NON_STRONG_BIOMETRIC_ALLOWED_CHANGED = "Face auth stopped because non strong biometric allowed changed" const val POSTURE_CHANGED = "Face auth started/stopped due to device posture changed." + const val DISPLAY_OFF = "Face auth stopped due to display state OFF." } /** @@ -221,7 +223,8 @@ constructor(private val id: Int, val reason: String, var extraInfo: Int = 0) : FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED(1255, STRONG_AUTH_ALLOWED_CHANGED), @UiEvent(doc = NON_STRONG_BIOMETRIC_ALLOWED_CHANGED) FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED(1256, NON_STRONG_BIOMETRIC_ALLOWED_CHANGED), - @UiEvent(doc = ACCESSIBILITY_ACTION) FACE_AUTH_ACCESSIBILITY_ACTION(1454, ACCESSIBILITY_ACTION); + @UiEvent(doc = ACCESSIBILITY_ACTION) FACE_AUTH_ACCESSIBILITY_ACTION(1454, ACCESSIBILITY_ACTION), + @UiEvent(doc = DISPLAY_OFF) FACE_AUTH_DISPLAY_OFF(1461, DISPLAY_OFF); override fun getId(): Int = this.id diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 00256c4d7707..30b8ed0f1750 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -335,7 +335,6 @@ public class KeyguardClockSwitch extends RelativeLayout { } }); - in.setAlpha(0); in.setVisibility(View.VISIBLE); mClockInAnim = new AnimatorSet(); mClockInAnim.setDuration(CLOCK_IN_MILLIS); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 2c224f62dac7..3d48f3cc5359 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -244,24 +244,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS return; } updateAodIcons(); - mStatusArea = mView.findViewById(R.id.keyguard_status_area); - if (mSmartspaceController.isEnabled()) { - View ksv = mView.findViewById(R.id.keyguard_slice_view); - int viewIndex = mStatusArea.indexOfChild(ksv); - ksv.setVisibility(View.GONE); - - // TODO(b/261757708): add content observer for the Settings toggle and add/remove - // weather according to the Settings. - if (mSmartspaceController.isDateWeatherDecoupled()) { - addDateWeatherView(viewIndex); - viewIndex += 1; - } - - addSmartspaceView(viewIndex); - } - mSecureSettings.registerContentObserverForUser( Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, false, /* notifyForDescendants */ @@ -275,13 +259,27 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mShowWeatherObserver, UserHandle.USER_ALL ); - updateDoubleLineClock(); - setDateWeatherVisibility(); - setWeatherVisibility(); mKeyguardUnlockAnimationController.addKeyguardUnlockAnimationListener( mKeyguardUnlockAnimationListener); + + if (mSmartspaceController.isEnabled()) { + View ksv = mView.findViewById(R.id.keyguard_slice_view); + int viewIndex = mStatusArea.indexOfChild(ksv); + ksv.setVisibility(View.GONE); + + mSmartspaceController.removeViewsFromParent(mStatusArea); + addSmartspaceView(); + // TODO(b/261757708): add content observer for the Settings toggle and add/remove + // weather according to the Settings. + if (mSmartspaceController.isDateWeatherDecoupled()) { + addDateWeatherView(); + } + } + + setDateWeatherVisibility(); + setWeatherVisibility(); } int getNotificationIconAreaHeight() { @@ -302,29 +300,22 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS void onLocaleListChanged() { if (mSmartspaceController.isEnabled()) { + mSmartspaceController.removeViewsFromParent(mStatusArea); + addSmartspaceView(); if (mSmartspaceController.isDateWeatherDecoupled()) { mDateWeatherView.removeView(mWeatherView); - int index = mStatusArea.indexOfChild(mDateWeatherView); - if (index >= 0) { - mStatusArea.removeView(mDateWeatherView); - addDateWeatherView(index); - } + addDateWeatherView(); setDateWeatherVisibility(); setWeatherVisibility(); } - int index = mStatusArea.indexOfChild(mSmartspaceView); - if (index >= 0) { - mStatusArea.removeView(mSmartspaceView); - addSmartspaceView(index); - } } } - private void addDateWeatherView(int index) { + private void addDateWeatherView() { mDateWeatherView = (ViewGroup) mSmartspaceController.buildAndConnectDateView(mView); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( MATCH_PARENT, WRAP_CONTENT); - mStatusArea.addView(mDateWeatherView, index, lp); + mStatusArea.addView(mDateWeatherView, 0, lp); int startPadding = getContext().getResources().getDimensionPixelSize( R.dimen.below_clock_padding_start); int endPadding = getContext().getResources().getDimensionPixelSize( @@ -344,11 +335,11 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mWeatherView.setPaddingRelative(0, 0, 4, 0); } - private void addSmartspaceView(int index) { + private void addSmartspaceView() { mSmartspaceView = mSmartspaceController.buildAndConnectView(mView); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( MATCH_PARENT, WRAP_CONTENT); - mStatusArea.addView(mSmartspaceView, index, lp); + mStatusArea.addView(mSmartspaceView, 0, lp); int startPadding = getContext().getResources().getDimensionPixelSize( R.dimen.below_clock_padding_start); int endPadding = getContext().getResources().getDimensionPixelSize( diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt index 461d390fd477..bb799fc55171 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt @@ -27,6 +27,7 @@ data class KeyguardFaceListenModel( override var userId: Int = 0, override var listening: Boolean = false, // keep sorted + var allowedDisplayState: Boolean = false, var alternateBouncerShowing: Boolean = false, var authInterruptActive: Boolean = false, var biometricSettingEnabledForUser: Boolean = false, @@ -57,6 +58,8 @@ data class KeyguardFaceListenModel( userId.toString(), listening.toString(), // keep sorted + allowedDisplayState.toString(), + alternateBouncerShowing.toString(), authInterruptActive.toString(), biometricSettingEnabledForUser.toString(), bouncerFullyShown.toString(), @@ -74,7 +77,6 @@ data class KeyguardFaceListenModel( supportsDetect.toString(), switchingUser.toString(), systemUser.toString(), - alternateBouncerShowing.toString(), udfpsFingerDown.toString(), userNotTrustedOrDetectionIsNeeded.toString(), ) @@ -96,7 +98,9 @@ data class KeyguardFaceListenModel( userId = model.userId listening = model.listening // keep sorted + allowedDisplayState = model.allowedDisplayState alternateBouncerShowing = model.alternateBouncerShowing + authInterruptActive = model.authInterruptActive biometricSettingEnabledForUser = model.biometricSettingEnabledForUser bouncerFullyShown = model.bouncerFullyShown faceAndFpNotAuthenticated = model.faceAndFpNotAuthenticated @@ -105,7 +109,6 @@ data class KeyguardFaceListenModel( faceLockedOut = model.faceLockedOut goingToSleep = model.goingToSleep keyguardAwake = model.keyguardAwake - goingToSleep = model.goingToSleep keyguardGoingAway = model.keyguardGoingAway listeningForFaceAssistant = model.listeningForFaceAssistant occludingAppRequestingFaceAuth = model.occludingAppRequestingFaceAuth @@ -140,6 +143,8 @@ data class KeyguardFaceListenModel( "userId", "listening", // keep sorted + "allowedDisplayState", + "alternateBouncerShowing", "authInterruptActive", "biometricSettingEnabledForUser", "bouncerFullyShown", @@ -157,7 +162,6 @@ data class KeyguardFaceListenModel( "supportsDetect", "switchingUser", "systemUser", - "udfpsBouncerShowing", "udfpsFingerDown", "userNotTrustedOrDetectionIsNeeded", ) diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java index 03d9eb3455fd..59ee0d817ef3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java @@ -32,6 +32,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.Context; +import android.content.res.ColorStateList; import android.graphics.Insets; import android.graphics.Rect; import android.os.Trace; @@ -71,6 +72,8 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { private Interpolator mLinearOutSlowInInterpolator; private Interpolator mFastOutLinearInInterpolator; private DisappearAnimationListener mDisappearAnimationListener; + private static final int[] DISABLE_STATE_SET = {-android.R.attr.state_enabled}; + private static final int[] ENABLE_STATE_SET = {android.R.attr.state_enabled}; public KeyguardPasswordView(Context context) { this(context, null); @@ -148,7 +151,10 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { @Override protected void setPasswordEntryEnabled(boolean enabled) { - mPasswordEntry.setEnabled(enabled); + int color = mPasswordEntry.getTextColors().getColorForState( + enabled ? ENABLE_STATE_SET : DISABLE_STATE_SET, 0); + mPasswordEntry.setBackgroundTintList(ColorStateList.valueOf(color)); + mPasswordEntry.setCursorVisible(enabled); } @Override @@ -189,17 +195,18 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { if (controller.isCancelled()) { return; } + float value = (float) animation.getAnimatedValue(); + float fraction = anim.getAnimatedFraction(); Insets shownInsets = controller.getShownStateInsets(); int dist = (int) (-shownInsets.bottom / 4 - * anim.getAnimatedFraction()); + * fraction); Insets insets = Insets.add(shownInsets, Insets.of(0, 0, 0, dist)); if (mDisappearAnimationListener != null) { mDisappearAnimationListener.setTranslationY(-dist); } - controller.setInsetsAndAlpha(insets, - (float) animation.getAnimatedValue(), - anim.getAnimatedFraction()); + controller.setInsetsAndAlpha(insets, value, fraction); + setAlpha(value); }); anim.addListener(new AnimatorListenerAdapter() { @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java index 1f6b09b91b87..5dbd01407a43 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java @@ -265,7 +265,8 @@ public class KeyguardPasswordViewController private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm, final boolean shouldIncludeAuxiliarySubtypes) { final List<InputMethodInfo> enabledImis = - imm.getEnabledInputMethodListAsUser(KeyguardUpdateMonitor.getCurrentUser()); + imm.getEnabledInputMethodListAsUser( + UserHandle.of(KeyguardUpdateMonitor.getCurrentUser())); // Number of the filtered IMEs int filteredImisCount = 0; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 8a0c9ef6680c..61addabe7341 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -1198,8 +1198,6 @@ public class KeyguardSecurityContainer extends ConstraintLayout { }); mPopup.show(); }); - - mUserSwitcherViewGroup.setAlpha(0f); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index aff25914ec36..4e1cbc737d5f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -476,18 +476,16 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) { // When the scene framework transitions from bouncer to gone, we dismiss the keyguard. mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow( - mSceneInteractor.get().getTransitions(), - sceneTransitionModel -> { - if (sceneTransitionModel != null - && sceneTransitionModel.getFrom() == SceneKey.Bouncer.INSTANCE - && sceneTransitionModel.getTo() == SceneKey.Gone.INSTANCE) { - final int selectedUserId = mUserInteractor.getSelectedUserId(); - showNextSecurityScreenOrFinish( - /* authenticated= */ true, - selectedUserId, - /* bypassSecondaryLockScreen= */ true, - mSecurityModel.getSecurityMode(selectedUserId)); - } + mSceneInteractor.get().finishedSceneTransitions( + /* from= */ SceneKey.Bouncer.INSTANCE, + /* to= */ SceneKey.Gone.INSTANCE), + unused -> { + final int selectedUserId = mUserInteractor.getSelectedUserId(); + showNextSecurityScreenOrFinish( + /* authenticated= */ true, + selectedUserId, + /* bypassSecondaryLockScreen= */ true, + mSecurityModel.getSecurityMode(selectedUserId)); }); } } @@ -677,6 +675,14 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mSecurityViewFlipperController.reset(); } + /** Prepares views in the bouncer before starting appear animation. */ + public void prepareToShow() { + View bouncerUserSwitcher = mView.findViewById(R.id.keyguard_bouncer_user_switcher); + if (bouncerUserSwitcher != null) { + bouncerUserSwitcher.setAlpha(0f); + } + } + @Override public void onResume(int reason) { if (DEBUG) Log.d(TAG, "screen on, instance " + Integer.toHexString(hashCode())); @@ -827,7 +833,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId); boolean isLockscreenDisabled = mLockPatternUtils.isLockScreenDisabled( KeyguardUpdateMonitor.getCurrentUser()); - if (securityMode == SecurityMode.None || isLockscreenDisabled) { + + if (securityMode == SecurityMode.None) { finish = isLockscreenDisabled; eventSubtype = BOUNCER_DISMISS_SIM; uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 8e92941c79fa..73b4c5f47cde 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -150,7 +150,8 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV @Override public void onInit() { mKeyguardClockSwitchController.init(); - mDumpManager.registerDumpable(this); + + mDumpManager.registerDumpable(getInstanceName(), this); if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { startCoroutines(EmptyCoroutineContext.INSTANCE); } @@ -190,7 +191,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV * Called in notificationPanelViewController to avoid leak */ public void onDestroy() { - mDumpManager.unregisterDumpable(TAG); + mDumpManager.unregisterDumpable(getInstanceName()); } /** @@ -385,7 +386,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV * Updates the alignment of the KeyguardStatusView and animates the transition if requested. */ public void updateAlignment( - ConstraintLayout notifContainerParent, + ConstraintLayout layout, boolean splitShadeEnabled, boolean shouldBeCentered, boolean animate) { @@ -395,16 +396,23 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } mStatusViewCentered = shouldBeCentered; - if (notifContainerParent == null) { + if (layout == null) { return; } ConstraintSet constraintSet = new ConstraintSet(); - constraintSet.clone(notifContainerParent); - int statusConstraint = shouldBeCentered ? PARENT_ID : R.id.qs_edge_guideline; + constraintSet.clone(layout); + int guideline; + if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + guideline = R.id.split_shade_guideline; + } else { + guideline = R.id.qs_edge_guideline; + } + + int statusConstraint = shouldBeCentered ? PARENT_ID : guideline; constraintSet.connect(R.id.keyguard_status_view, END, statusConstraint, END); if (!animate) { - constraintSet.applyTo(notifContainerParent); + constraintSet.applyTo(layout); return; } @@ -447,7 +455,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV // old animation rather than setting up the custom animations. if (clockContainerView == null || clockContainerView.getChildCount() == 0) { transition.addListener(mKeyguardStatusAlignmentTransitionListener); - TransitionManager.beginDelayedTransition(notifContainerParent, transition); + TransitionManager.beginDelayedTransition(layout, transition); } else { View clockView = clockContainerView.getChildAt(0); @@ -481,14 +489,14 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } set.addListener(mKeyguardStatusAlignmentTransitionListener); - TransitionManager.beginDelayedTransition(notifContainerParent, set); + TransitionManager.beginDelayedTransition(layout, set); } } else { transition.addListener(mKeyguardStatusAlignmentTransitionListener); - TransitionManager.beginDelayedTransition(notifContainerParent, transition); + TransitionManager.beginDelayedTransition(layout, transition); } - constraintSet.applyTo(notifContainerParent); + constraintSet.applyTo(layout); } @Override @@ -496,6 +504,10 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV mView.dump(pw, args); } + String getInstanceName() { + return TAG + "#" + hashCode(); + } + @VisibleForTesting static class SplitShadeTransitionAdapter extends Transition { private static final String PROP_BOUNDS_LEFT = "splitShadeTransitionAdapter:boundsLeft"; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index c03053d0a8a2..853a62a09f83 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -41,6 +41,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; import static com.android.keyguard.FaceAuthReasonKt.apiRequestReasonToUiEvent; +import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_DISPLAY_OFF; import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED; import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_DREAM_STARTED; import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED; @@ -135,6 +136,7 @@ import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.SparseArray; import android.util.SparseBooleanArray; +import android.view.Display; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -175,6 +177,7 @@ import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.WeatherData; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; @@ -335,6 +338,25 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } }; + private final DisplayTracker.Callback mDisplayCallback = new DisplayTracker.Callback() { + @Override + public void onDisplayChanged(int displayId) { + if (displayId != Display.DEFAULT_DISPLAY) { + return; + } + + if (mDisplayTracker.getDisplay(mDisplayTracker.getDefaultDisplayId()).getState() + == Display.STATE_OFF) { + mAllowedDisplayStateForFaceAuth = false; + updateFaceListeningState( + BIOMETRIC_ACTION_STOP, + FACE_AUTH_DISPLAY_OFF + ); + } else { + mAllowedDisplayStateForFaceAuth = true; + } + } + }; private final FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig; HashMap<Integer, SimData> mSimDatas = new HashMap<>(); @@ -355,6 +377,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean mOccludingAppRequestingFp; private boolean mOccludingAppRequestingFace; private boolean mSecureCameraLaunched; + private boolean mAllowedDisplayStateForFaceAuth = true; @VisibleForTesting protected boolean mTelephonyCapable; private boolean mAllowFingerprintOnCurrentOccludingActivity; @@ -403,6 +426,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private KeyguardFaceAuthInteractor mFaceAuthInteractor; private final TaskStackChangeListeners mTaskStackChangeListeners; private final IActivityTaskManager mActivityTaskManager; + private final DisplayTracker mDisplayTracker; private final LockPatternUtils mLockPatternUtils; @VisibleForTesting @DevicePostureInt @@ -2187,6 +2211,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp"); Assert.isMainThread(); + mAllowedDisplayStateForFaceAuth = true; updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); if (mFaceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(pmWakeReason)) { FACE_AUTH_UPDATED_STARTED_WAKING_UP.setExtraInfo(pmWakeReason); @@ -2342,7 +2367,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Optional<FingerprintInteractiveToAuthProvider> interactiveToAuthProvider, FeatureFlags featureFlags, TaskStackChangeListeners taskStackChangeListeners, - IActivityTaskManager activityTaskManagerService) { + IActivityTaskManager activityTaskManagerService, + DisplayTracker displayTracker) { mContext = context; mSubscriptionManager = subscriptionManager; mUserTracker = userTracker; @@ -2390,6 +2416,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab .collect(Collectors.toSet()); mTaskStackChangeListeners = taskStackChangeListeners; mActivityTaskManager = activityTaskManagerService; + mDisplayTracker = displayTracker; + if (mFeatureFlags.isEnabled(Flags.STOP_FACE_AUTH_ON_DISPLAY_OFF)) { + mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor); + } mHandler = new Handler(mainLooper) { @Override @@ -3199,7 +3229,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab && (!mSecureCameraLaunched || mAlternateBouncerShowing) && faceAndFpNotAuthenticated && !mGoingToSleep - && isPostureAllowedForFaceAuth; + && isPostureAllowedForFaceAuth + && mAllowedDisplayStateForFaceAuth; // Aggregate relevant fields for debug logging. logListenerModelData( @@ -3207,6 +3238,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab System.currentTimeMillis(), user, shouldListen, + mAllowedDisplayStateForFaceAuth, mAlternateBouncerShowing, mAuthInterruptActive, biometricEnabledForUser, @@ -4400,6 +4432,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mLockPatternUtils.unregisterStrongAuthTracker(mStrongAuthTracker); mTrustManager.unregisterTrustListener(this); + mDisplayTracker.removeCallback(mDisplayCallback); mHandler.removeCallbacksAndMessages(null); } diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 4845a6173be9..951a6aeef11b 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -25,6 +25,7 @@ import static com.android.keyguard.LockIconView.ICON_UNLOCK; import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1; import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED; +import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; import android.content.res.Configuration; @@ -39,6 +40,7 @@ import android.os.VibrationAttributes; import android.util.DisplayMetrics; import android.util.Log; import android.util.MathUtils; +import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; @@ -613,12 +615,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_HOVER_ENTER: if (!mDownDetected && mAccessibilityManager.isTouchExplorationEnabled()) { - mVibrator.vibrate( - Process.myUid(), - getContext().getOpPackageName(), - UdfpsController.EFFECT_CLICK, - "lock-icon-down", - TOUCH_VIBRATION_ATTRIBUTES); + vibrateOnTouchExploration(); } // The pointer that causes ACTION_DOWN is always at index 0. @@ -699,13 +696,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mOnGestureDetectedRunnable.run(); } - // play device entry haptic (same as biometric success haptic) - mVibrator.vibrate( - Process.myUid(), - getContext().getOpPackageName(), - UdfpsController.EFFECT_CLICK, - "lock-screen-lock-icon-longpress", - TOUCH_VIBRATION_ATTRIBUTES); + // play device entry haptic (consistent with UDFPS controller longpress) + vibrateOnLongPress(); mKeyguardViewController.showPrimaryBouncer(/* scrim */ true); } @@ -753,6 +745,37 @@ public class LockIconViewController extends ViewController<LockIconView> impleme }); } + @VisibleForTesting + void vibrateOnTouchExploration() { + if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { + mVibrator.performHapticFeedback( + mView, + HapticFeedbackConstants.CONTEXT_CLICK + ); + } else { + mVibrator.vibrate( + Process.myUid(), + getContext().getOpPackageName(), + UdfpsController.EFFECT_CLICK, + "lock-icon-down", + TOUCH_VIBRATION_ATTRIBUTES); + } + } + + @VisibleForTesting + void vibrateOnLongPress() { + if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { + mVibrator.performHapticFeedback(mView, UdfpsController.LONG_PRESS); + } else { + mVibrator.vibrate( + Process.myUid(), + getContext().getOpPackageName(), + UdfpsController.EFFECT_CLICK, + "lock-screen-lock-icon-longpress", + TOUCH_VIBRATION_ATTRIBUTES); + } + } + private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() { @Override public void onAllAuthenticatorsRegistered(@BiometricAuthenticator.Modality int modality) { diff --git a/core/java/android/view/selectiontoolbar/ToolbarMenuItem.aidl b/packages/SystemUI/src/com/android/systemui/aconfig/AConfigModule.kt index 711a85a7771e..251a699be38c 100644 --- a/core/java/android/view/selectiontoolbar/ToolbarMenuItem.aidl +++ b/packages/SystemUI/src/com/android/systemui/aconfig/AConfigModule.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2023 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. @@ -14,9 +14,20 @@ * limitations under the License. */ -package android.view.selectiontoolbar; +package com.android.systemui.aconfig -/** - * @hide - */ -parcelable ToolbarMenuItem; +import com.android.systemui.dagger.SysUISingleton +import dagger.Module +import dagger.Provides + +@Module +abstract class AConfigModule { + @Module + companion object { + @Provides + @SysUISingleton + fun providesImpl(): FeatureFlags { + return FeatureFlagsImpl() + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/aconfig/systemui.aconfig b/packages/SystemUI/src/com/android/systemui/aconfig/systemui.aconfig new file mode 100644 index 000000000000..2d6e25776134 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/aconfig/systemui.aconfig @@ -0,0 +1,8 @@ +package: "com.android.systemui.aconfig" + +flag { + name: "example_flag" + namespace: "systemui" + description: "An Example Flag" + bug: "292511372" +} diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/model/AuthenticationMethodModel.kt new file mode 100644 index 000000000000..6d23b11e5d66 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/authentication/data/model/AuthenticationMethodModel.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2023 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.authentication.data.model + +/** Enumerates all known authentication methods. */ +sealed class AuthenticationMethodModel( + /** + * Whether the authentication method is considered to be "secure". + * + * "Secure" authentication methods require authentication to unlock the device. Non-secure auth + * methods simply require user dismissal. + */ + open val isSecure: Boolean, +) { + /** There is no authentication method on the device. We shouldn't even show the lock screen. */ + object None : AuthenticationMethodModel(isSecure = false) + + object Pin : AuthenticationMethodModel(isSecure = true) + + object Password : AuthenticationMethodModel(isSecure = true) + + object Pattern : AuthenticationMethodModel(isSecure = true) +} diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt index deb3d035d753..8d1fc5d9d558 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt @@ -14,15 +14,21 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.authentication.data.repository +import android.app.admin.DevicePolicyManager +import android.content.IntentFilter +import android.os.UserHandle import com.android.internal.widget.LockPatternChecker import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockscreenCredential import com.android.keyguard.KeyguardSecurityModel -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationResultModel import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.data.repository.KeyguardRepository @@ -37,13 +43,17 @@ import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -54,9 +64,10 @@ interface AuthenticationRepository { * Whether the device is unlocked. * * A device that is not yet unlocked requires unlocking by completing an authentication - * challenge according to the current authentication method. - * - * Note that this state has no real bearing on whether the lockscreen is showing or dismissed. + * challenge according to the current authentication method, unless in cases when the current + * authentication method is not "secure" (for example, None); in such cases, the value of this + * flow will always be `true`, even if the lockscreen is showing and still needs to be dismissed + * by the user to proceed. */ val isUnlocked: StateFlow<Boolean> @@ -86,8 +97,29 @@ interface AuthenticationRepository { val throttling: StateFlow<AuthenticationThrottlingModel> /** + * The currently-configured authentication method. This determines how the authentication + * challenge needs to be completed in order to unlock an otherwise locked device. + * + * Note: there may be other ways to unlock the device that "bypass" the need for this + * authentication challenge (notably, biometrics like fingerprint or face unlock). + * + * Note: by design, this is a [Flow] and not a [StateFlow]; a consumer who wishes to get a + * snapshot of the current authentication method without establishing a collector of the flow + * can do so by invoking [getAuthenticationMethod]. + */ + val authenticationMethod: Flow<AuthenticationMethodModel> + + /** * Returns the currently-configured authentication method. This determines how the - * authentication challenge is completed in order to unlock an otherwise locked device. + * authentication challenge needs to be completed in order to unlock an otherwise locked device. + * + * Note: there may be other ways to unlock the device that "bypass" the need for this + * authentication challenge (notably, biometrics like fingerprint or face unlock). + * + * Note: by design, this is offered as a convenience method alongside [authenticationMethod]. + * The flow should be used for code that wishes to stay up-to-date its logic as the + * authentication changes over time and this method should be used for simple code that only + * needs to check the current value. */ suspend fun getAuthenticationMethod(): AuthenticationMethodModel @@ -141,6 +173,7 @@ constructor( private val userRepository: UserRepository, keyguardRepository: KeyguardRepository, private val lockPatternUtils: LockPatternUtils, + broadcastDispatcher: BroadcastDispatcher, ) : AuthenticationRepository { override val isUnlocked = keyguardRepository.isKeyguardUnlocked @@ -148,7 +181,7 @@ constructor( override suspend fun isLockscreenEnabled(): Boolean { return withContext(backgroundDispatcher) { val selectedUserId = userRepository.selectedUserId - !lockPatternUtils.isLockPatternEnabled(selectedUserId) + !lockPatternUtils.isLockScreenDisabled(selectedUserId) } } @@ -172,18 +205,31 @@ constructor( private val UserRepository.selectedUserId: Int get() = getSelectedUserInfo().id + override val authenticationMethod: Flow<AuthenticationMethodModel> = + userRepository.selectedUserInfo + .map { it.id } + .distinctUntilChanged() + .flatMapLatest { selectedUserId -> + broadcastDispatcher + .broadcastFlow( + filter = + IntentFilter( + DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED + ), + user = UserHandle.of(selectedUserId), + ) + .onStart { emit(Unit) } + .map { selectedUserId } + } + .map { selectedUserId -> + withContext(backgroundDispatcher) { + blockingAuthenticationMethodInternal(selectedUserId) + } + } + override suspend fun getAuthenticationMethod(): AuthenticationMethodModel { return withContext(backgroundDispatcher) { - val selectedUserId = userRepository.selectedUserId - when (getSecurityMode.apply(selectedUserId)) { - KeyguardSecurityModel.SecurityMode.PIN, - KeyguardSecurityModel.SecurityMode.SimPin, - KeyguardSecurityModel.SecurityMode.SimPuk -> AuthenticationMethodModel.Pin - KeyguardSecurityModel.SecurityMode.Password -> AuthenticationMethodModel.Password - KeyguardSecurityModel.SecurityMode.Pattern -> AuthenticationMethodModel.Pattern - KeyguardSecurityModel.SecurityMode.None -> AuthenticationMethodModel.None - KeyguardSecurityModel.SecurityMode.Invalid -> error("Invalid security mode!") - } + blockingAuthenticationMethodInternal(userRepository.selectedUserId) } } @@ -301,6 +347,27 @@ constructor( return flow.asStateFlow() } + + /** + * Returns the authentication method for the given user ID. + * + * WARNING: this is actually a blocking IPC/"binder" call that's expensive to do on the main + * thread. We keep it not marked as `suspend` because we want to be able to run this without a + * `runBlocking` which has a ton of performance/blocking problems. + */ + private fun blockingAuthenticationMethodInternal( + userId: Int, + ): AuthenticationMethodModel { + return when (getSecurityMode.apply(userId)) { + KeyguardSecurityModel.SecurityMode.PIN, + KeyguardSecurityModel.SecurityMode.SimPin, + KeyguardSecurityModel.SecurityMode.SimPuk -> AuthenticationMethodModel.Pin + KeyguardSecurityModel.SecurityMode.Password -> AuthenticationMethodModel.Password + KeyguardSecurityModel.SecurityMode.Pattern -> AuthenticationMethodModel.Pattern + KeyguardSecurityModel.SecurityMode.None -> AuthenticationMethodModel.None + KeyguardSecurityModel.SecurityMode.Invalid -> error("Invalid security mode!") + } + } } @Module diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt index d4371bf30e0e..75192021dab6 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt @@ -18,8 +18,10 @@ package com.android.systemui.authentication.domain.interactor import com.android.internal.widget.LockPatternView import com.android.internal.widget.LockscreenCredential +import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel import com.android.systemui.authentication.data.repository.AuthenticationRepository -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -35,8 +37,10 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.async import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -56,21 +60,40 @@ constructor( private val clock: SystemClock, ) { /** + * The currently-configured authentication method. This determines how the authentication + * challenge needs to be completed in order to unlock an otherwise locked device. + * + * Note: there may be other ways to unlock the device that "bypass" the need for this + * authentication challenge (notably, biometrics like fingerprint or face unlock). + * + * Note: by design, this is a [Flow] and not a [StateFlow]; a consumer who wishes to get a + * snapshot of the current authentication method without establishing a collector of the flow + * can do so by invoking [getAuthenticationMethod]. + * + * Note: this layer adds the synthetic authentication method of "swipe" which is special. When + * the current authentication method is "swipe", the user does not need to complete any + * authentication challenge to unlock the device; they just need to dismiss the lockscreen to + * get past it. This also means that the value of [isUnlocked] remains `false` even when the + * lockscreen is showing and still needs to be dismissed by the user to proceed. + */ + val authenticationMethod: Flow<DomainLayerAuthenticationMethodModel> = + repository.authenticationMethod.map { rawModel -> rawModel.toDomainLayer() } + + /** * Whether the device is unlocked. * * A device that is not yet unlocked requires unlocking by completing an authentication - * challenge according to the current authentication method. - * - * Note that this state has no real bearing on whether the lock screen is showing or dismissed. + * challenge according to the current authentication method, unless in cases when the current + * authentication method is not "secure" (for example, None and Swipe); in such cases, the value + * of this flow will always be `true`, even if the lockscreen is showing and still needs to be + * dismissed by the user to proceed. */ val isUnlocked: StateFlow<Boolean> = - repository.isUnlocked - .map { isUnlocked -> - if (getAuthenticationMethod() is AuthenticationMethodModel.None) { - true - } else { - isUnlocked - } + combine( + repository.isUnlocked, + authenticationMethod, + ) { isUnlocked, authenticationMethod -> + authenticationMethod is DomainLayerAuthenticationMethodModel.None || isUnlocked } .stateIn( scope = applicationScope, @@ -129,18 +152,24 @@ constructor( /** * Returns the currently-configured authentication method. This determines how the - * authentication challenge is completed in order to unlock an otherwise locked device. + * authentication challenge needs to be completed in order to unlock an otherwise locked device. + * + * Note: there may be other ways to unlock the device that "bypass" the need for this + * authentication challenge (notably, biometrics like fingerprint or face unlock). + * + * Note: by design, this is offered as a convenience method alongside [authenticationMethod]. + * The flow should be used for code that wishes to stay up-to-date its logic as the + * authentication changes over time and this method should be used for simple code that only + * needs to check the current value. + * + * Note: this layer adds the synthetic authentication method of "swipe" which is special. When + * the current authentication method is "swipe", the user does not need to complete any + * authentication challenge to unlock the device; they just need to dismiss the lockscreen to + * get past it. This also means that the value of [isUnlocked] remains `false` even when the + * lockscreen is showing and still needs to be dismissed by the user to proceed. */ - suspend fun getAuthenticationMethod(): AuthenticationMethodModel { - val authMethod = repository.getAuthenticationMethod() - return if ( - authMethod is AuthenticationMethodModel.None && repository.isLockscreenEnabled() - ) { - // We treat "None" as "Swipe" when the lockscreen is enabled. - AuthenticationMethodModel.Swipe - } else { - authMethod - } + suspend fun getAuthenticationMethod(): DomainLayerAuthenticationMethodModel { + return repository.getAuthenticationMethod().toDomainLayer() } /** @@ -270,21 +299,38 @@ constructor( } } - private fun AuthenticationMethodModel.createCredential( + private fun DomainLayerAuthenticationMethodModel.createCredential( input: List<Any> ): LockscreenCredential? { return when (this) { - is AuthenticationMethodModel.Pin -> + is DomainLayerAuthenticationMethodModel.Pin -> LockscreenCredential.createPin(input.joinToString("")) - is AuthenticationMethodModel.Password -> + is DomainLayerAuthenticationMethodModel.Password -> LockscreenCredential.createPassword(input.joinToString("")) - is AuthenticationMethodModel.Pattern -> + is DomainLayerAuthenticationMethodModel.Pattern -> LockscreenCredential.createPattern( input - .map { it as AuthenticationMethodModel.Pattern.PatternCoordinate } + .map { it as AuthenticationPatternCoordinate } .map { LockPatternView.Cell.of(it.y, it.x) } ) else -> null } } + + private suspend fun DataLayerAuthenticationMethodModel.toDomainLayer(): + DomainLayerAuthenticationMethodModel { + return when (this) { + is DataLayerAuthenticationMethodModel.None -> + if (repository.isLockscreenEnabled()) { + DomainLayerAuthenticationMethodModel.Swipe + } else { + DomainLayerAuthenticationMethodModel.None + } + is DataLayerAuthenticationMethodModel.Pin -> DomainLayerAuthenticationMethodModel.Pin + is DataLayerAuthenticationMethodModel.Password -> + DomainLayerAuthenticationMethodModel.Password + is DataLayerAuthenticationMethodModel.Pattern -> + DomainLayerAuthenticationMethodModel.Pattern + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/model/AuthenticationMethodModel.kt index 97c6697f10a5..d7e6099a8908 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/model/AuthenticationMethodModel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.authentication.shared.model +package com.android.systemui.authentication.domain.model /** Enumerates all known authentication methods. */ sealed class AuthenticationMethodModel( @@ -36,10 +36,5 @@ sealed class AuthenticationMethodModel( object Password : AuthenticationMethodModel(isSecure = true) - object Pattern : AuthenticationMethodModel(isSecure = true) { - data class PatternCoordinate( - val x: Int, - val y: Int, - ) - } + object Pattern : AuthenticationMethodModel(isSecure = true) } diff --git a/core/java/android/view/selectiontoolbar/WidgetInfo.aidl b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationPatternCoordinate.kt index 1057c516e233..8a3f780b3e54 100644 --- a/core/java/android/view/selectiontoolbar/WidgetInfo.aidl +++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationPatternCoordinate.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright 2023 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. @@ -14,9 +14,9 @@ * limitations under the License. */ -package android.view.selectiontoolbar; +package com.android.systemui.authentication.shared.model -/** - * @hide - */ -parcelable WidgetInfo; +data class AuthenticationPatternCoordinate( + val x: Int, + val y: Int, +) diff --git a/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt index b34f1b45d763..b81d7fced5e4 100644 --- a/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt +++ b/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt @@ -178,6 +178,11 @@ class AccessorizedBatteryDrawable( mainBatteryDrawable.charging = charging } + /** Returns whether the battery is currently charging. */ + fun getCharging(): Boolean { + return mainBatteryDrawable.charging + } + /** Sets the current level (out of 100) of the battery. */ fun setBatteryLevel(level: Int) { mainBatteryDrawable.setBatteryLevel(level) diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java index 46fcdbc909b6..87ea4111d8c8 100644 --- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java @@ -77,8 +77,9 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { private int mShowPercentMode = MODE_DEFAULT; private boolean mShowPercentAvailable; private String mEstimateText = null; - private boolean mCharging; + private boolean mPluggedIn; private boolean mIsBatteryDefender; + private boolean mIsIncompatibleCharging; private boolean mDisplayShieldEnabled; // Error state where we know nothing about the current battery state private boolean mBatteryStateUnknown; @@ -202,10 +203,10 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { * @param pluggedIn whether the device is plugged in or not */ public void onBatteryLevelChanged(@IntRange(from = 0, to = 100) int level, boolean pluggedIn) { - mDrawable.setCharging(pluggedIn); - mDrawable.setBatteryLevel(level); - mCharging = pluggedIn; + mPluggedIn = pluggedIn; mLevel = level; + mDrawable.setCharging(isCharging()); + mDrawable.setBatteryLevel(level); updatePercentText(); } @@ -224,6 +225,15 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { } } + void onIsIncompatibleChargingChanged(boolean isIncompatibleCharging) { + boolean valueChanged = mIsIncompatibleCharging != isIncompatibleCharging; + mIsIncompatibleCharging = isIncompatibleCharging; + if (valueChanged) { + mDrawable.setCharging(isCharging()); + updateContentDescription(); + } + } + private TextView loadPercentView() { return (TextView) LayoutInflater.from(getContext()) .inflate(R.layout.battery_percentage_view, null); @@ -263,7 +273,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { } if (mBatteryPercentView != null) { - if (mShowPercentMode == MODE_ESTIMATE && !mCharging) { + if (mShowPercentMode == MODE_ESTIMATE && !isCharging()) { mBatteryEstimateFetcher.fetchBatteryTimeRemainingEstimate( (String estimate) -> { if (mBatteryPercentView == null) { @@ -316,7 +326,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { } else if (mIsBatteryDefender) { contentDescription = context.getString(R.string.accessibility_battery_level_charging_paused, mLevel); - } else if (mCharging) { + } else if (isCharging()) { contentDescription = context.getString(R.string.accessibility_battery_level_charging, mLevel); } else { @@ -464,16 +474,24 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { } } + private boolean isCharging() { + return mPluggedIn && !mIsIncompatibleCharging; + } + public void dump(PrintWriter pw, String[] args) { String powerSave = mDrawable == null ? null : mDrawable.getPowerSaveEnabled() + ""; String displayShield = mDrawable == null ? null : mDrawable.getDisplayShield() + ""; + String charging = mDrawable == null ? null : mDrawable.getCharging() + ""; CharSequence percent = mBatteryPercentView == null ? null : mBatteryPercentView.getText(); pw.println(" BatteryMeterView:"); pw.println(" mDrawable.getPowerSave: " + powerSave); pw.println(" mDrawable.getDisplayShield: " + displayShield); + pw.println(" mDrawable.getCharging: " + charging); pw.println(" mBatteryPercentView.getText(): " + percent); pw.println(" mTextColor: #" + Integer.toHexString(mTextColor)); pw.println(" mBatteryStateUnknown: " + mBatteryStateUnknown); + pw.println(" mIsIncompatibleCharging: " + mIsIncompatibleCharging); + pw.println(" mPluggedIn: " + mPluggedIn); pw.println(" mLevel: " + mLevel); pw.println(" mMode: " + mShowPercentMode); } diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java index 6a5749cc5571..0ca38834960c 100644 --- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java +++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java @@ -32,6 +32,8 @@ import androidx.annotation.NonNull; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarLocation; @@ -50,6 +52,7 @@ public class BatteryMeterViewController extends ViewController<BatteryMeterView> private final TunerService mTunerService; private final Handler mMainHandler; private final ContentResolver mContentResolver; + private final FeatureFlags mFeatureFlags; private final BatteryController mBatteryController; private final String mSlotBattery; @@ -99,6 +102,13 @@ public class BatteryMeterViewController extends ViewController<BatteryMeterView> } @Override + public void onIsIncompatibleChargingChanged(boolean isIncompatibleCharging) { + if (mFeatureFlags.isEnabled(Flags.INCOMPATIBLE_CHARGING_BATTERY_ICON)) { + mView.onIsIncompatibleChargingChanged(isIncompatibleCharging); + } + } + + @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { pw.print(super.toString()); pw.println(" location=" + mLocation); @@ -129,6 +139,7 @@ public class BatteryMeterViewController extends ViewController<BatteryMeterView> TunerService tunerService, @Main Handler mainHandler, ContentResolver contentResolver, + FeatureFlags featureFlags, BatteryController batteryController) { super(view); mLocation = location; @@ -137,6 +148,7 @@ public class BatteryMeterViewController extends ViewController<BatteryMeterView> mTunerService = tunerService; mMainHandler = mainHandler; mContentResolver = contentResolver; + mFeatureFlags = featureFlags; mBatteryController = batteryController; mView.setBatteryEstimateFetcher(mBatteryController::getEstimatedTimeRemainingString); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt index 063f62e19686..869d0846e0ae 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt @@ -186,7 +186,7 @@ constructor( } private fun listenForAlternateBouncerVisibility() { - alternateBouncerInteractor.setAlternateBouncerUIAvailable(true) + alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, "SideFpsController") scope.launch { alternateBouncerInteractor.isVisible.collect { isVisible: Boolean -> if (isVisible) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt index 15bd73193687..db30a55ea1e1 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt @@ -83,6 +83,7 @@ constructor( dumpManager, ), UdfpsKeyguardViewControllerAdapter { + private val uniqueIdentifier = this.toString() private val useExpandedOverlay: Boolean = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) private var showingUdfpsBouncer = false @@ -282,7 +283,7 @@ constructor( public override fun onViewAttached() { super.onViewAttached() - alternateBouncerInteractor.setAlternateBouncerUIAvailable(true) + alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, uniqueIdentifier) val dozeAmount = statusBarStateController.dozeAmount lastDozeAmount = dozeAmount stateListener.onDozeAmountChanged(dozeAmount, dozeAmount) @@ -312,7 +313,7 @@ constructor( override fun onViewDetached() { super.onViewDetached() - alternateBouncerInteractor.setAlternateBouncerUIAvailable(false) + alternateBouncerInteractor.setAlternateBouncerUIAvailable(false, uniqueIdentifier) faceDetectRunning = false keyguardStateController.removeCallback(keyguardStateControllerCallback) statusBarStateController.removeCallback(stateListener) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt index 2a9f3eafc776..c9b1624d4d50 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt @@ -46,6 +46,7 @@ open class UdfpsKeyguardViewController( dumpManager, ), UdfpsKeyguardViewControllerAdapter { + private val uniqueIdentifier = this.toString() override val tag: String get() = TAG @@ -55,12 +56,12 @@ open class UdfpsKeyguardViewController( public override fun onViewAttached() { super.onViewAttached() - alternateBouncerInteractor.setAlternateBouncerUIAvailable(true) + alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, uniqueIdentifier) } public override fun onViewDetached() { super.onViewDetached() - alternateBouncerInteractor.setAlternateBouncerUIAvailable(false) + alternateBouncerInteractor.setAlternateBouncerUIAvailable(false, uniqueIdentifier) } override fun shouldPauseAuth(): Boolean { diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt index e3e9b3a3754a..98ae54b1340e 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt @@ -40,6 +40,7 @@ constructor( ) { var receivedDownTouch = false val isVisible: Flow<Boolean> = bouncerRepository.alternateBouncerVisible + private val alternateBouncerUiAvailableFromSource: HashSet<String> = HashSet() /** * Sets the correct bouncer states to show the alternate bouncer if it can show. @@ -69,8 +70,15 @@ constructor( return bouncerRepository.alternateBouncerVisible.value } - fun setAlternateBouncerUIAvailable(isAvailable: Boolean) { - bouncerRepository.setAlternateBouncerUIAvailable(isAvailable) + fun setAlternateBouncerUIAvailable(isAvailable: Boolean, token: String) { + if (isAvailable) { + alternateBouncerUiAvailableFromSource.add(token) + } else { + alternateBouncerUiAvailableFromSource.remove(token) + } + bouncerRepository.setAlternateBouncerUIAvailable( + alternateBouncerUiAvailableFromSource.isNotEmpty() + ) } fun canShowAlternateBouncerForFingerprint(): Boolean { diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt index 8ed964d4af22..1bf3a9ead08e 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt @@ -14,14 +14,12 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.bouncer.domain.interactor import android.content.Context import com.android.systemui.R import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.domain.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.bouncer.data.repository.BouncerRepository import com.android.systemui.dagger.SysUISingleton @@ -35,7 +33,6 @@ import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine @@ -107,14 +104,6 @@ constructor( } /** - * Returns the currently-configured authentication method. This determines how the - * authentication challenge is completed in order to unlock an otherwise locked device. - */ - suspend fun getAuthenticationMethod(): AuthenticationMethodModel { - return authenticationInteractor.getAuthenticationMethod() - } - - /** * Either shows the bouncer or unlocks the device, if the bouncer doesn't need to be shown. * * @param message An optional message to show to the user in the bouncer. @@ -124,13 +113,15 @@ constructor( ) { applicationScope.launch { if (authenticationInteractor.isAuthenticationRequired()) { - repository.setMessage(message ?: promptMessage(getAuthenticationMethod())) - sceneInteractor.setCurrentScene( + repository.setMessage( + message ?: promptMessage(authenticationInteractor.getAuthenticationMethod()) + ) + sceneInteractor.changeScene( scene = SceneModel(SceneKey.Bouncer), loggingReason = "request to unlock device while authentication required", ) } else { - sceneInteractor.setCurrentScene( + sceneInteractor.changeScene( scene = SceneModel(SceneKey.Gone), loggingReason = "request to unlock device while authentication isn't required", ) @@ -143,7 +134,9 @@ constructor( * method. */ fun resetMessage() { - applicationScope.launch { repository.setMessage(promptMessage(getAuthenticationMethod())) } + applicationScope.launch { + repository.setMessage(promptMessage(authenticationInteractor.getAuthenticationMethod())) + } } /** Removes the user-facing message. */ @@ -176,12 +169,12 @@ constructor( authenticationInteractor.authenticate(input, tryAutoConfirm) ?: return null if (isAuthenticated) { - sceneInteractor.setCurrentScene( + sceneInteractor.changeScene( scene = SceneModel(SceneKey.Gone), loggingReason = "successful authentication", ) } else { - repository.setMessage(errorMessage(getAuthenticationMethod())) + repository.setMessage(errorMessage(authenticationInteractor.getAuthenticationMethod())) } return isAuthenticated diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt index d9ec5d0d1744..56f1cf661cda 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt @@ -121,7 +121,7 @@ object KeyguardBouncerViewBinder { view.visibility = if (isShowing) View.VISIBLE else View.INVISIBLE if (isShowing) { // Reset security container because these views are not reinflated. - securityContainerController.reset() + securityContainerController.prepareToShow() securityContainerController.reinflateViewFlipper { // Reset Security Container entirely. securityContainerController.onBouncerVisibilityChanged( diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt index 68e1a29bc609..5b1998d1e5f6 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt @@ -14,25 +14,20 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.bouncer.ui.viewmodel import android.content.Context import com.android.systemui.R -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor +import com.android.systemui.authentication.domain.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags -import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import kotlin.math.ceil import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.channels.BufferOverflow -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -50,23 +45,24 @@ class BouncerViewModel constructor( @Application private val applicationContext: Context, @Application private val applicationScope: CoroutineScope, - private val interactor: BouncerInteractor, + private val bouncerInteractor: BouncerInteractor, + private val authenticationInteractor: AuthenticationInteractor, featureFlags: FeatureFlags, ) { private val isInputEnabled: StateFlow<Boolean> = - interactor.isThrottled + bouncerInteractor.isThrottled .map { !it } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = !interactor.isThrottled.value, + initialValue = !bouncerInteractor.isThrottled.value, ) private val pin: PinBouncerViewModel by lazy { PinBouncerViewModel( applicationContext = applicationContext, applicationScope = applicationScope, - interactor = interactor, + interactor = bouncerInteractor, isInputEnabled = isInputEnabled, ) } @@ -74,7 +70,7 @@ constructor( private val password: PasswordBouncerViewModel by lazy { PasswordBouncerViewModel( applicationScope = applicationScope, - interactor = interactor, + interactor = bouncerInteractor, isInputEnabled = isInputEnabled, ) } @@ -83,31 +79,35 @@ constructor( PatternBouncerViewModel( applicationContext = applicationContext, applicationScope = applicationScope, - interactor = interactor, + interactor = bouncerInteractor, isInputEnabled = isInputEnabled, ) } /** View-model for the current UI, based on the current authentication method. */ - private val _authMethod = - MutableSharedFlow<AuthMethodBouncerViewModel?>( - replay = 1, - onBufferOverflow = BufferOverflow.DROP_OLDEST, - ) val authMethod: StateFlow<AuthMethodBouncerViewModel?> = - _authMethod.stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = null, - ) + authenticationInteractor.authenticationMethod + .map { authenticationMethod -> + when (authenticationMethod) { + is AuthenticationMethodModel.Pin -> pin + is AuthenticationMethodModel.Password -> password + is AuthenticationMethodModel.Pattern -> pattern + else -> null + } + } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = null, + ) init { if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) { applicationScope.launch { - interactor.isThrottled + bouncerInteractor.isThrottled .map { isThrottled -> if (isThrottled) { - when (interactor.getAuthenticationMethod()) { + when (authenticationInteractor.getAuthenticationMethod()) { is AuthenticationMethodModel.Pin -> R.string.kg_too_many_failed_pin_attempts_dialog_message is AuthenticationMethodModel.Password -> @@ -118,8 +118,9 @@ constructor( }?.let { stringResourceId -> applicationContext.getString( stringResourceId, - interactor.throttling.value.failedAttemptCount, - ceil(interactor.throttling.value.remainingMs / 1000f).toInt(), + bouncerInteractor.throttling.value.failedAttemptCount, + ceil(bouncerInteractor.throttling.value.remainingMs / 1000f) + .toInt(), ) } } else { @@ -133,25 +134,14 @@ constructor( } } } - - applicationScope.launch { - _authMethod.subscriptionCount - .pairwise() - .map { (previousCount, currentCount) -> currentCount > previousCount } - .collect { subscriberAdded -> - if (subscriberAdded) { - reloadAuthMethod() - } - } - } } } /** The user-facing message to show in the bouncer. */ val message: StateFlow<MessageViewModel> = combine( - interactor.message, - interactor.isThrottled, + bouncerInteractor.message, + bouncerInteractor.isThrottled, ) { message, isThrottled -> toMessageViewModel(message, isThrottled) } @@ -160,8 +150,8 @@ constructor( started = SharingStarted.WhileSubscribed(), initialValue = toMessageViewModel( - message = interactor.message.value, - isThrottled = interactor.isThrottled.value, + message = bouncerInteractor.message.value, + isThrottled = bouncerInteractor.isThrottled.value, ), ) @@ -197,17 +187,6 @@ constructor( ) } - private suspend fun reloadAuthMethod() { - _authMethod.tryEmit( - when (interactor.getAuthenticationMethod()) { - is AuthenticationMethodModel.Pin -> pin - is AuthenticationMethodModel.Password -> password - is AuthenticationMethodModel.Pattern -> pattern - else -> null - } - ) - } - data class MessageViewModel( val text: String, diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt index 4be539d9396d..4425f9ffcb5e 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt @@ -18,7 +18,7 @@ package com.android.systemui.bouncer.ui.viewmodel import android.content.Context import android.util.TypedValue -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import kotlin.math.max import kotlin.math.min @@ -193,8 +193,8 @@ data class PatternDotViewModel( val x: Int, val y: Int, ) { - fun toCoordinate(): AuthenticationMethodModel.Pattern.PatternCoordinate { - return AuthenticationMethodModel.Pattern.PatternCoordinate( + fun toCoordinate(): AuthenticationPatternCoordinate { + return AuthenticationPatternCoordinate( x = x, y = y, ) diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index f6257c46e6a7..970d00b2f4bd 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -29,6 +29,7 @@ import com.android.systemui.BootCompleteCache; import com.android.systemui.BootCompleteCacheImpl; import com.android.systemui.accessibility.AccessibilityModule; import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule; +import com.android.systemui.aconfig.AConfigModule; import com.android.systemui.appops.dagger.AppOpsModule; import com.android.systemui.assist.AssistModule; import com.android.systemui.authentication.AuthenticationModule; @@ -156,6 +157,7 @@ import javax.inject.Named; @Module(includes = { AccessibilityModule.class, AccessibilityRepositoryModule.class, + AConfigModule.class, AppOpsModule.class, AssistModule.class, AuthenticationModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java index 2c11d78f534b..4c78e4ce4fe4 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java @@ -32,7 +32,6 @@ import android.content.IntentFilter; import android.content.res.Resources; import android.os.Bundle; import android.os.UserHandle; -import android.provider.DeviceConfig; import android.util.Log; import androidx.annotation.NonNull; @@ -94,8 +93,7 @@ public class FeatureFlagsDebug implements FeatureFlags { shouldRestart = true; } } else if (mStringFlagCache.containsKey(flag.getName())) { - String newValue = value == null ? "" : value; - if (mStringFlagCache.get(flag.getName()) != value) { + if (!mStringFlagCache.get(flag.getName()).equals(value)) { shouldRestart = true; } } else if (mIntFlagCache.containsKey(flag.getName())) { @@ -203,8 +201,7 @@ public class FeatureFlagsDebug implements FeatureFlags { String name = flag.getName(); if (!mStringFlagCache.containsKey(name)) { mStringFlagCache.put(name, - readFlagValueInternal( - flag.getId(), name, flag.getDefault(), StringFlagSerializer.INSTANCE)); + readFlagValueInternal(name, flag.getDefault(), StringFlagSerializer.INSTANCE)); } return mStringFlagCache.get(name); @@ -216,36 +213,30 @@ public class FeatureFlagsDebug implements FeatureFlags { String name = flag.getName(); if (!mStringFlagCache.containsKey(name)) { mStringFlagCache.put(name, - readFlagValueInternal( - flag.getId(), name, mResources.getString(flag.getResourceId()), + readFlagValueInternal(name, mResources.getString(flag.getResourceId()), StringFlagSerializer.INSTANCE)); } return mStringFlagCache.get(name); } - - @NonNull @Override public int getInt(@NonNull IntFlag flag) { String name = flag.getName(); if (!mIntFlagCache.containsKey(name)) { mIntFlagCache.put(name, - readFlagValueInternal( - flag.getId(), name, flag.getDefault(), IntFlagSerializer.INSTANCE)); + readFlagValueInternal(name, flag.getDefault(), IntFlagSerializer.INSTANCE)); } return mIntFlagCache.get(name); } - @NonNull @Override public int getInt(@NonNull ResourceIntFlag flag) { String name = flag.getName(); if (!mIntFlagCache.containsKey(name)) { mIntFlagCache.put(name, - readFlagValueInternal( - flag.getId(), name, mResources.getInteger(flag.getResourceId()), + readFlagValueInternal(name, mResources.getInteger(flag.getResourceId()), IntFlagSerializer.INSTANCE)); } @@ -255,13 +246,6 @@ public class FeatureFlagsDebug implements FeatureFlags { /** Specific override for Boolean flags that checks against the teamfood list.*/ private boolean readBooleanFlagInternal(Flag<Boolean> flag, boolean defaultValue) { Boolean result = readBooleanFlagOverride(flag.getName()); - if (result == null) { - result = readBooleanFlagOverride(flag.getId()); - if (result != null) { - // Move overrides from id to name - setFlagValueInternal(flag.getName(), result, BooleanFlagSerializer.INSTANCE); - } - } boolean hasServerOverride = mServerFlagReader.hasOverride( flag.getNamespace(), flag.getName()); @@ -279,45 +263,22 @@ public class FeatureFlagsDebug implements FeatureFlags { flag.getNamespace(), flag.getName(), defaultValue) : result; } - private Boolean readBooleanFlagOverride(int id) { - return readFlagValueInternal(id, BooleanFlagSerializer.INSTANCE); - } private Boolean readBooleanFlagOverride(String name) { return readFlagValueInternal(name, BooleanFlagSerializer.INSTANCE); } - // TODO(b/265188950): Remove id from this method once ids are fully deprecated. @NonNull private <T> T readFlagValueInternal( - int id, String name, @NonNull T defaultValue, FlagSerializer<T> serializer) { + String name, @NonNull T defaultValue, FlagSerializer<T> serializer) { requireNonNull(defaultValue, "defaultValue"); T resultForName = readFlagValueInternal(name, serializer); if (resultForName == null) { - T resultForId = readFlagValueInternal(id, serializer); - if (resultForId == null) { - return defaultValue; - } else { - setFlagValue(name, resultForId, serializer); - return resultForId; - } + return defaultValue; } return resultForName; } - - /** Returns the stored value or null if not set. */ - // TODO(b/265188950): Remove method this once ids are fully deprecated. - @Nullable - private <T> T readFlagValueInternal(int id, FlagSerializer<T> serializer) { - try { - return mFlagManager.readFlagValue(id, serializer); - } catch (Exception e) { - eraseInternal(id); - } - return null; - } - /** Returns the stored value or null if not set. */ @Nullable private <T> T readFlagValueInternal(String name, FlagSerializer<T> serializer) { @@ -385,15 +346,6 @@ public class FeatureFlagsDebug implements FeatureFlags { } /** Works just like {@link #eraseFlag(String)} except that it doesn't restart SystemUI. */ - // TODO(b/265188950): Remove method this once ids are fully deprecated. - private void eraseInternal(int id) { - // We can't actually "erase" things from settings, but we can set them to empty! - mGlobalSettings.putStringForUser(mFlagManager.idToSettingsKey(id), "", - UserHandle.USER_CURRENT); - Log.i(TAG, "Erase name " + id); - } - - /** Works just like {@link #eraseFlag(String)} except that it doesn't restart SystemUI. */ private void eraseInternal(String name) { // We can't actually "erase" things from settings, but we can set them to empty! mGlobalSettings.putStringForUser(mFlagManager.nameToSettingsKey(name), "", @@ -434,7 +386,7 @@ public class FeatureFlagsDebug implements FeatureFlags { setFlagValue(flag.getName(), value, BooleanFlagSerializer.INSTANCE); } else if (flag instanceof SysPropBooleanFlag) { // Store SysProp flags in SystemProperties where they can read by outside parties. - mSystemProperties.setBoolean(((SysPropBooleanFlag) flag).getName(), value); + mSystemProperties.setBoolean(flag.getName(), value); dispatchListenersAndMaybeRestart( flag.getName(), suppressRestart -> restartSystemUI( @@ -527,7 +479,7 @@ public class FeatureFlagsDebug implements FeatureFlags { } } catch (IllegalArgumentException e) { Log.w(TAG, - "Unable to set " + flag.getId() + " of type " + flag.getClass() + "Unable to set " + flag.getName() + " of type " + flag.getClass() + " to value of type " + (value == null ? null : value.getClass())); } } @@ -545,21 +497,18 @@ public class FeatureFlagsDebug implements FeatureFlags { if (f instanceof ReleasedFlag) { enabled = isEnabled((ReleasedFlag) f); - overridden = readBooleanFlagOverride(f.getName()) != null - || readBooleanFlagOverride(f.getId()) != null; + overridden = readBooleanFlagOverride(f.getName()) != null; } else if (f instanceof UnreleasedFlag) { enabled = isEnabled((UnreleasedFlag) f); - overridden = readBooleanFlagOverride(f.getName()) != null - || readBooleanFlagOverride(f.getId()) != null; + overridden = readBooleanFlagOverride(f.getName()) != null; } else if (f instanceof ResourceBooleanFlag) { enabled = isEnabled((ResourceBooleanFlag) f); - overridden = readBooleanFlagOverride(f.getName()) != null - || readBooleanFlagOverride(f.getId()) != null; + overridden = readBooleanFlagOverride(f.getName()) != null; } else if (f instanceof SysPropBooleanFlag) { // TODO(b/223379190): Teamfood not supported for sysprop flags yet. enabled = isEnabled((SysPropBooleanFlag) f); teamfood = false; - overridden = !mSystemProperties.get(((SysPropBooleanFlag) f).getName()).isEmpty(); + overridden = !mSystemProperties.get(f.getName()).isEmpty(); } else { // TODO: add support for other flag types. Log.w(TAG, "Unsupported Flag Type. Please file a bug."); @@ -567,11 +516,9 @@ public class FeatureFlagsDebug implements FeatureFlags { } if (enabled) { - return new ReleasedFlag( - f.getId(), f.getName(), f.getNamespace(), teamfood, overridden); + return new ReleasedFlag(f.getName(), f.getNamespace(), teamfood, overridden); } else { - return new UnreleasedFlag( - f.getId(), f.getName(), f.getNamespace(), teamfood, overridden); + return new UnreleasedFlag(f.getName(), f.getNamespace(), teamfood, overridden); } } }; diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java index 9d19a7dc4604..e03b43873e05 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java @@ -67,7 +67,7 @@ public class FeatureFlagsRelease implements FeatureFlags { } } else if (mStringCache.containsKey(flag.getName())) { String newValue = value == null ? "" : value; - if (mStringCache.get(flag.getName()) != newValue) { + if (!mStringCache.get(flag.getName()).equals(newValue)) { shouldRestart = true; } } else if (mIntCache.containsKey(flag.getName())) { @@ -185,14 +185,12 @@ public class FeatureFlagsRelease implements FeatureFlags { return mStringCache.get(name); } - @NonNull @Override public int getInt(@NonNull IntFlag flag) { // Fill the cache. return getIntInternal(flag.getName(), flag.getDefault()); } - @NonNull @Override public int getInt(@NonNull ResourceIntFlag flag) { // Fill the cache. diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java index daf9429344a7..bd0ed482c961 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java @@ -219,17 +219,6 @@ public class FlagCommand implements Command { return 0; } - private int flagNameToId(String flagName) { - Map<String, Flag<?>> flagFields = FlagsFactory.INSTANCE.getKnownFlags(); - for (String fieldName : flagFields.keySet()) { - if (flagName.equals(fieldName)) { - return flagFields.get(fieldName).getId(); - } - } - - return 0; - } - private void printKnownFlags(PrintWriter pw) { Map<String, Flag<?>> fields = FlagsFactory.INSTANCE.getKnownFlags(); diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index ab219371ffc6..e77d355ac15f 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -36,30 +36,29 @@ import com.android.systemui.flags.FlagsFactory.unreleasedFlag * See [FeatureFlagsDebug] for instructions on flipping the flags via adb. */ object Flags { - @JvmField val TEAMFOOD = unreleasedFlag(1, "teamfood") + @JvmField val TEAMFOOD = unreleasedFlag("teamfood") // 100 - notification // TODO(b/254512751): Tracking Bug val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING = - unreleasedFlag(103, "notification_pipeline_developer_logging") + unreleasedFlag("notification_pipeline_developer_logging") // TODO(b/254512732): Tracking Bug - @JvmField val NSSL_DEBUG_LINES = unreleasedFlag(105, "nssl_debug_lines") + @JvmField val NSSL_DEBUG_LINES = unreleasedFlag("nssl_debug_lines") // TODO(b/254512505): Tracking Bug - @JvmField val NSSL_DEBUG_REMOVE_ANIMATION = unreleasedFlag(106, "nssl_debug_remove_animation") + @JvmField val NSSL_DEBUG_REMOVE_ANIMATION = unreleasedFlag("nssl_debug_remove_animation") // TODO(b/254512624): Tracking Bug @JvmField val NOTIFICATION_DRAG_TO_CONTENTS = resourceBooleanFlag( - 108, R.bool.config_notificationToContents, "notification_drag_to_contents" ) // TODO(b/254512538): Tracking Bug - val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply") + val INSTANT_VOICE_REPLY = unreleasedFlag("instant_voice_reply") /** * This flag controls whether we register a listener for StatsD notification memory reports. @@ -67,48 +66,47 @@ object Flags { * enabled as well. */ val NOTIFICATION_MEMORY_LOGGING_ENABLED = - releasedFlag(119, "notification_memory_logging_enabled") + releasedFlag("notification_memory_logging_enabled") // TODO(b/260335638): Tracking Bug @JvmField val NOTIFICATION_INLINE_REPLY_ANIMATION = - unreleasedFlag(174148361, "notification_inline_reply_animation") + unreleasedFlag("notification_inline_reply_animation") /** Makes sure notification panel is updated before the user switch is complete. */ // TODO(b/278873737): Tracking Bug @JvmField val LOAD_NOTIFICATIONS_BEFORE_THE_USER_SWITCH_IS_COMPLETE = - releasedFlag(278873737, "load_notifications_before_the_user_switch_is_complete") + releasedFlag("load_notifications_before_the_user_switch_is_complete") // TODO(b/277338665): Tracking Bug @JvmField val NOTIFICATION_SHELF_REFACTOR = - unreleasedFlag(271161129, "notification_shelf_refactor", teamfood = true) + unreleasedFlag("notification_shelf_refactor", teamfood = true) // TODO(b/290787599): Tracking Bug @JvmField val NOTIFICATION_ICON_CONTAINER_REFACTOR = - unreleasedFlag(278765923, "notification_icon_container_refactor") + unreleasedFlag("notification_icon_container_refactor") // TODO(b/288326013): Tracking Bug @JvmField val NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION = - unreleasedFlag(288326013, "notification_async_hybrid_view_inflation", teamfood = false) + unreleasedFlag("notification_async_hybrid_view_inflation", teamfood = false) @JvmField val ANIMATED_NOTIFICATION_SHADE_INSETS = - releasedFlag(270682168, "animated_notification_shade_insets") + releasedFlag("animated_notification_shade_insets") // TODO(b/268005230): Tracking Bug @JvmField - val SENSITIVE_REVEAL_ANIM = unreleasedFlag(268005230, "sensitive_reveal_anim", teamfood = true) + val SENSITIVE_REVEAL_ANIM = unreleasedFlag("sensitive_reveal_anim", teamfood = true) // TODO(b/280783617): Tracking Bug @Keep @JvmField val BUILDER_EXTRAS_OVERRIDE = sysPropBooleanFlag( - 128, "persist.sysui.notification.builder_extras_override", default = true ) @@ -117,27 +115,26 @@ object Flags { // TODO(b/292213543): Tracking Bug @JvmField val NOTIFICATION_GROUP_EXPANSION_CHANGE = - unreleasedFlag(292213543, "notification_group_expansion_change", teamfood = false) + unreleasedFlag("notification_group_expansion_change", teamfood = false) // 200 - keyguard/lockscreen // ** Flag retired ** // public static final BooleanFlag KEYGUARD_LAYOUT = - // new BooleanFlag(200, true); + // new BooleanFlag(true); // TODO(b/254512750): Tracking Bug - val NEW_UNLOCK_SWIPE_ANIMATION = releasedFlag(202, "new_unlock_swipe_animation") - val CHARGING_RIPPLE = resourceBooleanFlag(203, R.bool.flag_charging_ripple, "charging_ripple") + val NEW_UNLOCK_SWIPE_ANIMATION = releasedFlag("new_unlock_swipe_animation") + val CHARGING_RIPPLE = resourceBooleanFlag(R.bool.flag_charging_ripple, "charging_ripple") // TODO(b/254512281): Tracking Bug @JvmField val BOUNCER_USER_SWITCHER = - resourceBooleanFlag(204, R.bool.config_enableBouncerUserSwitcher, "bouncer_user_switcher") + resourceBooleanFlag(R.bool.config_enableBouncerUserSwitcher, "bouncer_user_switcher") // TODO(b/254512676): Tracking Bug @JvmField val LOCKSCREEN_CUSTOM_CLOCKS = resourceBooleanFlag( - 207, R.bool.config_enableLockScreenCustomClocks, "lockscreen_custom_clocks" ) @@ -145,28 +142,28 @@ object Flags { // TODO(b/275694445): Tracking Bug @JvmField val LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING = - releasedFlag(208, "lockscreen_without_secure_lock_when_dreaming") + releasedFlag("lockscreen_without_secure_lock_when_dreaming") // TODO(b/286092087): Tracking Bug @JvmField - val ENABLE_SYSTEM_UI_DREAM_CONTROLLER = unreleasedFlag(301, "enable_system_ui_dream_controller") + val ENABLE_SYSTEM_UI_DREAM_CONTROLLER = unreleasedFlag("enable_system_ui_dream_controller") // TODO(b/288287730): Tracking Bug @JvmField - val ENABLE_SYSTEM_UI_DREAM_HOSTING = unreleasedFlag(302, "enable_system_ui_dream_hosting") + val ENABLE_SYSTEM_UI_DREAM_HOSTING = unreleasedFlag("enable_system_ui_dream_hosting") /** * Whether the clock on a wide lock screen should use the new "stepping" animation for moving * the digits when the clock moves. */ - @JvmField val STEP_CLOCK_ANIMATION = releasedFlag(212, "step_clock_animation") + @JvmField val STEP_CLOCK_ANIMATION = releasedFlag("step_clock_animation") /** * Migration from the legacy isDozing/dozeAmount paths to the new KeyguardTransitionRepository * will occur in stages. This is one stage of many to come. */ // TODO(b/255607168): Tracking Bug - @JvmField val DOZING_MIGRATION_1 = unreleasedFlag(213, "dozing_migration_1") + @JvmField val DOZING_MIGRATION_1 = unreleasedFlag("dozing_migration_1") /** * Migrates control of the LightRevealScrim's reveal effect and amount from legacy code to the @@ -174,80 +171,76 @@ object Flags { */ // TODO(b/281655028): Tracking bug @JvmField - val LIGHT_REVEAL_MIGRATION = unreleasedFlag(218, "light_reveal_migration", teamfood = false) + val LIGHT_REVEAL_MIGRATION = unreleasedFlag("light_reveal_migration", teamfood = false) /** Flag to control the migration of face auth to modern architecture. */ // TODO(b/262838215): Tracking bug - @JvmField val FACE_AUTH_REFACTOR = unreleasedFlag(220, "face_auth_refactor") + @JvmField val FACE_AUTH_REFACTOR = unreleasedFlag("face_auth_refactor") /** Flag to control the revamp of keyguard biometrics progress animation */ // TODO(b/244313043): Tracking bug - @JvmField val BIOMETRICS_ANIMATION_REVAMP = unreleasedFlag(221, "biometrics_animation_revamp") + @JvmField val BIOMETRICS_ANIMATION_REVAMP = unreleasedFlag("biometrics_animation_revamp") // TODO(b/262780002): Tracking Bug - @JvmField val REVAMPED_WALLPAPER_UI = releasedFlag(222, "revamped_wallpaper_ui") + @JvmField val REVAMPED_WALLPAPER_UI = releasedFlag("revamped_wallpaper_ui") // flag for controlling auto pin confirmation and material u shapes in bouncer @JvmField - val AUTO_PIN_CONFIRMATION = releasedFlag(224, "auto_pin_confirmation", "auto_pin_confirmation") + val AUTO_PIN_CONFIRMATION = releasedFlag("auto_pin_confirmation", "auto_pin_confirmation") // TODO(b/262859270): Tracking Bug - @JvmField val FALSING_OFF_FOR_UNFOLDED = releasedFlag(225, "falsing_off_for_unfolded") + @JvmField val FALSING_OFF_FOR_UNFOLDED = releasedFlag("falsing_off_for_unfolded") /** Enables code to show contextual loyalty cards in wallet entrypoints */ // TODO(b/294110497): Tracking Bug @JvmField val ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS = - unreleasedFlag(226, "enable_wallet_contextual_loyalty_cards", teamfood = true) + unreleasedFlag("enable_wallet_contextual_loyalty_cards", teamfood = true) // TODO(b/242908637): Tracking Bug - @JvmField val WALLPAPER_FULLSCREEN_PREVIEW = releasedFlag(227, "wallpaper_fullscreen_preview") + @JvmField val WALLPAPER_FULLSCREEN_PREVIEW = releasedFlag("wallpaper_fullscreen_preview") /** Whether the long-press gesture to open wallpaper picker is enabled. */ // TODO(b/266242192): Tracking Bug @JvmField - val LOCK_SCREEN_LONG_PRESS_ENABLED = releasedFlag(228, "lock_screen_long_press_enabled") + val LOCK_SCREEN_LONG_PRESS_ENABLED = releasedFlag("lock_screen_long_press_enabled") /** Enables UI updates for AI wallpapers in the wallpaper picker. */ // TODO(b/267722622): Tracking Bug - @JvmField val WALLPAPER_PICKER_UI_FOR_AIWP = releasedFlag(229, "wallpaper_picker_ui_for_aiwp") + @JvmField val WALLPAPER_PICKER_UI_FOR_AIWP = releasedFlag("wallpaper_picker_ui_for_aiwp") /** Whether to use a new data source for intents to run on keyguard dismissal. */ // TODO(b/275069969): Tracking bug. @JvmField - val REFACTOR_KEYGUARD_DISMISS_INTENT = unreleasedFlag(231, "refactor_keyguard_dismiss_intent") + val REFACTOR_KEYGUARD_DISMISS_INTENT = unreleasedFlag("refactor_keyguard_dismiss_intent") /** Whether to allow long-press on the lock screen to directly open wallpaper picker. */ // TODO(b/277220285): Tracking bug. @JvmField val LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP = - unreleasedFlag(232, "lock_screen_long_press_directly_opens_wallpaper_picker") + unreleasedFlag("lock_screen_long_press_directly_opens_wallpaper_picker") /** Whether page transition animations in the wallpaper picker are enabled */ // TODO(b/291710220): Tracking bug. @JvmField val WALLPAPER_PICKER_PAGE_TRANSITIONS = - unreleasedFlag(291710220, "wallpaper_picker_page_transitions") + unreleasedFlag("wallpaper_picker_page_transitions") /** Whether to run the new udfps keyguard refactor code. */ // TODO(b/279440316): Tracking bug. @JvmField - val REFACTOR_UDFPS_KEYGUARD_VIEWS = unreleasedFlag(233, "refactor_udfps_keyguard_views") + val REFACTOR_UDFPS_KEYGUARD_VIEWS = unreleasedFlag("refactor_udfps_keyguard_views") /** Provide new auth messages on the bouncer. */ // TODO(b/277961132): Tracking bug. - @JvmField val REVAMPED_BOUNCER_MESSAGES = unreleasedFlag(234, "revamped_bouncer_messages") + @JvmField val REVAMPED_BOUNCER_MESSAGES = unreleasedFlag("revamped_bouncer_messages") /** Whether to delay showing bouncer UI when face auth or active unlock are enrolled. */ // TODO(b/279794160): Tracking bug. - @JvmField val DELAY_BOUNCER = releasedFlag(235, "delay_bouncer") + @JvmField val DELAY_BOUNCER = releasedFlag("delay_bouncer") /** Keyguard Migration */ - /** Migrate the indication area to the new keyguard root view. */ - // TODO(b/280067944): Tracking bug. - @JvmField val MIGRATE_INDICATION_AREA = releasedFlag(236, "migrate_indication_area") - /** * Migrate the bottom area to the new keyguard root view. Because there is no such thing as a * "bottom area" after this, this also breaks it up into many smaller, modular pieces. @@ -255,203 +248,213 @@ object Flags { // TODO(b/290652751): Tracking bug. @JvmField val MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA = - unreleasedFlag(290652751, "migrate_split_keyguard_bottom_area") + unreleasedFlag("migrate_split_keyguard_bottom_area") /** Whether to listen for fingerprint authentication over keyguard occluding activities. */ // TODO(b/283260512): Tracking bug. - @JvmField val FP_LISTEN_OCCLUDING_APPS = unreleasedFlag(237, "fp_listen_occluding_apps") + @JvmField val FP_LISTEN_OCCLUDING_APPS = unreleasedFlag("fp_listen_occluding_apps", + teamfood = true) /** Flag meant to guard the talkback fix for the KeyguardIndicationTextView */ // TODO(b/286563884): Tracking bug - @JvmField val KEYGUARD_TALKBACK_FIX = releasedFlag(238, "keyguard_talkback_fix") + @JvmField val KEYGUARD_TALKBACK_FIX = releasedFlag("keyguard_talkback_fix") // TODO(b/287268101): Tracking bug. - @JvmField val TRANSIT_CLOCK = unreleasedFlag(239, "lockscreen_custom_transit_clock", teamfood = true) + @JvmField val TRANSIT_CLOCK = releasedFlag("lockscreen_custom_transit_clock") /** Migrate the lock icon view to the new keyguard root view. */ // TODO(b/286552209): Tracking bug. - @JvmField val MIGRATE_LOCK_ICON = unreleasedFlag(240, "migrate_lock_icon") + @JvmField val MIGRATE_LOCK_ICON = unreleasedFlag("migrate_lock_icon") // TODO(b/288276738): Tracking bug. - @JvmField val WIDGET_ON_KEYGUARD = unreleasedFlag(241, "widget_on_keyguard") + @JvmField val WIDGET_ON_KEYGUARD = unreleasedFlag("widget_on_keyguard") /** Migrate the NSSL to the a sibling to both the panel and keyguard root view. */ // TODO(b/288074305): Tracking bug. - @JvmField val MIGRATE_NSSL = unreleasedFlag(242, "migrate_nssl") + @JvmField val MIGRATE_NSSL = unreleasedFlag("migrate_nssl") /** Migrate the status view from the notification panel to keyguard root view. */ // TODO(b/291767565): Tracking bug. - @JvmField val MIGRATE_KEYGUARD_STATUS_VIEW = unreleasedFlag(243, "migrate_keyguard_status_view") + @JvmField val MIGRATE_KEYGUARD_STATUS_VIEW = unreleasedFlag("migrate_keyguard_status_view") /** Enables preview loading animation in the wallpaper picker. */ // TODO(b/274443705): Tracking Bug @JvmField val WALLPAPER_PICKER_PREVIEW_ANIMATION = unreleasedFlag( - 244, - "wallpaper_picker_preview_animation" + "wallpaper_picker_preview_animation", + teamfood = true ) + /** Stop running face auth when the display state changes to OFF. */ + // TODO(b/294221702): Tracking bug. + @JvmField val STOP_FACE_AUTH_ON_DISPLAY_OFF = resourceBooleanFlag( + R.bool.flag_stop_face_auth_on_display_off, "stop_face_auth_on_display_off") + // 300 - power menu // TODO(b/254512600): Tracking Bug - @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite") + @JvmField val POWER_MENU_LITE = releasedFlag("power_menu_lite") // 400 - smartspace // TODO(b/254513100): Tracking Bug val SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED = - releasedFlag(401, "smartspace_shared_element_transition_enabled") + releasedFlag("smartspace_shared_element_transition_enabled") // TODO(b/258517050): Clean up after the feature is launched. @JvmField val SMARTSPACE_DATE_WEATHER_DECOUPLED = - sysPropBooleanFlag(403, "persist.sysui.ss.dw_decoupled", default = true) + sysPropBooleanFlag("persist.sysui.ss.dw_decoupled", default = true) // TODO(b/270223352): Tracking Bug @JvmField - val HIDE_SMARTSPACE_ON_DREAM_OVERLAY = releasedFlag(404, "hide_smartspace_on_dream_overlay") + val HIDE_SMARTSPACE_ON_DREAM_OVERLAY = releasedFlag("hide_smartspace_on_dream_overlay") // TODO(b/271460958): Tracking Bug @JvmField val SHOW_WEATHER_COMPLICATION_ON_DREAM_OVERLAY = - releasedFlag(405, "show_weather_complication_on_dream_overlay") + releasedFlag("show_weather_complication_on_dream_overlay") // 500 - quick settings - val PEOPLE_TILE = resourceBooleanFlag(502, R.bool.flag_conversations, "people_tile") + val PEOPLE_TILE = resourceBooleanFlag(R.bool.flag_conversations, "people_tile") @JvmField val QS_USER_DETAIL_SHORTCUT = resourceBooleanFlag( - 503, R.bool.flag_lockscreen_qs_user_detail_shortcut, "qs_user_detail_shortcut" ) @JvmField - val QS_PIPELINE_NEW_HOST = unreleasedFlag(504, "qs_pipeline_new_host", teamfood = true) + val QS_PIPELINE_NEW_HOST = unreleasedFlag("qs_pipeline_new_host", teamfood = true) // TODO(b/278068252): Tracking Bug @JvmField - val QS_PIPELINE_AUTO_ADD = unreleasedFlag(505, "qs_pipeline_auto_add", teamfood = false) + val QS_PIPELINE_AUTO_ADD = unreleasedFlag("qs_pipeline_auto_add", teamfood = false) // TODO(b/254512383): Tracking Bug @JvmField val FULL_SCREEN_USER_SWITCHER = resourceBooleanFlag( - 506, R.bool.config_enableFullscreenUserSwitcher, "full_screen_user_switcher" ) // TODO(b/244064524): Tracking Bug - @JvmField val QS_SECONDARY_DATA_SUB_INFO = releasedFlag(508, "qs_secondary_data_sub_info") + @JvmField val QS_SECONDARY_DATA_SUB_INFO = releasedFlag("qs_secondary_data_sub_info") /** Enables Font Scaling Quick Settings tile */ // TODO(b/269341316): Tracking Bug - @JvmField val ENABLE_FONT_SCALING_TILE = releasedFlag(509, "enable_font_scaling_tile") + @JvmField val ENABLE_FONT_SCALING_TILE = releasedFlag("enable_font_scaling_tile") /** Enables new QS Edit Mode visual refresh */ // TODO(b/269787742): Tracking Bug @JvmField - val ENABLE_NEW_QS_EDIT_MODE = unreleasedFlag(510, "enable_new_qs_edit_mode", teamfood = false) + val ENABLE_NEW_QS_EDIT_MODE = unreleasedFlag("enable_new_qs_edit_mode", teamfood = false) // 600- status bar // TODO(b/265892345): Tracking Bug - val PLUG_IN_STATUS_BAR_CHIP = releasedFlag(265892345, "plug_in_status_bar_chip") + val PLUG_IN_STATUS_BAR_CHIP = releasedFlag("plug_in_status_bar_chip") // TODO(b/280426085): Tracking Bug - @JvmField val NEW_BLUETOOTH_REPOSITORY = releasedFlag(612, "new_bluetooth_repository") + @JvmField val NEW_BLUETOOTH_REPOSITORY = releasedFlag("new_bluetooth_repository") // TODO(b/292533677): Tracking Bug val WIFI_TRACKER_LIB_FOR_WIFI_ICON = - unreleasedFlag(613, "wifi_tracker_lib_for_wifi_icon") + unreleasedFlag("wifi_tracker_lib_for_wifi_icon") + + // TODO(b/293863612): Tracking Bug + @JvmField val INCOMPATIBLE_CHARGING_BATTERY_ICON = + unreleasedFlag("incompatible_charging_battery_icon") + + // TODO(b/293585143): Tracking Bug + val INSTANT_TETHER = unreleasedFlag("instant_tether") // 700 - dialer/calls // TODO(b/254512734): Tracking Bug - val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag(700, "ongoing_call_status_bar_chip") + val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag("ongoing_call_status_bar_chip") // TODO(b/254512681): Tracking Bug - val ONGOING_CALL_IN_IMMERSIVE = releasedFlag(701, "ongoing_call_in_immersive") + val ONGOING_CALL_IN_IMMERSIVE = releasedFlag("ongoing_call_in_immersive") // TODO(b/254512753): Tracking Bug - val ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP = releasedFlag(702, "ongoing_call_in_immersive_chip_tap") + val ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP = releasedFlag("ongoing_call_in_immersive_chip_tap") // 800 - general visual/theme - @JvmField val MONET = resourceBooleanFlag(800, R.bool.flag_monet, "monet") + @JvmField val MONET = resourceBooleanFlag(R.bool.flag_monet, "monet") // 801 - region sampling // TODO(b/254512848): Tracking Bug - val REGION_SAMPLING = unreleasedFlag(801, "region_sampling") + val REGION_SAMPLING = unreleasedFlag("region_sampling") // 803 - screen contents translation // TODO(b/254513187): Tracking Bug - val SCREEN_CONTENTS_TRANSLATION = unreleasedFlag(803, "screen_contents_translation") + val SCREEN_CONTENTS_TRANSLATION = unreleasedFlag("screen_contents_translation") // 804 - monochromatic themes - @JvmField val MONOCHROMATIC_THEME = releasedFlag(804, "monochromatic") + @JvmField val MONOCHROMATIC_THEME = releasedFlag("monochromatic") // TODO(b/293380347): Tracking Bug - @JvmField val COLOR_FIDELITY = unreleasedFlag(805, "color_fidelity") + @JvmField val COLOR_FIDELITY = unreleasedFlag("color_fidelity") // 900 - media // TODO(b/254512697): Tracking Bug - val MEDIA_TAP_TO_TRANSFER = releasedFlag(900, "media_tap_to_transfer") + val MEDIA_TAP_TO_TRANSFER = releasedFlag("media_tap_to_transfer") // TODO(b/254512502): Tracking Bug - val MEDIA_SESSION_ACTIONS = unreleasedFlag(901, "media_session_actions") + val MEDIA_SESSION_ACTIONS = unreleasedFlag("media_session_actions") // TODO(b/254512654): Tracking Bug - @JvmField val DREAM_MEDIA_COMPLICATION = unreleasedFlag(905, "dream_media_complication") + @JvmField val DREAM_MEDIA_COMPLICATION = unreleasedFlag("dream_media_complication") // TODO(b/254512673): Tracking Bug - @JvmField val DREAM_MEDIA_TAP_TO_OPEN = unreleasedFlag(906, "dream_media_tap_to_open") + @JvmField val DREAM_MEDIA_TAP_TO_OPEN = unreleasedFlag("dream_media_tap_to_open") // TODO(b/254513168): Tracking Bug - @JvmField val UMO_SURFACE_RIPPLE = releasedFlag(907, "umo_surface_ripple") + @JvmField val UMO_SURFACE_RIPPLE = releasedFlag("umo_surface_ripple") // TODO(b/261734857): Tracking Bug - @JvmField val UMO_TURBULENCE_NOISE = releasedFlag(909, "umo_turbulence_noise") + @JvmField val UMO_TURBULENCE_NOISE = releasedFlag("umo_turbulence_noise") // TODO(b/263272731): Tracking Bug - val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE = releasedFlag(910, "media_ttt_receiver_success_ripple") + val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE = releasedFlag("media_ttt_receiver_success_ripple") // TODO(b/266157412): Tracking Bug - val MEDIA_RETAIN_SESSIONS = unreleasedFlag(913, "media_retain_sessions") + val MEDIA_RETAIN_SESSIONS = unreleasedFlag("media_retain_sessions") // TODO(b/267007629): Tracking Bug - val MEDIA_RESUME_PROGRESS = releasedFlag(915, "media_resume_progress") + val MEDIA_RESUME_PROGRESS = releasedFlag("media_resume_progress") // TODO(b/267166152) : Tracking Bug - val MEDIA_RETAIN_RECOMMENDATIONS = unreleasedFlag(916, "media_retain_recommendations") + val MEDIA_RETAIN_RECOMMENDATIONS = unreleasedFlag("media_retain_recommendations") // TODO(b/270437894): Tracking Bug - val MEDIA_REMOTE_RESUME = unreleasedFlag(917, "media_remote_resume") + val MEDIA_REMOTE_RESUME = unreleasedFlag("media_remote_resume") // 1000 - dock - val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging") + val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag("simulate_dock_through_charging") // TODO(b/254512758): Tracking Bug - @JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple") + @JvmField val ROUNDED_BOX_RIPPLE = releasedFlag("rounded_box_ripple") // TODO(b/273509374): Tracking Bug @JvmField val ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS = - releasedFlag(1006, "always_show_home_controls_on_dreams") + releasedFlag("always_show_home_controls_on_dreams") // 1100 - windowing @Keep @JvmField val WM_ENABLE_SHELL_TRANSITIONS = - sysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", default = true) + sysPropBooleanFlag("persist.wm.debug.shell_transit", default = true) // TODO(b/254513207): Tracking Bug @Keep @JvmField val WM_ENABLE_PARTIAL_SCREEN_SHARING = unreleasedFlag( - 1102, name = "record_task_content", namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER, teamfood = true @@ -461,50 +464,49 @@ object Flags { @Keep @JvmField val HIDE_NAVBAR_WINDOW = - sysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", default = false) + sysPropBooleanFlag("persist.wm.debug.hide_navbar_window", default = false) @Keep @JvmField val WM_DESKTOP_WINDOWING = - sysPropBooleanFlag(1104, "persist.wm.debug.desktop_mode", default = false) + sysPropBooleanFlag("persist.wm.debug.desktop_mode", default = false) @Keep @JvmField val WM_CAPTION_ON_SHELL = - sysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", default = true) + sysPropBooleanFlag("persist.wm.debug.caption_on_shell", default = true) @Keep @JvmField val ENABLE_FLING_TO_DISMISS_BUBBLE = - sysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", default = true) + sysPropBooleanFlag("persist.wm.debug.fling_to_dismiss_bubble", default = true) @Keep @JvmField val ENABLE_FLING_TO_DISMISS_PIP = - sysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", default = true) + sysPropBooleanFlag("persist.wm.debug.fling_to_dismiss_pip", default = true) @Keep @JvmField val ENABLE_PIP_KEEP_CLEAR_ALGORITHM = - sysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", default = true) + sysPropBooleanFlag("persist.wm.debug.enable_pip_keep_clear_algorithm", default = true) // TODO(b/256873975): Tracking Bug @JvmField @Keep - val WM_BUBBLE_BAR = sysPropBooleanFlag(1111, "persist.wm.debug.bubble_bar", default = false) + val WM_BUBBLE_BAR = sysPropBooleanFlag("persist.wm.debug.bubble_bar", default = false) // TODO(b/260271148): Tracking bug @Keep @JvmField val WM_DESKTOP_WINDOWING_2 = - sysPropBooleanFlag(1112, "persist.wm.debug.desktop_mode_2", default = false) + sysPropBooleanFlag("persist.wm.debug.desktop_mode_2", default = false) // TODO(b/254513207): Tracking Bug to delete @Keep @JvmField val WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES = unreleasedFlag( - 1113, name = "screen_record_enterprise_policies", namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER, teamfood = false @@ -514,233 +516,238 @@ object Flags { @Keep @JvmField val ENABLE_PIP_SIZE_LARGE_SCREEN = - sysPropBooleanFlag(1114, "persist.wm.debug.enable_pip_size_large_screen", default = true) + sysPropBooleanFlag("persist.wm.debug.enable_pip_size_large_screen", default = true) // TODO(b/265998256): Tracking bug @Keep @JvmField val ENABLE_PIP_APP_ICON_OVERLAY = - sysPropBooleanFlag(1115, "persist.wm.debug.enable_pip_app_icon_overlay", default = true) + sysPropBooleanFlag("persist.wm.debug.enable_pip_app_icon_overlay", default = true) // TODO(b/273443374): Tracking Bug @Keep @JvmField val LOCKSCREEN_LIVE_WALLPAPER = - sysPropBooleanFlag(1117, "persist.wm.debug.lockscreen_live_wallpaper", default = true) + sysPropBooleanFlag("persist.wm.debug.lockscreen_live_wallpaper", default = true) // TODO(b/281648899): Tracking bug @Keep @JvmField val WALLPAPER_MULTI_CROP = - sysPropBooleanFlag(1118, "persist.wm.debug.wallpaper_multi_crop", default = false) + sysPropBooleanFlag("persist.wm.debug.wallpaper_multi_crop", default = false) // TODO(b/290220798): Tracking Bug @Keep @JvmField val ENABLE_PIP2_IMPLEMENTATION = - sysPropBooleanFlag(1119, "persist.wm.debug.enable_pip2_implementation", default = false) + sysPropBooleanFlag("persist.wm.debug.enable_pip2_implementation", default = false) // 1200 - predictive back @Keep @JvmField val WM_ENABLE_PREDICTIVE_BACK = - sysPropBooleanFlag(1200, "persist.wm.debug.predictive_back", default = true) + sysPropBooleanFlag("persist.wm.debug.predictive_back", default = true) @Keep @JvmField val WM_ENABLE_PREDICTIVE_BACK_ANIM = - sysPropBooleanFlag(1201, "persist.wm.debug.predictive_back_anim", default = true) + sysPropBooleanFlag("persist.wm.debug.predictive_back_anim", default = true) @Keep @JvmField val WM_ALWAYS_ENFORCE_PREDICTIVE_BACK = - sysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", default = false) + sysPropBooleanFlag("persist.wm.debug.predictive_back_always_enforce", default = false) // TODO(b/254512728): Tracking Bug - @JvmField val NEW_BACK_AFFORDANCE = releasedFlag(1203, "new_back_affordance") + @JvmField val NEW_BACK_AFFORDANCE = releasedFlag("new_back_affordance") // TODO(b/255854141): Tracking Bug @JvmField val WM_ENABLE_PREDICTIVE_BACK_SYSUI = - unreleasedFlag(1204, "persist.wm.debug.predictive_back_sysui_enable", teamfood = true) + unreleasedFlag("persist.wm.debug.predictive_back_sysui_enable", teamfood = true) // TODO(b/270987164): Tracking Bug - @JvmField val TRACKPAD_GESTURE_FEATURES = releasedFlag(1205, "trackpad_gesture_features") + @JvmField val TRACKPAD_GESTURE_FEATURES = releasedFlag("trackpad_gesture_features") // TODO(b/263826204): Tracking Bug @JvmField val WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM = - unreleasedFlag(1206, "persist.wm.debug.predictive_back_bouncer_anim", teamfood = true) + unreleasedFlag("persist.wm.debug.predictive_back_bouncer_anim", teamfood = true) // TODO(b/238475428): Tracking Bug @JvmField val WM_SHADE_ALLOW_BACK_GESTURE = - sysPropBooleanFlag(1207, "persist.wm.debug.shade_allow_back_gesture", default = false) + sysPropBooleanFlag("persist.wm.debug.shade_allow_back_gesture", default = false) // TODO(b/238475428): Tracking Bug @JvmField val WM_SHADE_ANIMATE_BACK_GESTURE = - unreleasedFlag(1208, "persist.wm.debug.shade_animate_back_gesture", teamfood = false) + unreleasedFlag("persist.wm.debug.shade_animate_back_gesture", teamfood = false) // TODO(b/265639042): Tracking Bug @JvmField val WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM = - unreleasedFlag(1209, "persist.wm.debug.predictive_back_qs_dialog_anim", teamfood = true) + unreleasedFlag("persist.wm.debug.predictive_back_qs_dialog_anim", teamfood = true) // TODO(b/273800936): Tracking Bug - @JvmField val TRACKPAD_GESTURE_COMMON = releasedFlag(1210, "trackpad_gesture_common") + @JvmField val TRACKPAD_GESTURE_COMMON = releasedFlag("trackpad_gesture_common") // 1300 - screenshots // TODO(b/264916608): Tracking Bug - @JvmField val SCREENSHOT_METADATA = unreleasedFlag(1302, "screenshot_metadata") + @JvmField val SCREENSHOT_METADATA = unreleasedFlag("screenshot_metadata") // TODO(b/266955521): Tracking bug - @JvmField val SCREENSHOT_DETECTION = releasedFlag(1303, "screenshot_detection") + @JvmField val SCREENSHOT_DETECTION = releasedFlag("screenshot_detection") // TODO(b/251205791): Tracking Bug - @JvmField val SCREENSHOT_APP_CLIPS = releasedFlag(1304, "screenshot_app_clips") + @JvmField val SCREENSHOT_APP_CLIPS = releasedFlag("screenshot_app_clips") // 1400 - columbus // TODO(b/254512756): Tracking Bug - val QUICK_TAP_IN_PCC = releasedFlag(1400, "quick_tap_in_pcc") + val QUICK_TAP_IN_PCC = releasedFlag("quick_tap_in_pcc") // TODO(b/261979569): Tracking Bug val QUICK_TAP_FLOW_FRAMEWORK = - unreleasedFlag(1401, "quick_tap_flow_framework", teamfood = false) + unreleasedFlag("quick_tap_flow_framework", teamfood = false) // 1500 - chooser aka sharesheet // 1700 - clipboard - @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior") + @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag("clipboard_remote_behavior") // TODO(b/278714186) Tracking Bug @JvmField - val CLIPBOARD_IMAGE_TIMEOUT = unreleasedFlag(1702, "clipboard_image_timeout", teamfood = true) + val CLIPBOARD_IMAGE_TIMEOUT = unreleasedFlag("clipboard_image_timeout", teamfood = true) // TODO(b/279405451): Tracking Bug @JvmField val CLIPBOARD_SHARED_TRANSITIONS = - unreleasedFlag(1703, "clipboard_shared_transitions", teamfood = true) + unreleasedFlag("clipboard_shared_transitions", teamfood = true) // TODO(b/283300105): Tracking Bug - @JvmField val SCENE_CONTAINER = unreleasedFlag(1802, "scene_container") + @JvmField val SCENE_CONTAINER = unreleasedFlag("scene_container") // 1900 - @JvmField val NOTE_TASKS = releasedFlag(1900, "keycode_flag") + @JvmField val NOTE_TASKS = releasedFlag("keycode_flag") // 2000 - device controls - @JvmField val APP_PANELS_ALL_APPS_ALLOWED = releasedFlag(2001, "app_panels_all_apps_allowed") + @JvmField val APP_PANELS_ALL_APPS_ALLOWED = releasedFlag("app_panels_all_apps_allowed") // 2200 - biometrics (udfps, sfps, BiometricPrompt, etc.) // TODO(b/259264861): Tracking Bug - @JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag(2200, "udfps_new_touch_detection") - @JvmField val UDFPS_ELLIPSE_DETECTION = releasedFlag(2201, "udfps_ellipse_detection") + @JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag("udfps_new_touch_detection") + @JvmField val UDFPS_ELLIPSE_DETECTION = releasedFlag("udfps_ellipse_detection") // TODO(b/278622168): Tracking Bug - @JvmField val BIOMETRIC_BP_STRONG = releasedFlag(2202, "biometric_bp_strong") + @JvmField val BIOMETRIC_BP_STRONG = releasedFlag("biometric_bp_strong") // 2300 - stylus - @JvmField val TRACK_STYLUS_EVER_USED = releasedFlag(2300, "track_stylus_ever_used") - @JvmField val ENABLE_STYLUS_CHARGING_UI = releasedFlag(2301, "enable_stylus_charging_ui") + @JvmField val TRACK_STYLUS_EVER_USED = releasedFlag("track_stylus_ever_used") + @JvmField val ENABLE_STYLUS_CHARGING_UI = releasedFlag("enable_stylus_charging_ui") @JvmField - val ENABLE_USI_BATTERY_NOTIFICATIONS = releasedFlag(2302, "enable_usi_battery_notifications") - @JvmField val ENABLE_STYLUS_EDUCATION = releasedFlag(2303, "enable_stylus_education") + val ENABLE_USI_BATTERY_NOTIFICATIONS = releasedFlag("enable_usi_battery_notifications") + @JvmField val ENABLE_STYLUS_EDUCATION = releasedFlag("enable_stylus_education") // 2400 - performance tools and debugging info // TODO(b/238923086): Tracking Bug @JvmField val WARN_ON_BLOCKING_BINDER_TRANSACTIONS = - unreleasedFlag(2400, "warn_on_blocking_binder_transactions") + unreleasedFlag("warn_on_blocking_binder_transactions") // TODO(b/283071711): Tracking bug @JvmField val TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK = - unreleasedFlag(2401, "trim_resources_with_background_trim_on_lock") + unreleasedFlag("trim_resources_with_background_trim_on_lock") // TODO:(b/283203305): Tracking bug - @JvmField val TRIM_FONT_CACHES_AT_UNLOCK = unreleasedFlag(2402, "trim_font_caches_on_unlock") + @JvmField val TRIM_FONT_CACHES_AT_UNLOCK = unreleasedFlag("trim_font_caches_on_unlock") // 2700 - unfold transitions // TODO(b/265764985): Tracking Bug @Keep @JvmField val ENABLE_DARK_VIGNETTE_WHEN_FOLDING = - unreleasedFlag(2700, "enable_dark_vignette_when_folding") + unreleasedFlag("enable_dark_vignette_when_folding") // TODO(b/265764985): Tracking Bug @Keep @JvmField val ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS = - unreleasedFlag(2701, "enable_unfold_status_bar_animations") + unreleasedFlag("enable_unfold_status_bar_animations") // TODO(b259590361): Tracking bug - val EXPERIMENTAL_FLAG = unreleasedFlag(2, "exp_flag_release") + val EXPERIMENTAL_FLAG = unreleasedFlag("exp_flag_release") // 2600 - keyboard // TODO(b/259352579): Tracking Bug - @JvmField val SHORTCUT_LIST_SEARCH_LAYOUT = releasedFlag(2600, "shortcut_list_search_layout") + @JvmField val SHORTCUT_LIST_SEARCH_LAYOUT = releasedFlag("shortcut_list_search_layout") // TODO(b/259428678): Tracking Bug - @JvmField val KEYBOARD_BACKLIGHT_INDICATOR = releasedFlag(2601, "keyboard_backlight_indicator") + @JvmField val KEYBOARD_BACKLIGHT_INDICATOR = releasedFlag("keyboard_backlight_indicator") // TODO(b/277192623): Tracking Bug - @JvmField val KEYBOARD_EDUCATION = unreleasedFlag(2603, "keyboard_education", teamfood = false) + @JvmField val KEYBOARD_EDUCATION = unreleasedFlag("keyboard_education", teamfood = false) // TODO(b/277201412): Tracking Bug @JvmField - val SPLIT_SHADE_SUBPIXEL_OPTIMIZATION = releasedFlag(2805, "split_shade_subpixel_optimization") + val SPLIT_SHADE_SUBPIXEL_OPTIMIZATION = releasedFlag("split_shade_subpixel_optimization") // TODO(b/288868056): Tracking Bug @JvmField - val PARTIAL_SCREEN_SHARING_TASK_SWITCHER = unreleasedFlag(288868056, "pss_task_switcher") + val PARTIAL_SCREEN_SHARING_TASK_SWITCHER = unreleasedFlag("pss_task_switcher") // TODO(b/278761837): Tracking Bug - @JvmField val USE_NEW_ACTIVITY_STARTER = releasedFlag(2801, name = "use_new_activity_starter") + @JvmField val USE_NEW_ACTIVITY_STARTER = releasedFlag(name = "use_new_activity_starter") // 2900 - Zero Jank fixes. Naming convention is: zj_<bug number>_<cuj name> // TODO:(b/285623104): Tracking bug @JvmField val ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD = - releasedFlag(2900, "zj_285570694_lockscreen_transition_from_aod") + releasedFlag("zj_285570694_lockscreen_transition_from_aod") // 3000 - dream // TODO(b/285059790) : Tracking Bug @JvmField val LOCKSCREEN_WALLPAPER_DREAM_ENABLED = - unreleasedFlag(3000, name = "enable_lockscreen_wallpaper_dream", teamfood = true) + unreleasedFlag(name = "enable_lockscreen_wallpaper_dream", teamfood = true) // TODO(b/283084712): Tracking Bug - @JvmField val IMPROVED_HUN_ANIMATIONS = unreleasedFlag(283084712, "improved_hun_animations") + @JvmField val IMPROVED_HUN_ANIMATIONS = unreleasedFlag("improved_hun_animations") // TODO(b/283447257): Tracking bug @JvmField val BIGPICTURE_NOTIFICATION_LAZY_LOADING = - unreleasedFlag(283447257, "bigpicture_notification_lazy_loading") + unreleasedFlag("bigpicture_notification_lazy_loading") + + // TODO(b/292062937): Tracking bug + @JvmField + val NOTIFICATION_CLEARABLE_REFACTOR = + unreleasedFlag("notification_clearable_refactor") // TODO(b/283740863): Tracking Bug @JvmField val ENABLE_NEW_PRIVACY_DIALOG = - unreleasedFlag(283740863, "enable_new_privacy_dialog", teamfood = true) + unreleasedFlag("enable_new_privacy_dialog", teamfood = true) // TODO(b/289573946): Tracking Bug - @JvmField val PRECOMPUTED_TEXT = unreleasedFlag(289573946, "precomputed_text") + @JvmField val PRECOMPUTED_TEXT = unreleasedFlag("precomputed_text") // 2900 - CentralSurfaces-related flags // TODO(b/285174336): Tracking Bug @JvmField val USE_REPOS_FOR_BOUNCER_SHOWING = - unreleasedFlag(2900, "use_repos_for_bouncer_showing", teamfood = true) + unreleasedFlag("use_repos_for_bouncer_showing", teamfood = true) // 3100 - Haptic interactions // TODO(b/290213663): Tracking Bug @JvmField - val ONE_WAY_HAPTICS_API_MIGRATION = unreleasedFlag(3100, "oneway_haptics_api_migration") + val ONE_WAY_HAPTICS_API_MIGRATION = unreleasedFlag("oneway_haptics_api_migration") /** Enable the Compose implementation of the PeopleSpaceActivity. */ @JvmField - val COMPOSE_PEOPLE_SPACE = unreleasedFlag(293570761, "compose_people_space") + val COMPOSE_PEOPLE_SPACE = unreleasedFlag("compose_people_space") /** Enable the Compose implementation of the Quick Settings footer actions. */ @JvmField - val COMPOSE_QS_FOOTER_ACTIONS = unreleasedFlag(293569320, "compose_qs_footer_actions") + val COMPOSE_QS_FOOTER_ACTIONS = unreleasedFlag("compose_qs_footer_actions") } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index 489d2ab4d342..9a09df4828d3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -929,28 +929,41 @@ class KeyguardUnlockAnimationController @Inject constructor( /** * Called by [KeyguardViewMediator] to let us know that the remote animation has finished, and - * we should clean up all of our state. + * we should clean up all of our state. [showKeyguard] will tell us which surface should be + * visible after the animation has been completed or canceled. * * This is generally triggered by us, calling * [KeyguardViewMediator.finishSurfaceBehindRemoteAnimation]. */ - fun notifyFinishedKeyguardExitAnimation(cancelled: Boolean) { + fun notifyFinishedKeyguardExitAnimation(showKeyguard: Boolean) { // Cancel any pending actions. handler.removeCallbacksAndMessages(null) - // Make sure we made the surface behind fully visible, just in case. It should already be - // fully visible. The exit animation is finished, and we should not hold the leash anymore, - // so forcing it to 1f. - surfaceBehindAlpha = 1f - setSurfaceBehindAppearAmount(1f) + // The lockscreen surface is gone, so it is now safe to re-show the smartspace. + if (lockscreenSmartspace?.visibility == View.INVISIBLE) { + lockscreenSmartspace?.visibility = View.VISIBLE + } + + if (!showKeyguard) { + // Make sure we made the surface behind fully visible, just in case. It should already be + // fully visible. The exit animation is finished, and we should not hold the leash anymore, + // so forcing it to 1f. + surfaceBehindAlpha = 1f + setSurfaceBehindAppearAmount(1f) + + try { + launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */) + } catch (e: RemoteException) { + Log.e(TAG, "Remote exception in notifyFinishedKeyguardExitAnimation", e) + } + } + + listeners.forEach { it.onUnlockAnimationFinished() } + + // Reset all state surfaceBehindAlphaAnimator.cancel() surfaceBehindEntryAnimator.cancel() wallpaperCannedUnlockAnimator.cancel() - try { - launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */) - } catch (e: RemoteException) { - Log.e(TAG, "Remote exception in notifyFinishedKeyguardExitAnimation", e) - } // That target is no longer valid since the animation finished, null it out. surfaceBehindRemoteAnimationTargets = null @@ -960,13 +973,6 @@ class KeyguardUnlockAnimationController @Inject constructor( dismissAmountThresholdsReached = false willUnlockWithInWindowLauncherAnimations = false willUnlockWithSmartspaceTransition = false - - // The lockscreen surface is gone, so it is now safe to re-show the smartspace. - if (lockscreenSmartspace?.visibility == View.INVISIBLE) { - lockscreenSmartspace?.visibility = View.VISIBLE - } - - listeners.forEach { it.onUnlockAnimationFinished() } } /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index 6213265cb66e..2b6f77d280f8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -20,6 +20,8 @@ package com.android.systemui.keyguard import android.content.res.Configuration import android.view.View import android.view.ViewGroup +import com.android.keyguard.KeyguardStatusViewController +import com.android.keyguard.dagger.KeyguardStatusViewComponent import com.android.systemui.CoreStartable import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton @@ -80,6 +82,7 @@ constructor( private val chipbarCoordinator: ChipbarCoordinator, private val keyguardBlueprintCommandListener: KeyguardBlueprintCommandListener, private val keyguardBlueprintViewModel: KeyguardBlueprintViewModel, + private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory, ) : CoreStartable { private var rootViewHandle: DisposableHandle? = null @@ -88,6 +91,7 @@ constructor( private var rightShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null private var ambientIndicationAreaHandle: KeyguardAmbientIndicationAreaViewBinder.Binding? = null private var settingsPopupMenuHandle: DisposableHandle? = null + private var keyguardStatusViewController: KeyguardStatusViewController? = null override fun start() { bindKeyguardRootView() @@ -96,6 +100,7 @@ constructor( unbindKeyguardBottomArea(notificationPanel) bindIndicationArea() bindLockIconView(notificationPanel) + bindKeyguardStatusView(notificationPanel) setupNotificationStackScrollLayout(notificationPanel) bindLeftShortcut() bindRightShortcut() @@ -117,7 +122,7 @@ constructor( sharedNotificationContainer.addNotificationStackScrollLayout(nssl) SharedNotificationContainerBinder.bind( sharedNotificationContainer, - sharedNotificationContainerViewModel + sharedNotificationContainerViewModel, ) } } @@ -132,14 +137,6 @@ constructor( fun bindIndicationArea() { indicationAreaHandle?.dispose() - // At startup, 2 views with the ID `R.id.keyguard_indication_area` will be available. - // Disable one of them - if (!featureFlags.isEnabled(Flags.MIGRATE_INDICATION_AREA)) { - keyguardRootView.findViewById<View?>(R.id.keyguard_indication_area)?.let { - keyguardRootView.removeView(it) - } - } - indicationAreaHandle = KeyguardIndicationAreaBinder.bind( notificationShadeWindowView, @@ -255,4 +252,31 @@ constructor( } } } + + fun bindKeyguardStatusView(legacyParent: ViewGroup) { + // At startup, 2 views with the ID `R.id.keyguard_status_view` will be available. + // Disable one of them + if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + legacyParent.findViewById<View>(R.id.keyguard_status_view)?.let { + legacyParent.removeView(it) + } + + val keyguardStatusView = keyguardRootView.addStatusView() + val statusViewComponent = keyguardStatusViewComponentFactory.build(keyguardStatusView) + val controller = statusViewComponent.getKeyguardStatusViewController() + controller.init() + keyguardStatusViewController = controller + } else { + keyguardRootView.findViewById<View?>(R.id.keyguard_status_view)?.let { + keyguardRootView.removeView(it) + } + } + } + + /** + * Temporary, to allow NotificationPanelViewController to use the same instance while code is + * migrated: b/288242803 + */ + fun getKeyguardStatusViewController() = keyguardStatusViewController + fun getKeyguardRootView() = keyguardRootView } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 66de371ab6e0..f861d5e94540 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -2561,7 +2561,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } else if (mSurfaceBehindRemoteAnimationRunning) { // We're already running the keyguard exit animation, likely due to an in-progress swipe // to unlock. - exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */); + exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* showKeyguard */); } else if (!mHideAnimationRun) { if (DEBUG) Log.d(TAG, "tryKeyguardDone: starting pre-hide animation"); mHideAnimationRun = true; @@ -3003,7 +3003,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mContext.getMainExecutor().execute(() -> { if (finishedCallback == null) { mKeyguardUnlockAnimationControllerLazy.get() - .notifyFinishedKeyguardExitAnimation(false /* cancelled */); + .notifyFinishedKeyguardExitAnimation(false /* showKeyguard */); mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION); return; } @@ -3120,7 +3120,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // A lock is pending, meaning the keyguard exit animation was cancelled because we're // re-locking. We should just end the surface-behind animation without exiting the // keyguard. The pending lock will be handled by onFinishedGoingToSleep(). - finishSurfaceBehindRemoteAnimation(true); + finishSurfaceBehindRemoteAnimation(true /* showKeyguard */); maybeHandlePendingLock(); } else { Log.d(TAG, "#handleCancelKeyguardExitAnimation: keyguard exit animation cancelled. " @@ -3129,7 +3129,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // No lock is pending, so the animation was cancelled during the unlock sequence, but // we should end up unlocked. Show the surface and exit the keyguard. showSurfaceBehindKeyguard(); - exitKeyguardAndFinishSurfaceBehindRemoteAnimation(true /* cancelled */); + exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* showKeyguard */); } } @@ -3140,12 +3140,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, * with the RemoteAnimation, actually hide the keyguard, and clean up state related to the * keyguard exit animation. * - * @param cancelled {@code true} if the animation was cancelled before it finishes. + * @param showKeyguard {@code true} if the animation was cancelled and keyguard should remain + * visible */ - public void exitKeyguardAndFinishSurfaceBehindRemoteAnimation(boolean cancelled) { + public void exitKeyguardAndFinishSurfaceBehindRemoteAnimation(boolean showKeyguard) { Log.d(TAG, "onKeyguardExitRemoteAnimationFinished"); if (!mSurfaceBehindRemoteAnimationRunning && !mSurfaceBehindRemoteAnimationRequested) { - Log.d(TAG, "skip onKeyguardExitRemoteAnimationFinished cancelled=" + cancelled + Log.d(TAG, "skip onKeyguardExitRemoteAnimationFinished showKeyguard=" + showKeyguard + " surfaceAnimationRunning=" + mSurfaceBehindRemoteAnimationRunning + " surfaceAnimationRequested=" + mSurfaceBehindRemoteAnimationRequested); return; @@ -3170,9 +3171,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, + " wasShowing=" + wasShowing); } - mKeyguardUnlockAnimationControllerLazy.get() - .notifyFinishedKeyguardExitAnimation(cancelled); - finishSurfaceBehindRemoteAnimation(cancelled); + finishSurfaceBehindRemoteAnimation(showKeyguard); // Dispatch the callback on animation finishes. mUpdateMonitor.dispatchKeyguardDismissAnimationFinished(); @@ -3236,7 +3235,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, * This does not set keyguard state to either locked or unlocked, it simply ends the remote * animation on the surface behind the keyguard. This can be called by */ - void finishSurfaceBehindRemoteAnimation(boolean cancelled) { + void finishSurfaceBehindRemoteAnimation(boolean showKeyguard) { + mKeyguardUnlockAnimationControllerLazy.get() + .notifyFinishedKeyguardExitAnimation(showKeyguard); + mSurfaceBehindRemoteAnimationRequested = false; mSurfaceBehindRemoteAnimationRunning = false; mKeyguardStateController.notifyKeyguardGoingAway(false); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt index 20ed549e42d4..45277b833ea3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt @@ -78,16 +78,8 @@ constructor( override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState { return when { - !controller.isAvailableOnDevice -> + !isEnabledForPickerStateOption() -> KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice - !controller.isAbleToOpenCameraApp -> { - KeyguardQuickAffordanceConfig.PickerScreenState.Disabled( - explanation = - context.getString( - R.string.qr_scanner_quick_affordance_unavailable_explanation - ), - ) - } else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default() } } @@ -118,6 +110,11 @@ constructor( } } + /** Returns whether QR scanner be shown as one of available lockscreen shortcut option. */ + private fun isEnabledForPickerStateOption(): Boolean { + return controller.isAbleToLaunchScannerActivity && controller.isAllowedOnLockScreen + } + companion object { private const val TAG = "QrCodeScannerKeyguardQuickAffordanceConfig" } 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 f1b344199516..e35c3699aabf 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 @@ -199,13 +199,16 @@ interface KeyguardRepository { fun setAnimateDozingTransitions(animate: Boolean) /** Sets the current amount of alpha that should be used for rendering the bottom area. */ - @Deprecated("Deprecated as part of b/278057014") - fun setBottomAreaAlpha(alpha: Float) + @Deprecated("Deprecated as part of b/278057014") fun setBottomAreaAlpha(alpha: Float) /** Sets the current amount of alpha that should be used for rendering the keyguard. */ fun setKeyguardAlpha(alpha: Float) - fun setKeyguardVisibility(statusBarState: Int, goingToFullShade: Boolean, occlusionTransitionRunning: Boolean) + fun setKeyguardVisibility( + statusBarState: Int, + goingToFullShade: Boolean, + occlusionTransitionRunning: Boolean + ) /** * Sets the relative offset of the lock-screen clock from its natural position on the screen. diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt deleted file mode 100644 index 278c68d3c55b..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2023 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.authentication.domain.interactor.AuthenticationInteractor -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel -import com.android.systemui.bouncer.domain.interactor.BouncerInteractor -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application -import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn - -/** Hosts business and application state accessing logic for the lockscreen scene. */ -@SysUISingleton -class LockscreenSceneInteractor -@Inject -constructor( - @Application applicationScope: CoroutineScope, - private val authenticationInteractor: AuthenticationInteractor, - private val bouncerInteractor: BouncerInteractor, -) { - /** Whether the device is currently locked. */ - val isDeviceLocked: StateFlow<Boolean> = - authenticationInteractor.isUnlocked - .map { !it } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = !authenticationInteractor.isUnlocked.value, - ) - - /** Whether it's currently possible to swipe up to dismiss the lockscreen. */ - val isSwipeToDismissEnabled: StateFlow<Boolean> = - authenticationInteractor.isUnlocked - .map { isUnlocked -> - !isUnlocked && - authenticationInteractor.getAuthenticationMethod() is - AuthenticationMethodModel.Swipe - } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = false, - ) - - /** Attempts to dismiss the lockscreen. This will cause the bouncer to show, if needed. */ - fun dismissLockscreen() { - bouncerInteractor.showOrUnlockDevice() - } -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt index e60901f922c3..a94874176a34 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt @@ -24,6 +24,7 @@ import android.view.View import android.widget.ImageView import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.res.ResourcesCompat +import com.android.keyguard.KeyguardStatusView import com.android.keyguard.LockIconView import com.android.systemui.R import com.android.systemui.animation.view.LaunchableImageView @@ -38,6 +39,8 @@ class KeyguardRootView( attrs, ) { + private var statusView: KeyguardStatusView? = null + init { addIndicationTextArea() addLockIconView() @@ -45,6 +48,7 @@ class KeyguardRootView( addLeftShortcut() addRightShortcut() addSettingsPopupMenu() + addStatusView() } private fun addIndicationTextArea() { @@ -119,4 +123,19 @@ class KeyguardRootView( } addView(view) } + + fun addStatusView(): KeyguardStatusView { + // StatusView may need to be rebuilt on config changes. Remove and reinflate + statusView?.let { removeView(it) } + val view = + (LayoutInflater.from(context).inflate(R.layout.keyguard_status_view, this, false) + as KeyguardStatusView) + .apply { + setClipChildren(false) + statusView = this + } + + addView(view) + return view + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt index 5538fe7e7006..518df0719aaa 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt @@ -25,6 +25,8 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAr import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection +import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines import javax.inject.Inject /** @@ -42,6 +44,8 @@ constructor( private val defaultShortcutsSection: DefaultShortcutsSection, private val defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection, private val defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection, + private val defaultStatusViewSection: DefaultStatusViewSection, + private val splitShadeGuidelines: SplitShadeGuidelines, ) : KeyguardBlueprint { override val id: String = DEFAULT @@ -51,6 +55,8 @@ constructor( defaultShortcutsSection.apply(constraintSet) defaultAmbientIndicationAreaSection.apply(constraintSet) defaultSettingsPopupMenuSection.apply(constraintSet) + defaultStatusViewSection.apply(constraintSet) + splitShadeGuidelines.apply(constraintSet) } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt index 19410e4ba89d..54c27960db3c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt @@ -27,6 +27,8 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAr import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection +import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines import javax.inject.Inject /** Vertically aligns the shortcuts with the udfps. */ @@ -41,6 +43,8 @@ constructor( private val defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection, private val alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection, private val defaultShortcutsSection: DefaultShortcutsSection, + private val defaultStatusViewSection: DefaultStatusViewSection, + private val splitShadeGuidelines: SplitShadeGuidelines, ) : KeyguardBlueprint { override val id: String = SHORTCUTS_BESIDE_UDFPS @@ -54,6 +58,8 @@ constructor( } else { defaultShortcutsSection.apply(constraintSet) } + defaultStatusViewSection.apply(constraintSet) + splitShadeGuidelines.apply(constraintSet) } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt new file mode 100644 index 000000000000..3f319ba2d0e4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 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.ui.view.layout.sections + +import android.content.Context +import android.view.ViewGroup +import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.R +import com.android.systemui.keyguard.data.repository.KeyguardSection +import javax.inject.Inject +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT +import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID +import androidx.constraintlayout.widget.ConstraintSet.START +import androidx.constraintlayout.widget.ConstraintSet.TOP +import androidx.constraintlayout.widget.ConstraintSet.END + +class DefaultStatusViewSection @Inject constructor(private val context: Context) : + KeyguardSection { + private val statusViewId = R.id.keyguard_status_view + + override fun apply(constraintSet: ConstraintSet) { + constraintSet.apply { + constrainWidth(statusViewId, MATCH_CONSTRAINT) + constrainHeight(statusViewId, WRAP_CONTENT) + connect(statusViewId, TOP, PARENT_ID, TOP) + connect(statusViewId, START, PARENT_ID, START) + connect(statusViewId, END, PARENT_ID, END) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeGuidelines.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeGuidelines.kt new file mode 100644 index 000000000000..668b17ffeba0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeGuidelines.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 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.ui.view.layout.sections + +import android.content.Context +import android.view.ViewGroup +import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.R +import com.android.systemui.keyguard.data.repository.KeyguardSection +import javax.inject.Inject +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT +import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID +import androidx.constraintlayout.widget.ConstraintSet.START +import androidx.constraintlayout.widget.ConstraintSet.TOP +import androidx.constraintlayout.widget.ConstraintSet.END +import androidx.constraintlayout.widget.ConstraintSet.VERTICAL + +class SplitShadeGuidelines @Inject constructor(private val context: Context) : + KeyguardSection { + + override fun apply(constraintSet: ConstraintSet) { + constraintSet.apply { + // For use on large screens, it will provide a guideline vertically in the center to + // enable items to be aligned on the left or right sides + create(R.id.split_shade_guideline, VERTICAL) + setGuidelinePercent(R.id.split_shade_guideline, 0.5f) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt index abd178ca6c1d..f46d0eb449a0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt @@ -17,14 +17,17 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.R +import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor +import com.android.systemui.authentication.domain.model.AuthenticationMethodModel +import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor import com.android.systemui.scene.shared.model.SceneKey import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map @@ -36,36 +39,37 @@ class LockscreenSceneViewModel @Inject constructor( @Application applicationScope: CoroutineScope, - private val interactor: LockscreenSceneInteractor, + authenticationInteractor: AuthenticationInteractor, + private val bouncerInteractor: BouncerInteractor, ) { /** The icon for the "lock" button on the lockscreen. */ val lockButtonIcon: StateFlow<Icon> = - interactor.isDeviceLocked - .map { isLocked -> lockIcon(isLocked = isLocked) } + authenticationInteractor.isUnlocked + .map { isUnlocked -> lockIcon(isUnlocked = isUnlocked) } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = lockIcon(isLocked = interactor.isDeviceLocked.value), + initialValue = lockIcon(isUnlocked = authenticationInteractor.isUnlocked.value), ) /** The key of the scene we should switch to when swiping up. */ - val upDestinationSceneKey: StateFlow<SceneKey> = - interactor.isSwipeToDismissEnabled - .map { isSwipeToUnlockEnabled -> upDestinationSceneKey(isSwipeToUnlockEnabled) } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = upDestinationSceneKey(interactor.isSwipeToDismissEnabled.value), - ) + val upDestinationSceneKey: Flow<SceneKey> = + authenticationInteractor.authenticationMethod.map { authenticationMethod -> + if (authenticationMethod is AuthenticationMethodModel.Swipe) { + SceneKey.Gone + } else { + SceneKey.Bouncer + } + } /** Notifies that the lock button on the lock screen was clicked. */ fun onLockButtonClicked() { - interactor.dismissLockscreen() + bouncerInteractor.showOrUnlockDevice() } /** Notifies that some content on the lock screen was clicked. */ fun onContentClicked() { - interactor.dismissLockscreen() + bouncerInteractor.showOrUnlockDevice() } private fun upDestinationSceneKey( @@ -75,22 +79,22 @@ constructor( } private fun lockIcon( - isLocked: Boolean, + isUnlocked: Boolean, ): Icon { return Icon.Resource( res = - if (isLocked) { - R.drawable.ic_device_lock_on - } else { + if (isUnlocked) { R.drawable.ic_device_lock_off + } else { + R.drawable.ic_device_lock_on }, contentDescription = ContentDescription.Resource( res = - if (isLocked) { - R.string.accessibility_lock_icon - } else { + if (isUnlocked) { R.string.accessibility_unlock_button + } else { + R.string.accessibility_lock_icon } ) ) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt index fdb3ddd7294f..8f884d24ad21 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt @@ -30,11 +30,16 @@ import android.os.IBinder import android.os.ResultReceiver import android.os.UserHandle import android.view.ViewGroup -import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyStateProvider -import com.android.intentresolver.AbstractMultiProfilePagerAdapter.MyUserIdProvider -import com.android.intentresolver.ChooserActivity -import com.android.intentresolver.chooser.TargetInfo +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleRegistry import com.android.internal.annotations.VisibleForTesting +import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider +import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider +import com.android.internal.app.ChooserActivity +import com.android.internal.app.ResolverListController +import com.android.internal.app.chooser.NotSelectableTargetInfo +import com.android.internal.app.chooser.TargetInfo import com.android.systemui.R import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorComponent import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorController @@ -51,8 +56,12 @@ class MediaProjectionAppSelectorActivity( private val activityLauncher: AsyncActivityLauncher, /** This is used to override the dependency in a screenshot test */ @VisibleForTesting - private val listControllerFactory: ((userHandle: UserHandle) -> ChooserListController)? -) : ChooserActivity(), MediaProjectionAppSelectorView, MediaProjectionAppSelectorResultHandler { + private val listControllerFactory: ((userHandle: UserHandle) -> ResolverListController)? +) : + ChooserActivity(), + MediaProjectionAppSelectorView, + MediaProjectionAppSelectorResultHandler, + LifecycleOwner { @Inject constructor( @@ -60,6 +69,8 @@ class MediaProjectionAppSelectorActivity( activityLauncher: AsyncActivityLauncher ) : this(componentFactory, activityLauncher, listControllerFactory = null) + private val lifecycleRegistry = LifecycleRegistry(this) + override val lifecycle = lifecycleRegistry private lateinit var configurationController: ConfigurationController private lateinit var controller: MediaProjectionAppSelectorController private lateinit var recentsViewController: MediaProjectionRecentsViewController @@ -73,7 +84,8 @@ class MediaProjectionAppSelectorActivity( override fun getLayoutResource() = R.layout.media_projection_app_selector public override fun onCreate(bundle: Bundle?) { - component = componentFactory.create(view = this, resultHandler = this) + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) + component = componentFactory.create(activity = this, view = this, resultHandler = this) component.lifecycleObservers.forEach { lifecycle.addObserver(it) } // Create a separate configuration controller for this activity as the configuration @@ -95,6 +107,26 @@ class MediaProjectionAppSelectorActivity( controller.init() } + override fun onStart() { + super.onStart() + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START) + } + + override fun onResume() { + super.onResume() + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) + } + + override fun onPause() { + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE) + super.onPause() + } + + override fun onStop() { + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP) + super.onStop() + } + override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) configurationController.onConfigurationChanged(newConfig) @@ -105,13 +137,13 @@ class MediaProjectionAppSelectorActivity( override fun createBlockerEmptyStateProvider(): EmptyStateProvider = component.emptyStateProvider - override fun createListController(userHandle: UserHandle): ChooserListController = + override fun createListController(userHandle: UserHandle): ResolverListController = listControllerFactory?.invoke(userHandle) ?: super.createListController(userHandle) override fun startSelected(which: Int, always: Boolean, filtered: Boolean) { val currentListAdapter = mChooserMultiProfilePagerAdapter.activeListAdapter val targetInfo = currentListAdapter.targetInfoForPosition(which, filtered) ?: return - if (targetInfo.isNotSelectableTargetInfo) return + if (targetInfo is NotSelectableTargetInfo) return val intent = createIntent(targetInfo) @@ -151,6 +183,7 @@ class MediaProjectionAppSelectorActivity( } override fun onDestroy() { + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) component.lifecycleObservers.forEach { lifecycle.removeObserver(it) } // onDestroy is also called when an app is selected, in that case we only want to send // RECORD_CONTENT_TASK but not RECORD_CANCEL diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java index 7712690de195..3a1d8b0e238e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java @@ -296,7 +296,10 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements @Override public void stop() { - if (isBroadcastSupported() && mIsLeBroadcastCallbackRegistered) { + // unregister broadcast callback should only depend on profile and registered flag + // rather than remote device or broadcast state + // otherwise it might have risks of leaking registered callback handle + if (mMediaOutputController.isBroadcastSupported() && mIsLeBroadcastCallbackRegistered) { mMediaOutputController.unregisterLeBroadcastServiceCallback(mBroadcastCallback); mIsLeBroadcastCallbackRegistered = false; } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java index f3865f52e863..e5a6bb523916 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java @@ -104,11 +104,16 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { @Override public boolean isBroadcastSupported() { boolean isBluetoothLeDevice = false; + boolean isBroadcastEnabled = false; if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST)) { if (mMediaOutputController.getCurrentConnectedMediaDevice() != null) { isBluetoothLeDevice = mMediaOutputController.isBluetoothLeDevice( mMediaOutputController.getCurrentConnectedMediaDevice()); + // if broadcast is active, broadcast should be considered as supported + // there could be a valid case that broadcast is ongoing + // without active LEA device connected + isBroadcastEnabled = mMediaOutputController.isBluetoothLeBroadcastEnabled(); } } else { // To decouple LE Audio Broadcast and Unicast, it always displays the button when there @@ -116,7 +121,8 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { isBluetoothLeDevice = true; } - return mMediaOutputController.isBroadcastSupported() && isBluetoothLeDevice; + return mMediaOutputController.isBroadcastSupported() + && (isBluetoothLeDevice || isBroadcastEnabled); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt index e0869ac6498e..11538fadf24e 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt @@ -161,6 +161,7 @@ interface MediaProjectionAppSelectorComponent { interface Factory { /** Create a factory to inject the activity into the graph */ fun create( + @BindsInstance activity: MediaProjectionAppSelectorActivity, @BindsInstance view: MediaProjectionAppSelectorView, @BindsInstance resultHandler: MediaProjectionAppSelectorResultHandler, ): MediaProjectionAppSelectorComponent diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt index fd14e2b9a96b..829b0ddbe3a8 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt @@ -17,10 +17,10 @@ package com.android.systemui.mediaprojection.appselector import android.content.Context import android.os.UserHandle -import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyState -import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyStateProvider -import com.android.intentresolver.ResolverListAdapter import com.android.internal.R as AndroidR +import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState +import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider +import com.android.internal.app.ResolverListAdapter import com.android.systemui.R import com.android.systemui.mediaprojection.devicepolicy.PersonalProfile import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java deleted file mode 100644 index d1d3e3de39f0..000000000000 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.people; - -import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID; -import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID; - -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; -import android.view.ViewGroup; - -import androidx.activity.ComponentActivity; -import androidx.lifecycle.ViewModelProvider; - -import com.android.systemui.compose.ComposeFacade; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; -import com.android.systemui.people.ui.view.PeopleViewBinder; -import com.android.systemui.people.ui.viewmodel.PeopleViewModel; - -import javax.inject.Inject; - -import kotlin.Unit; -import kotlin.jvm.functions.Function1; - -/** People Tile Widget configuration activity that shows the user their conversation tiles. */ -public class PeopleSpaceActivity extends ComponentActivity { - - private static final String TAG = "PeopleSpaceActivity"; - private static final boolean DEBUG = PeopleSpaceUtils.DEBUG; - - private final PeopleViewModel.Factory mViewModelFactory; - private final FeatureFlags mFeatureFlags; - - @Inject - public PeopleSpaceActivity(PeopleViewModel.Factory viewModelFactory, - FeatureFlags featureFlags) { - super(); - mViewModelFactory = viewModelFactory; - mFeatureFlags = featureFlags; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setResult(RESULT_CANCELED); - - PeopleViewModel viewModel = new ViewModelProvider(this, mViewModelFactory).get( - PeopleViewModel.class); - - // Update the widget ID coming from the intent. - int widgetId = getIntent().getIntExtra(EXTRA_APPWIDGET_ID, INVALID_APPWIDGET_ID); - viewModel.onWidgetIdChanged(widgetId); - - Function1<PeopleViewModel.Result, Unit> onResult = (result) -> { - finishActivity(result); - return null; - }; - - if (mFeatureFlags.isEnabled(Flags.COMPOSE_PEOPLE_SPACE) - && ComposeFacade.INSTANCE.isComposeAvailable()) { - Log.d(TAG, "Using the Compose implementation of the PeopleSpaceActivity"); - ComposeFacade.INSTANCE.setPeopleSpaceActivityContent(this, viewModel, onResult); - } else { - Log.d(TAG, "Using the View implementation of the PeopleSpaceActivity"); - ViewGroup view = PeopleViewBinder.create(this); - PeopleViewBinder.bind(view, viewModel, /* lifecycleOwner= */ this, onResult); - setContentView(view); - } - } - - private void finishActivity(PeopleViewModel.Result result) { - if (result instanceof PeopleViewModel.Result.Success) { - if (DEBUG) Log.d(TAG, "Widget added!"); - Intent data = ((PeopleViewModel.Result.Success) result).getData(); - setResult(RESULT_OK, data); - } else { - if (DEBUG) Log.d(TAG, "Activity dismissed with no widgets added!"); - setResult(RESULT_CANCELED); - } - finish(); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt new file mode 100644 index 000000000000..5b7eb454597c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2023 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.people + +import android.appwidget.AppWidgetManager +import android.os.Bundle +import android.util.Log +import androidx.activity.ComponentActivity +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.compose.ComposeFacade.isComposeAvailable +import com.android.systemui.compose.ComposeFacade.setPeopleSpaceActivityContent +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.people.ui.view.PeopleViewBinder +import com.android.systemui.people.ui.view.PeopleViewBinder.bind +import com.android.systemui.people.ui.viewmodel.PeopleViewModel +import javax.inject.Inject +import kotlinx.coroutines.launch + +/** People Tile Widget configuration activity that shows the user their conversation tiles. */ +class PeopleSpaceActivity +@Inject +constructor( + private val viewModelFactory: PeopleViewModel.Factory, + private val featureFlags: FeatureFlags, +) : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setResult(RESULT_CANCELED) + + // Update the widget ID coming from the intent. + val viewModel = ViewModelProvider(this, viewModelFactory)[PeopleViewModel::class.java] + val widgetId = + intent.getIntExtra( + AppWidgetManager.EXTRA_APPWIDGET_ID, + AppWidgetManager.INVALID_APPWIDGET_ID, + ) + viewModel.onWidgetIdChanged(widgetId) + + // Make sure to refresh the tiles/conversations when the lifecycle is resumed, so that it + // updates them when going back to the Activity after leaving it. + // Note that we do this here instead of inside an effect in the PeopleScreen() composable + // because otherwise onTileRefreshRequested() will be called after the first composition, + // which will trigger a new recomposition and redraw, affecting the GPU memory (see + // b/276871425). + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.RESUMED) { viewModel.onTileRefreshRequested() } + } + + // Set the content of the activity, using either the View or Compose implementation. + if (featureFlags.isEnabled(Flags.COMPOSE_PEOPLE_SPACE) && isComposeAvailable()) { + Log.d(TAG, "Using the Compose implementation of the PeopleSpaceActivity") + setPeopleSpaceActivityContent( + activity = this, + viewModel, + onResult = { finishActivity(it) }, + ) + } else { + Log.d(TAG, "Using the View implementation of the PeopleSpaceActivity") + val view = PeopleViewBinder.create(this) + bind(view, viewModel, lifecycleOwner = this, onResult = { finishActivity(it) }) + setContentView(view) + } + } + + private fun finishActivity(result: PeopleViewModel.Result) { + if (result is PeopleViewModel.Result.Success) { + if (DEBUG) Log.d(TAG, "Widget added!") + setResult(RESULT_OK, result.data) + } else { + if (DEBUG) Log.d(TAG, "Activity dismissed with no widgets added!") + setResult(RESULT_CANCELED) + } + + finish() + } + + companion object { + private const val TAG = "PeopleSpaceActivity" + private const val DEBUG = PeopleSpaceUtils.DEBUG + } +} diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt index d8a429e5bb1a..5f338c30c966 100644 --- a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt @@ -109,14 +109,6 @@ object PeopleViewBinder { } } } - - // Make sure to refresh the tiles/conversations when the Activity is resumed, so that it - // updates them when going back to the Activity after leaving it. - lifecycleOwner.lifecycleScope.launch { - lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { - viewModel.onTileRefreshRequested() - } - } } private fun setNoConversationsContent(view: ViewGroup, onGotItClicked: () -> Unit) { diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java index fa3f878ff431..2d460a0d28fb 100644 --- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java +++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java @@ -120,6 +120,7 @@ public class QRCodeScannerController implements mUserTracker = userTracker; mConfigEnableLockScreenButton = mContext.getResources().getBoolean( android.R.bool.config_enableQrCodeScannerOnLockScreen); + mExecutor.execute(this::updateQRCodeScannerActivityDetails); } /** @@ -158,18 +159,18 @@ public class QRCodeScannerController implements * Returns true if lock screen entry point for QR Code Scanner is to be enabled. */ public boolean isEnabledForLockScreenButton() { - return mQRCodeScannerEnabled && isAbleToOpenCameraApp() && isAvailableOnDevice(); + return mQRCodeScannerEnabled && isAbleToLaunchScannerActivity() && isAllowedOnLockScreen(); } - /** Returns whether the feature is available on the device. */ - public boolean isAvailableOnDevice() { + /** Returns whether the QR scanner button is allowed on lockscreen. */ + public boolean isAllowedOnLockScreen() { return mConfigEnableLockScreenButton; } /** - * Returns true if the feature can open a camera app on the device. + * Returns true if the feature can open the configured QR scanner activity. */ - public boolean isAbleToOpenCameraApp() { + public boolean isAbleToLaunchScannerActivity() { return mIntent != null && isActivityCallable(mIntent); } @@ -355,9 +356,6 @@ public class QRCodeScannerController implements // Reset cached values to default as we are no longer listening mOnDefaultQRCodeScannerChangedListener = null; - mQRCodeScannerActivity = null; - mIntent = null; - mComponentName = null; } private void notifyQRCodeScannerActivityChanged() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index 7523d6e976ce..ddd9463affd9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -7,6 +7,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; +import android.app.ActivityManager; import android.content.Context; import android.content.res.Configuration; import android.os.Bundle; @@ -549,10 +550,8 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { return mPages.get(0).mRecords.size(); } - public void startTileReveal(Set<String> tileSpecs, final Runnable postAnimation) { - if (tileSpecs.isEmpty() || mPages.size() < 2 || getScrollX() != 0 || !beginFakeDrag()) { - // Do not start the reveal animation unless there are tiles to animate, multiple - // TileLayouts available and the user has not already started dragging. + public void startTileReveal(Set<String> tilesToReveal, final Runnable postAnimation) { + if (shouldNotRunAnimation(tilesToReveal)) { return; } @@ -560,13 +559,13 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { final TileLayout lastPage = mPages.get(lastPageNumber); final ArrayList<Animator> bounceAnims = new ArrayList<>(); for (TileRecord tr : lastPage.mRecords) { - if (tileSpecs.contains(tr.tile.getTileSpec())) { + if (tilesToReveal.contains(tr.tile.getTileSpec())) { bounceAnims.add(setupBounceAnimator(tr.tileView, bounceAnims.size())); } } if (bounceAnims.isEmpty()) { - // All tileSpecs are on the first page. Nothing to do. + // All tilesToReveal are on the first page. Nothing to do. // TODO: potentially show a bounce animation for first page QS tiles endFakeDrag(); return; @@ -588,6 +587,16 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { postInvalidateOnAnimation(); } + private boolean shouldNotRunAnimation(Set<String> tilesToReveal) { + boolean noAnimationNeeded = tilesToReveal.isEmpty() || mPages.size() < 2; + boolean scrollingInProgress = getScrollX() != 0 || !beginFakeDrag(); + // isRunningInTestHarness() to disable animation in functional testing as it caused + // flakiness and is not needed there. Alternative solutions were more complex and would + // still be either potentially flaky or modify internal data. + // For more info see b/253493927 and b/293234595 + return noAnimationNeeded || scrollingInProgress || ActivityManager.isRunningInTestHarness(); + } + private int sanitizePageAction(int action) { int pageLeftId = AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT.getId(); int pageRightId = AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT.getId(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java index 9e365d34bed9..1ba377bc9702 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java @@ -120,7 +120,7 @@ public class QRCodeScannerTile extends QSTileImpl<QSTile.State> { state.label = mContext.getString(R.string.qr_code_scanner_title); state.contentDescription = state.label; state.icon = ResourceIcon.get(R.drawable.ic_qr_code_scanner); - state.state = mQRCodeScannerController.isAbleToOpenCameraApp() ? Tile.STATE_INACTIVE + state.state = mQRCodeScannerController.isAbleToLaunchScannerActivity() ? Tile.STATE_INACTIVE : Tile.STATE_UNAVAILABLE; // The assumption is that if the OEM has the QR code scanner module enabled then the scanner // would go to "Unavailable" state only when GMS core is updating. diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt index 5e6a44bf8130..4c6281e1cdb0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt @@ -16,19 +16,17 @@ package com.android.systemui.qs.ui.viewmodel +import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor import javax.inject.Inject /** Models UI state and handles user input for the quick settings scene. */ @SysUISingleton class QuickSettingsSceneViewModel @Inject -constructor( - private val lockscreenSceneInteractor: LockscreenSceneInteractor, -) { +constructor(private val bouncerInteractor: BouncerInteractor) { /** Notifies that some content in quick settings was clicked. */ fun onContentClicked() { - lockscreenSceneInteractor.dismissLockscreen() + bouncerInteractor.showOrUnlockDevice() } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt index fee3960ff0e1..350fa38f2052 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt @@ -18,50 +18,49 @@ package com.android.systemui.scene.data.repository +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel -import com.android.systemui.scene.shared.model.SceneTransitionModel import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.stateIn /** Source of truth for scene framework application state. */ class SceneContainerRepository @Inject constructor( + @Application applicationScope: CoroutineScope, private val config: SceneContainerConfig, ) { + private val _desiredScene = MutableStateFlow(SceneModel(config.initialSceneKey)) + val desiredScene: StateFlow<SceneModel> = _desiredScene.asStateFlow() private val _isVisible = MutableStateFlow(true) val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow() - private val _currentScene = MutableStateFlow(SceneModel(config.initialSceneKey)) - val currentScene: StateFlow<SceneModel> = _currentScene.asStateFlow() - - private val transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null) - val transitionProgress: Flow<Float> = - transitionState.flatMapLatest { observableTransitionStateFlow -> - observableTransitionStateFlow?.flatMapLatest { observableTransitionState -> - when (observableTransitionState) { - is ObservableTransitionState.Idle -> flowOf(1f) - is ObservableTransitionState.Transition -> observableTransitionState.progress - } - } - ?: flowOf(1f) - } - - private val _transitions = MutableStateFlow<SceneTransitionModel?>(null) - val transitions: StateFlow<SceneTransitionModel?> = _transitions.asStateFlow() + private val defaultTransitionState = ObservableTransitionState.Idle(config.initialSceneKey) + private val _transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null) + val transitionState: StateFlow<ObservableTransitionState> = + _transitionState + .flatMapLatest { innerFlowOrNull -> innerFlowOrNull ?: flowOf(defaultTransitionState) } + .stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = defaultTransitionState, + ) /** - * Returns the keys to all scenes in the container with the given name. + * Returns the keys to all scenes in the container. * * The scenes will be sorted in z-order such that the last one is the one that should be * rendered on top of all previous ones. @@ -70,40 +69,19 @@ constructor( return config.sceneKeys } - /** Sets the current scene in the container with the given name. */ - fun setCurrentScene(scene: SceneModel) { + fun setDesiredScene(scene: SceneModel) { check(allSceneKeys().contains(scene.key)) { """ - Cannot set current scene key to "${scene.key}". The configuration does not contain a - scene with that key. - """ - .trimIndent() - } - - _currentScene.value = scene - } - - /** Sets the scene transition in the container with the given name. */ - fun setSceneTransition(from: SceneKey, to: SceneKey) { - check(allSceneKeys().contains(from)) { - """ - Cannot set current scene key to "$from". The configuration does not contain a scene - with that key. - """ - .trimIndent() - } - check(allSceneKeys().contains(to)) { - """ - Cannot set current scene key to "$to". The configuration does not contain a scene - with that key. + Cannot set the desired scene key to "${scene.key}". The configuration does not + contain a scene with that key. """ .trimIndent() } - _transitions.value = SceneTransitionModel(from = from, to = to) + _desiredScene.value = scene } - /** Sets whether the container with the given name is visible. */ + /** Sets whether the container is visible. */ fun setVisible(isVisible: Boolean) { _isVisible.value = isVisible } @@ -114,6 +92,6 @@ constructor( * Note that you must call is with `null` when the UI is done or risk a memory leak. */ fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) { - this.transitionState.value = transitionState + _transitionState.value = transitionState } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt index 64715bc26674..cf7abdd34b70 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt @@ -23,12 +23,15 @@ import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.RemoteUserInput import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel -import com.android.systemui.scene.shared.model.SceneTransitionModel +import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull /** * Generic business logic and app state accessors for the scene framework. @@ -46,7 +49,54 @@ constructor( ) { /** - * Returns the keys of all scenes in the container with the given name. + * The currently *desired* scene. + * + * **Important:** this value will _commonly be different_ from what is being rendered in the UI, + * by design. + * + * There are two intended sources for this value: + * 1. Programmatic requests to transition to another scene (calls to [changeScene]). + * 2. Reports from the UI about completing a transition to another scene (calls to + * [onSceneChanged]). + * + * Both the sources above cause the value of this flow to change; however, they cause mismatches + * in different ways. + * + * **Updates from programmatic transitions** + * + * When an external bit of code asks the framework to switch to another scene, the value here + * will update immediately. Downstream, the UI will detect this change and initiate the + * transition animation. As the transition animation progresses, a threshold will be reached, at + * which point the UI and the state here will match each other. + * + * **Updates from the UI** + * + * When the user interacts with the UI, the UI runs a transition animation that tracks the user + * pointer (for example, the user's finger). During this time, the state value here and what the + * UI shows will likely not match. Once/if a threshold is met, the UI reports it and commits the + * change, making the value here match the UI again. + */ + val desiredScene: StateFlow<SceneModel> = repository.desiredScene + + /** + * The current state of the transition. + * + * Consumers should use this state to know: + * 1. Whether there is an ongoing transition or if the system is at rest. + * 2. When transitioning, which scenes are being transitioned between. + * 3. When transitioning, what the progress of the transition is. + */ + val transitionState: StateFlow<ObservableTransitionState> = repository.transitionState + + /** Whether the scene container is visible. */ + val isVisible: StateFlow<Boolean> = repository.isVisible + + private val _remoteUserInput: MutableStateFlow<RemoteUserInput?> = MutableStateFlow(null) + /** A flow of motion events originating from outside of the scene framework. */ + val remoteUserInput: StateFlow<RemoteUserInput?> = _remoteUserInput.asStateFlow() + + /** + * Returns the keys of all scenes in the container. * * The scenes will be sorted in z-order such that the last one is the one that should be * rendered on top of all previous ones. @@ -55,26 +105,20 @@ constructor( return repository.allSceneKeys() } - /** Sets the scene in the container with the given name. */ - fun setCurrentScene(scene: SceneModel, loggingReason: String) { - val currentSceneKey = repository.currentScene.value.key - if (currentSceneKey == scene.key) { - return - } - - logger.logSceneChange( - from = currentSceneKey, - to = scene.key, - reason = loggingReason, - ) - repository.setCurrentScene(scene) - repository.setSceneTransition(from = currentSceneKey, to = scene.key) + /** + * Requests a scene change to the given scene. + * + * The change is animated. Therefore, while the value in [desiredScene] will update immediately, + * it will be some time before the UI will switch to the desired scene. The scene change + * requested is remembered here but served by the UI layer, which will start a transition + * animation. Once enough of the transition has occurred, the system will come into agreement + * between the [desiredScene] and the UI. + */ + fun changeScene(scene: SceneModel, loggingReason: String) { + updateDesiredScene(scene, loggingReason, logger::logSceneChangeRequested) } - /** The current scene in the container with the given name. */ - val currentScene: StateFlow<SceneModel> = repository.currentScene - - /** Sets the visibility of the container with the given name. */ + /** Sets the visibility of the container. */ fun setVisible(isVisible: Boolean, loggingReason: String) { val wasVisible = repository.isVisible.value if (wasVisible == isVisible) { @@ -89,9 +133,6 @@ constructor( return repository.setVisible(isVisible) } - /** Whether the container with the given name is visible. */ - val isVisible: StateFlow<Boolean> = repository.isVisible - /** * Binds the given flow so the system remembers it. * @@ -101,23 +142,53 @@ constructor( repository.setTransitionState(transitionState) } - /** Progress of the transition into the current scene in the container with the given name. */ - val transitionProgress: Flow<Float> = repository.transitionProgress - /** - * Scene transitions as pairs of keys. A new value is emitted exactly once, each time a scene - * transition occurs. The flow begins with a `null` value at first, because the initial scene is - * not something that we transition to from another scene. + * Returns a stream of events that emits one [Unit] every time the framework transitions from + * [from] to [to]. */ - val transitions: StateFlow<SceneTransitionModel?> = repository.transitions - - private val _remoteUserInput: MutableStateFlow<RemoteUserInput?> = MutableStateFlow(null) - - /** A flow of motion events originating from outside of the scene framework. */ - val remoteUserInput: StateFlow<RemoteUserInput?> = _remoteUserInput.asStateFlow() + fun finishedSceneTransitions(from: SceneKey, to: SceneKey): Flow<Unit> { + return transitionState + .mapNotNull { it as? ObservableTransitionState.Idle } + .map { idleState -> idleState.scene } + .distinctUntilChanged() + .pairwise() + .mapNotNull { (previousSceneKey, currentSceneKey) -> + Unit.takeIf { previousSceneKey == from && currentSceneKey == to } + } + } /** Handles a remote user input. */ fun onRemoteUserInput(input: RemoteUserInput) { _remoteUserInput.value = input } + + /** + * Notifies that the UI has transitioned sufficiently to the given scene. + * + * *Not intended for external use!* + * + * Once a transition between one scene and another passes a threshold, the UI invokes this + * method to report it, updating the value in [desiredScene] to match what the UI shows. + */ + internal fun onSceneChanged(scene: SceneModel, loggingReason: String) { + updateDesiredScene(scene, loggingReason, logger::logSceneChangeCommitted) + } + + private fun updateDesiredScene( + scene: SceneModel, + loggingReason: String, + log: (from: SceneKey, to: SceneKey, loggingReason: String) -> Unit, + ) { + val currentSceneKey = desiredScene.value.key + if (currentSceneKey == scene.key) { + return + } + + log( + /* from= */ currentSceneKey, + /* to= */ scene.key, + /* loggingReason= */ loggingReason, + ) + repository.setDesiredScene(scene) + } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 20ee393e8dac..afefccb27214 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -18,6 +18,7 @@ package com.android.systemui.scene.domain.startable import com.android.systemui.CoreStartable import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor +import com.android.systemui.authentication.domain.model.AuthenticationMethodModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.DisplayId @@ -29,6 +30,7 @@ import com.android.systemui.model.SysUiState import com.android.systemui.model.updateFlags import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.logger.SceneLogger +import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING @@ -39,8 +41,8 @@ import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_B import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.launch /** @@ -72,14 +74,31 @@ constructor( } } - /** Updates the visibility of the scene container based on the current scene. */ + /** Updates the visibility of the scene container. */ private fun hydrateVisibility() { applicationScope.launch { - sceneInteractor.currentScene - .map { it.key } + sceneInteractor.transitionState + .mapNotNull { state -> + when (state) { + is ObservableTransitionState.Idle -> { + if (state.scene != SceneKey.Gone) { + true to "scene is not Gone" + } else { + false to "scene is Gone" + } + } + is ObservableTransitionState.Transition -> { + if (state.fromScene == SceneKey.Gone) { + true to "scene transitioning away from Gone" + } else { + null + } + } + } + } .distinctUntilChanged() - .collect { sceneKey -> - sceneInteractor.setVisible(sceneKey != SceneKey.Gone, "scene is $sceneKey") + .collect { (isVisible, loggingReason) -> + sceneInteractor.setVisible(isVisible, loggingReason) } } } @@ -88,43 +107,55 @@ constructor( private fun automaticallySwitchScenes() { applicationScope.launch { authenticationInteractor.isUnlocked - .map { isUnlocked -> - val currentSceneKey = sceneInteractor.currentScene.value.key + .mapNotNull { isUnlocked -> + val renderedScenes = + when (val transitionState = sceneInteractor.transitionState.value) { + is ObservableTransitionState.Idle -> setOf(transitionState.scene) + is ObservableTransitionState.Transition -> + setOf( + transitionState.progress, + transitionState.toScene, + ) + } val isBypassEnabled = authenticationInteractor.isBypassEnabled() when { isUnlocked -> - when (currentSceneKey) { + when { // When the device becomes unlocked in Bouncer, go to Gone. - is SceneKey.Bouncer -> + renderedScenes.contains(SceneKey.Bouncer) -> SceneKey.Gone to "device unlocked in Bouncer scene" + // When the device becomes unlocked in Lockscreen, go to Gone if // bypass is enabled. - is SceneKey.Lockscreen -> + renderedScenes.contains(SceneKey.Lockscreen) -> if (isBypassEnabled) { SceneKey.Gone to "device unlocked in Lockscreen scene with bypass" } else { null } + // We got unlocked while on a scene that's not Lockscreen or // Bouncer, no need to change scenes. else -> null } + // When the device becomes locked, to Lockscreen. !isUnlocked -> - when (currentSceneKey) { + when { // Already on lockscreen or bouncer, no need to change scenes. - is SceneKey.Lockscreen, - is SceneKey.Bouncer -> null + renderedScenes.contains(SceneKey.Lockscreen) || + renderedScenes.contains(SceneKey.Bouncer) -> null + // We got locked while on a scene that's not Lockscreen or Bouncer, // go to Lockscreen. else -> - SceneKey.Lockscreen to "device locked in $currentSceneKey scene" + SceneKey.Lockscreen to + "device locked in non-Lockscreen and non-Bouncer scene" } else -> null } } - .filterNotNull() .collect { (targetSceneKey, loggingReason) -> switchToScene( targetSceneKey = targetSceneKey, @@ -135,22 +166,27 @@ constructor( applicationScope.launch { keyguardInteractor.wakefulnessModel - .map { it.state == WakefulnessState.ASLEEP } + .map { wakefulnessModel -> wakefulnessModel.state } .distinctUntilChanged() - .collect { isAsleep -> - if (isAsleep) { - // When the device goes to sleep, reset the current scene. - val isUnlocked = authenticationInteractor.isUnlocked.value - val (targetSceneKey, loggingReason) = - if (isUnlocked) { - SceneKey.Gone to "device is asleep while unlocked" - } else { - SceneKey.Lockscreen to "device is asleep while locked" + .collect { wakefulnessState -> + when (wakefulnessState) { + WakefulnessState.STARTING_TO_SLEEP -> { + switchToScene( + targetSceneKey = SceneKey.Lockscreen, + loggingReason = "device is starting to sleep", + ) + } + WakefulnessState.STARTING_TO_WAKE -> { + val authMethod = authenticationInteractor.getAuthenticationMethod() + if (authMethod == AuthenticationMethodModel.None) { + switchToScene( + targetSceneKey = SceneKey.Gone, + loggingReason = + "device is starting to wake up while auth method is None", + ) } - switchToScene( - targetSceneKey = targetSceneKey, - loggingReason = loggingReason, - ) + } + else -> Unit } } } @@ -159,8 +195,9 @@ constructor( /** Keeps [SysUiState] up-to-date */ private fun hydrateSystemUiState() { applicationScope.launch { - sceneInteractor.currentScene - .map { it.key } + sceneInteractor.transitionState + .mapNotNull { it as? ObservableTransitionState.Idle } + .map { it.scene } .distinctUntilChanged() .collect { sceneKey -> sysUiState.updateFlags( @@ -177,7 +214,7 @@ constructor( } private fun switchToScene(targetSceneKey: SceneKey, loggingReason: String) { - sceneInteractor.setCurrentScene( + sceneInteractor.changeScene( scene = SceneModel(targetSceneKey), loggingReason = loggingReason, ) diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt index 0adbd5ad19a7..62136dcd8e1d 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt @@ -37,7 +37,7 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: ) } - fun logSceneChange( + fun logSceneChangeRequested( from: SceneKey, to: SceneKey, reason: String, @@ -50,7 +50,24 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: str2 = to.toString() str3 = reason }, - messagePrinter = { "$str1 → $str2, reason: $str3" }, + messagePrinter = { "Scene change requested: $str1 → $str2, reason: $str3" }, + ) + } + + fun logSceneChangeCommitted( + from: SceneKey, + to: SceneKey, + reason: String, + ) { + logBuffer.log( + tag = TAG, + level = LogLevel.INFO, + messageInitializer = { + str1 = from.toString() + str2 = to.toString() + str3 = reason + }, + messagePrinter = { "Scene change committed: $str1 → $str2, reason: $str3" }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt deleted file mode 100644 index c8f46a72d64f..000000000000 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2023 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.scene.shared.model - -/** Models a transition between two scenes. */ -data class SceneTransitionModel( - /** The scene we transitioned away from. */ - val from: SceneKey, - /** The scene we transitioned into. */ - val to: SceneKey, -) diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt index b4ebaece21f1..3e9bbe464e2c 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt @@ -45,15 +45,15 @@ constructor( */ val allSceneKeys: List<SceneKey> = interactor.allSceneKeys() - /** The current scene. */ - val currentScene: StateFlow<SceneModel> = interactor.currentScene + /** The scene that should be rendered. */ + val currentScene: StateFlow<SceneModel> = interactor.desiredScene /** Whether the container is visible. */ val isVisible: StateFlow<Boolean> = interactor.isVisible - /** Requests a transition to the scene with the given key. */ - fun setCurrentScene(scene: SceneModel) { - interactor.setCurrentScene( + /** Notifies that the UI has transitioned sufficiently to the given scene. */ + fun onSceneChanged(scene: SceneModel) { + interactor.onSceneChanged( scene = scene, loggingReason = SCENE_TRANSITION_LOGGING_REASON, ) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt index a8f99bef2423..05a0416f8f64 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt @@ -19,6 +19,7 @@ package com.android.systemui.screenshot import android.content.ClipData import android.content.ClipDescription import android.content.ComponentName +import android.content.ContentProvider import android.content.Context import android.content.Intent import android.net.Uri @@ -26,21 +27,19 @@ import com.android.systemui.R object ActionIntentCreator { /** @return a chooser intent to share the given URI. */ - fun createShareIntent(uri: Uri) = createShareIntent(uri, null, null) + fun createShare(uri: Uri): Intent = createShare(uri, subject = null, text = null) /** @return a chooser intent to share the given URI with the optional provided subject. */ - fun createShareIntentWithSubject(uri: Uri, subject: String?) = - createShareIntent(uri, subject = subject) + fun createShareWithSubject(uri: Uri, subject: String): Intent = + createShare(uri, subject = subject) /** @return a chooser intent to share the given URI with the optional provided extra text. */ - fun createShareIntentWithExtraText(uri: Uri, extraText: String?) = - createShareIntent(uri, extraText = extraText) + fun createShareWithText(uri: Uri, extraText: String): Intent = + createShare(uri, text = extraText) + + private fun createShare(rawUri: Uri, subject: String? = null, text: String? = null): Intent { + val uri = uriWithoutUserId(rawUri) - private fun createShareIntent( - uri: Uri, - subject: String? = null, - extraText: String? = null - ): Intent { // Create a share intent, this will always go through the chooser activity first // which should not trigger auto-enter PiP val sharingIntent = @@ -56,8 +55,8 @@ object ActionIntentCreator { ClipData.Item(uri) ) - putExtra(Intent.EXTRA_SUBJECT, subject) - putExtra(Intent.EXTRA_TEXT, extraText) + subject?.let { putExtra(Intent.EXTRA_SUBJECT, subject) } + text?.let { putExtra(Intent.EXTRA_TEXT, text) } addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) } @@ -72,13 +71,13 @@ object ActionIntentCreator { * @return an ACTION_EDIT intent for the given URI, directed to config_screenshotEditor if * available. */ - fun createEditIntent(uri: Uri, context: Context): Intent { + fun createEdit(rawUri: Uri, context: Context): Intent { + val uri = uriWithoutUserId(rawUri) val editIntent = Intent(Intent.ACTION_EDIT) - context.getString(R.string.config_screenshotEditor)?.let { - if (it.isNotEmpty()) { - editIntent.component = ComponentName.unflattenFromString(it) - } + val editor = context.getString(R.string.config_screenshotEditor) + if (editor.isNotEmpty()) { + editIntent.component = ComponentName.unflattenFromString(editor) } return editIntent @@ -89,3 +88,12 @@ object ActionIntentCreator { .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) } } + +/** + * URIs here are passed only via Intent which are sent to the target user via Intent. Because of + * this, the userId component can be removed to prevent compatibility issues when an app attempts + * valid a URI containing a userId within the authority. + */ +private fun uriWithoutUserId(uri: Uri): Uri { + return ContentProvider.getUriWithoutUserId(uri) +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt index 187019a4851d..ecd456887fa4 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt @@ -19,6 +19,7 @@ package com.android.systemui.screenshot import android.content.Context import android.content.Intent import android.os.Bundle +import android.os.Process.myUserHandle import android.os.RemoteException import android.os.UserHandle import android.util.Log @@ -44,10 +45,10 @@ import kotlinx.coroutines.withContext class ActionIntentExecutor @Inject constructor( + private val context: Context, @Application private val applicationScope: CoroutineScope, @Main private val mainDispatcher: CoroutineDispatcher, - private val context: Context, - private val displayTracker: DisplayTracker + private val displayTracker: DisplayTracker, ) { /** * Execute the given intent with startActivity while performing operations for screenshot action @@ -58,25 +59,25 @@ constructor( */ fun launchIntentAsync( intent: Intent, - bundle: Bundle, - userId: Int, + options: Bundle?, + user: UserHandle, overrideTransition: Boolean, ) { - applicationScope.launch { launchIntent(intent, bundle, userId, overrideTransition) } + applicationScope.launch { launchIntent(intent, options, user, overrideTransition) } } suspend fun launchIntent( intent: Intent, - bundle: Bundle, - userId: Int, + options: Bundle?, + user: UserHandle, overrideTransition: Boolean, ) { dismissKeyguard() - if (userId == UserHandle.myUserId()) { - withContext(mainDispatcher) { context.startActivity(intent, bundle) } + if (user == myUserHandle()) { + withContext(mainDispatcher) { context.startActivity(intent, options) } } else { - launchCrossProfileIntent(userId, intent, bundle) + launchCrossProfileIntent(user, intent, options) } if (overrideTransition) { @@ -111,17 +112,21 @@ constructor( completion.await() } - private fun getCrossProfileConnector(userId: Int): ServiceConnector<ICrossProfileService> = + private fun getCrossProfileConnector(user: UserHandle): ServiceConnector<ICrossProfileService> = ServiceConnector.Impl<ICrossProfileService>( context, Intent(context, ScreenshotCrossProfileService::class.java), Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE, - userId, + user.identifier, ICrossProfileService.Stub::asInterface, ) - private suspend fun launchCrossProfileIntent(userId: Int, intent: Intent, bundle: Bundle) { - val connector = getCrossProfileConnector(userId) + private suspend fun launchCrossProfileIntent( + user: UserHandle, + intent: Intent, + bundle: Bundle? + ) { + val connector = getCrossProfileConnector(user) val completion = CompletableDeferred<Unit>() connector.post { it.launchIntent(intent, bundle) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java index e6e1faccc3e2..53dbe7624806 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java @@ -334,9 +334,9 @@ public class LongScreenshotActivity extends Activity { if (mScreenshotUserHandle != Process.myUserHandle()) { // TODO: Fix transition for work profile. Omitting it in the meantime. mActionExecutor.launchIntentAsync( - ActionIntentCreator.INSTANCE.createEditIntent(uri, this), + ActionIntentCreator.INSTANCE.createEdit(uri, this), null, - mScreenshotUserHandle.getIdentifier(), false); + mScreenshotUserHandle, false); } else { String editorPackage = getString(R.string.config_screenshotEditor); Intent intent = new Intent(Intent.ACTION_EDIT); @@ -362,9 +362,8 @@ public class LongScreenshotActivity extends Activity { } private void doShare(Uri uri) { - Intent shareIntent = ActionIntentCreator.INSTANCE.createShareIntent(uri); - mActionExecutor.launchIntentAsync(shareIntent, null, - mScreenshotUserHandle.getIdentifier(), false); + Intent shareIntent = ActionIntentCreator.INSTANCE.createShare(uri); + mActionExecutor.launchIntentAsync(shareIntent, null, mScreenshotUserHandle, false); } private void onClicked(View v) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index 093c09fff995..3903bb2815ef 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -801,31 +801,31 @@ public class ScreenshotView extends FrameLayout implements Intent shareIntent; if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && mScreenshotData != null && mScreenshotData.getContextUrl() != null) { - shareIntent = ActionIntentCreator.INSTANCE.createShareIntentWithExtraText( + shareIntent = ActionIntentCreator.INSTANCE.createShareWithText( imageData.uri, mScreenshotData.getContextUrl().toString()); } else { - shareIntent = ActionIntentCreator.INSTANCE.createShareIntentWithSubject( + shareIntent = ActionIntentCreator.INSTANCE.createShareWithSubject( imageData.uri, imageData.subject); } mActionExecutor.launchIntentAsync(shareIntent, imageData.shareTransition.get().bundle, - imageData.owner.getIdentifier(), false); + imageData.owner, false); }); mEditChip.setOnClickListener(v -> { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED, 0, mPackageName); prepareSharedTransition(); mActionExecutor.launchIntentAsync( - ActionIntentCreator.INSTANCE.createEditIntent(imageData.uri, mContext), + ActionIntentCreator.INSTANCE.createEdit(imageData.uri, mContext), imageData.editTransition.get().bundle, - imageData.owner.getIdentifier(), true); + imageData.owner, true); }); mScreenshotPreview.setOnClickListener(v -> { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED, 0, mPackageName); prepareSharedTransition(); mActionExecutor.launchIntentAsync( - ActionIntentCreator.INSTANCE.createEditIntent(imageData.uri, mContext), + ActionIntentCreator.INSTANCE.createEdit(imageData.uri, mContext), imageData.editTransition.get().bundle, - imageData.owner.getIdentifier(), true); + imageData.owner, true); }); if (mQuickShareChip != null) { if (imageData.quickShareAction != null) { diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt index d33d113f6cbd..2f0fc5127009 100644 --- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt @@ -228,6 +228,8 @@ open class SensorUseStartedActivity @Inject constructor( } override fun onDismiss(dialog: DialogInterface?) { - finish() + if (!isChangingConfigurations) { + finish() + } } } diff --git a/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt index 468a75d8276e..e7ee961e1888 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt @@ -48,6 +48,9 @@ interface DisplayTracker { /** Remove a [Callback] previously added. */ fun removeCallback(callback: Callback) + /** Gets the Display with the given displayId */ + fun getDisplay(displayId: Int): Display + /** Ćallback for notifying of changes. */ interface Callback { diff --git a/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt index 5169f88c373c..68cc483fbe80 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt @@ -115,6 +115,10 @@ internal constructor( } } + override fun getDisplay(displayId: Int): Display { + return displayManager.getDisplay(displayId) + } + @WorkerThread private fun onDisplayAdded(displayId: Int, list: List<DisplayTrackerDataItem>) { Assert.isNotMainThread() diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 416f147b7429..35fd98cf34c6 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -90,6 +90,8 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.Interpolator; import android.widget.FrameLayout; +import androidx.constraintlayout.widget.ConstraintLayout; + import com.android.app.animation.Interpolators; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; @@ -1053,10 +1055,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardStatusBarViewController.init(); mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent); - updateViewControllers( - mView.findViewById(R.id.keyguard_status_view), - userAvatarContainer, - keyguardUserSwitcherView); + updateViewControllers(userAvatarContainer, keyguardUserSwitcherView); mNotificationStackScrollLayoutController.setOnHeightChangedListener( new NsslHeightChangedListener()); @@ -1218,18 +1217,31 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mQsController.loadDimens(); } - private void updateViewControllers(KeyguardStatusView keyguardStatusView, + private void updateViewControllers( FrameLayout userAvatarView, KeyguardUserSwitcherView keyguardUserSwitcherView) { + // Re-associate the KeyguardStatusViewController if (mKeyguardStatusViewController != null) { mKeyguardStatusViewController.onDestroy(); } - // Re-associate the KeyguardStatusViewController - KeyguardStatusViewComponent statusViewComponent = + + if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + // Need a shared controller until mKeyguardStatusViewController can be removed from + // here, due to important state being set in that controller. Rebind in order to pick + // up config changes + mKeyguardViewConfigurator.bindKeyguardStatusView(mView); + mKeyguardStatusViewController = + mKeyguardViewConfigurator.getKeyguardStatusViewController(); + } else { + KeyguardStatusView keyguardStatusView = mView.getRootView().findViewById( + R.id.keyguard_status_view); + KeyguardStatusViewComponent statusViewComponent = mKeyguardStatusViewComponentFactory.build(keyguardStatusView); - mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController(); - mKeyguardStatusViewController.init(); + mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController(); + mKeyguardStatusViewController.init(); + } mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled); + updateClockAppearance(); if (mKeyguardUserSwitcherController != null) { @@ -1335,15 +1347,22 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump void reInflateViews() { debugLog("reInflateViews"); // Re-inflate the status view group. - KeyguardStatusView keyguardStatusView = - mNotificationContainerParent.findViewById(R.id.keyguard_status_view); - int statusIndex = mNotificationContainerParent.indexOfChild(keyguardStatusView); - mNotificationContainerParent.removeView(keyguardStatusView); - keyguardStatusView = (KeyguardStatusView) mLayoutInflater.inflate( - R.layout.keyguard_status_view, mNotificationContainerParent, false); - mNotificationContainerParent.addView(keyguardStatusView, statusIndex); - attachSplitShadeMediaPlayerContainer( - keyguardStatusView.findViewById(R.id.status_view_media_container)); + if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + KeyguardStatusView keyguardStatusView = + mNotificationContainerParent.findViewById(R.id.keyguard_status_view); + int statusIndex = mNotificationContainerParent.indexOfChild(keyguardStatusView); + mNotificationContainerParent.removeView(keyguardStatusView); + keyguardStatusView = (KeyguardStatusView) mLayoutInflater.inflate( + R.layout.keyguard_status_view, mNotificationContainerParent, false); + mNotificationContainerParent.addView(keyguardStatusView, statusIndex); + + attachSplitShadeMediaPlayerContainer( + keyguardStatusView.findViewById(R.id.status_view_media_container)); + } else { + attachSplitShadeMediaPlayerContainer( + mKeyguardViewConfigurator.getKeyguardRootView() + .findViewById(R.id.status_view_media_container)); + } // we need to update KeyguardStatusView constraints after reinflating it updateResources(); @@ -1369,8 +1388,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump R.layout.keyguard_user_switcher /* layoutId */, showKeyguardUserSwitcher /* enabled */); - updateViewControllers(mView.findViewById(R.id.keyguard_status_view), userAvatarView, - keyguardUserSwitcherView); + updateViewControllers(userAvatarView, keyguardUserSwitcherView); if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { // Update keyguard bottom area @@ -1666,8 +1684,14 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private void updateKeyguardStatusViewAlignment(boolean animate) { boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered(); + ConstraintLayout layout; + if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + layout = mKeyguardViewConfigurator.getKeyguardRootView(); + } else { + layout = mNotificationContainerParent; + } mKeyguardStatusViewController.updateAlignment( - mNotificationContainerParent, mSplitShadeEnabled, shouldBeCentered, animate); + layout, mSplitShadeEnabled, shouldBeCentered, animate); mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(shouldBeCentered)); } @@ -3390,7 +3414,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump ipw.print("mPanelFlingOvershootAmount="); ipw.println(mPanelFlingOvershootAmount); ipw.print("mLastGesturedOverExpansion="); ipw.println(mLastGesturedOverExpansion); ipw.print("mIsSpringBackAnimation="); ipw.println(mIsSpringBackAnimation); - ipw.print("mSplitShadeEnabled="); ipw.println(mSplitShadeEnabled); ipw.print("mHintDistance="); ipw.println(mHintDistance); ipw.print("mInitialOffsetOnTouch="); ipw.println(mInitialOffsetOnTouch); ipw.print("mCollapsedAndHeadsUpOnDown="); ipw.println(mCollapsedAndHeadsUpOnDown); @@ -3423,7 +3446,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump ipw.print("mGestureWaitForTouchSlop="); ipw.println(mGestureWaitForTouchSlop); ipw.print("mIgnoreXTouchSlop="); ipw.println(mIgnoreXTouchSlop); ipw.print("mExpandLatencyTracking="); ipw.println(mExpandLatencyTracking); - ipw.print("mExpandLatencyTracking="); ipw.println(mExpandLatencyTracking); ipw.println("gestureExclusionRect:" + calculateGestureExclusionRect()); new DumpsysTableLogger( TAG, diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index 18e964462189..5b5785e36ccc 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -508,6 +508,7 @@ public class NotificationShadeWindowViewController { MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); event.setSource(InputDevice.SOURCE_TOUCHSCREEN); } + Log.w(TAG, "Canceling current touch event (should be very rare)"); mView.dispatchTouchEvent(event); event.recycle(); mTouchCancelled = true; diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt index 5c1dd5670d8a..941254223965 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt @@ -155,10 +155,8 @@ class NotificationsQSContainerController @Inject constructor( largeScreenShadeHeaderActive = LargeScreenUtils.shouldUseLargeScreenShadeHeader(resources) notificationsBottomMargin = resources.getDimensionPixelSize( R.dimen.notification_panel_margin_bottom) - largeScreenShadeHeaderHeight = - resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height) - shadeHeaderHeight = - resources.getDimensionPixelSize(R.dimen.qs_header_height) + largeScreenShadeHeaderHeight = calculateLargeShadeHeaderHeight() + shadeHeaderHeight = calculateShadeHeaderHeight() panelMarginHorizontal = resources.getDimensionPixelSize( R.dimen.notification_panel_margin_horizontal) topMargin = if (largeScreenShadeHeaderActive) { @@ -182,6 +180,23 @@ class NotificationsQSContainerController @Inject constructor( } } + private fun calculateLargeShadeHeaderHeight(): Int { + return resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height) + } + + private fun calculateShadeHeaderHeight(): Int { + val minHeight = resources.getDimensionPixelSize(R.dimen.qs_header_height) + + // Following the constraints in xml/qs_header, the total needed height would be the sum of + // 1. privacy_container height (R.dimen.large_screen_shade_header_min_height) + // 2. carrier_group height (R.dimen.large_screen_shade_header_min_height) + // 3. date height (R.dimen.new_qs_header_non_clickable_element_height) + val estimatedHeight = + 2 * resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height) + + resources.getDimensionPixelSize(R.dimen.new_qs_header_non_clickable_element_height) + return estimatedHeight.coerceAtLeast(minHeight) + } + override fun setCustomizerAnimating(animating: Boolean) { if (isQSCustomizerAnimating != animating) { isQSCustomizerAnimating = animating diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index baac57ca44ba..e85024e3f5d1 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -1011,6 +1011,7 @@ public class QuickSettingsController implements Dumpable { && mPanelViewControllerLazy.get().mAnimateBack) { mPanelViewControllerLazy.get().adjustBackAnimationScale(adjustedExpansionFraction); } + mShadeExpansionStateManager.onQsExpansionFractionChanged(qsExpansionFraction); mMediaHierarchyManager.setQsExpansion(qsExpansionFraction); int qsPanelBottomY = calculateBottomPosition(qsExpansionFraction); mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY); diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt index 2db47aeb9e90..0554c5855d61 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt @@ -38,6 +38,8 @@ class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents { private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>() private val fullExpansionListeners = CopyOnWriteArrayList<ShadeFullExpansionListener>() private val qsExpansionListeners = CopyOnWriteArrayList<ShadeQsExpansionListener>() + private val qsExpansionFractionListeners = + CopyOnWriteArrayList<ShadeQsExpansionFractionListener>() private val stateListeners = CopyOnWriteArrayList<ShadeStateListener>() private val shadeStateEventsListeners = CopyOnWriteArrayList<ShadeStateEventsListener>() @@ -45,6 +47,7 @@ class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents { @FloatRange(from = 0.0, to = 1.0) private var fraction: Float = 0f private var expanded: Boolean = false private var qsExpanded: Boolean = false + private var qsExpansionFraction = 0f private var tracking: Boolean = false private var dragDownPxAmount: Float = 0f @@ -82,6 +85,15 @@ class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents { qsExpansionListeners.remove(listener) } + fun addQsExpansionFractionListener(listener: ShadeQsExpansionFractionListener) { + qsExpansionFractionListeners.add(listener) + listener.onQsExpansionFractionChanged(qsExpansionFraction) + } + + fun removeQsExpansionFractionListener(listener: ShadeQsExpansionFractionListener) { + qsExpansionFractionListeners.remove(listener) + } + /** Adds a listener that will be notified when the panel state has changed. */ fun addStateListener(listener: ShadeStateListener) { stateListeners.add(listener) @@ -175,6 +187,15 @@ class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents { qsExpansionListeners.forEach { it.onQsExpansionChanged(qsExpanded) } } + fun onQsExpansionFractionChanged(qsExpansionFraction: Float) { + this.qsExpansionFraction = qsExpansionFraction + + debugLog("qsExpansionFraction=$qsExpansionFraction") + qsExpansionFractionListeners.forEach { + it.onQsExpansionFractionChanged(qsExpansionFraction) + } + } + fun onShadeExpansionFullyChanged(isExpanded: Boolean) { this.expanded = isExpanded diff --git a/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderServiceCallback.aidl b/packages/SystemUI/src/com/android/systemui/shade/ShadeQsExpansionFractionListener.kt index f6c47ddf1e00..c787f49e22ce 100644 --- a/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderServiceCallback.aidl +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeQsExpansionFractionListener.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * 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. @@ -14,15 +14,10 @@ * limitations under the License. */ -package android.service.selectiontoolbar; +package com.android.systemui.shade -import android.os.IBinder; - -/** - * The interface from the SelectionToolbarRenderService to the system. - * - * @hide - */ -oneway interface ISelectionToolbarRenderServiceCallback { - void transferTouch(in IBinder source, in IBinder target); +/** A listener interface to be notified of expansion events for the quick settings panel. */ +fun interface ShadeQsExpansionFractionListener { + /** Invoked whenever the quick settings expansion fraction changes */ + fun onQsExpansionFractionChanged(qsExpansionFraction: Float) } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt index 29551188c5c7..6e7678407805 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt @@ -273,6 +273,7 @@ abstract class ShadeViewProviderModule { tunerService: TunerService, @Main mainHandler: Handler, contentResolver: ContentResolver, + featureFlags: FeatureFlags, batteryController: BatteryController, ): BatteryMeterViewController { return BatteryMeterViewController( @@ -283,6 +284,7 @@ abstract class ShadeViewProviderModule { tunerService, mainHandler, contentResolver, + featureFlags, batteryController, ) } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt index 0b3ed5601c2e..87abc9208d45 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt @@ -16,9 +16,10 @@ package com.android.systemui.shade.ui.viewmodel +import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor +import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor import com.android.systemui.scene.shared.model.SceneKey import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -33,29 +34,30 @@ class ShadeSceneViewModel @Inject constructor( @Application private val applicationScope: CoroutineScope, - private val lockscreenSceneInteractor: LockscreenSceneInteractor, + authenticationInteractor: AuthenticationInteractor, + private val bouncerInteractor: BouncerInteractor, ) { /** The key of the scene we should switch to when swiping up. */ val upDestinationSceneKey: StateFlow<SceneKey> = - lockscreenSceneInteractor.isDeviceLocked - .map { isLocked -> upDestinationSceneKey(isLocked = isLocked) } + authenticationInteractor.isUnlocked + .map { isUnlocked -> upDestinationSceneKey(isUnlocked = isUnlocked) } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = upDestinationSceneKey( - isLocked = lockscreenSceneInteractor.isDeviceLocked.value, + isUnlocked = authenticationInteractor.isUnlocked.value, ), ) /** Notifies that some content in the shade was clicked. */ fun onContentClicked() { - lockscreenSceneInteractor.dismissLockscreen() + bouncerInteractor.showOrUnlockDevice() } private fun upDestinationSceneKey( - isLocked: Boolean, + isUnlocked: Boolean, ): SceneKey { - return if (isLocked) SceneKey.Lockscreen else SceneKey.Gone + return if (isUnlocked) SceneKey.Gone else SceneKey.Lockscreen } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt index 94251ffc74c8..efd7d2ef2718 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -446,6 +446,12 @@ constructor( session?.requestSmartspaceUpdate() } + fun removeViewsFromParent(viewGroup: ViewGroup) { + smartspaceViews.toList().forEach { + viewGroup.removeView(it as View) + } + } + /** * Disconnects the smartspace view from the smartspace service and cleans up any resources. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index 5c72731e71a7..e763797d9966 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -99,6 +99,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Ra import com.android.systemui.statusbar.notification.collection.notifcollection.RankingUpdatedEvent; import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider; import com.android.systemui.util.Assert; +import com.android.systemui.util.NamedListenerSet; import com.android.systemui.util.time.SystemClock; import java.io.PrintWriter; @@ -161,7 +162,8 @@ public class NotifCollection implements Dumpable, PipelineDumpable { private final HashMap<String, FutureDismissal> mFutureDismissals = new HashMap<>(); @Nullable private CollectionReadyForBuildListener mBuildListener; - private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>(); + private final NamedListenerSet<NotifCollectionListener> + mNotifCollectionListeners = new NamedListenerSet<>(); private final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>(); private final List<NotifDismissInterceptor> mDismissInterceptors = new ArrayList<>(); @@ -236,7 +238,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { /** @see NotifPipeline#addCollectionListener(NotifCollectionListener) */ void addCollectionListener(NotifCollectionListener listener) { Assert.isMainThread(); - mNotifCollectionListeners.add(listener); + mNotifCollectionListeners.addIfAbsent(listener); } /** @see NotifPipeline#removeCollectionListener(NotifCollectionListener) */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt index d95d593778a9..5acc50ab878f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt @@ -21,7 +21,6 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.util.Assert import com.android.systemui.util.ListenerSet -import com.android.systemui.util.isNotEmpty import com.android.systemui.util.traceSection import java.util.Collections.unmodifiableList import java.util.concurrent.Executor diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index 02055237c2b3..240ae0c13ac7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -69,6 +69,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt; import com.android.systemui.util.Assert; +import com.android.systemui.util.NamedListenerSet; import com.android.systemui.util.time.SystemClock; import java.io.PrintWriter; @@ -121,14 +122,14 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { private final List<NotifSection> mNotifSections = new ArrayList<>(); private NotifStabilityManager mNotifStabilityManager; - private final List<OnBeforeTransformGroupsListener> mOnBeforeTransformGroupsListeners = - new ArrayList<>(); - private final List<OnBeforeSortListener> mOnBeforeSortListeners = - new ArrayList<>(); - private final List<OnBeforeFinalizeFilterListener> mOnBeforeFinalizeFilterListeners = - new ArrayList<>(); - private final List<OnBeforeRenderListListener> mOnBeforeRenderListListeners = - new ArrayList<>(); + private final NamedListenerSet<OnBeforeTransformGroupsListener> + mOnBeforeTransformGroupsListeners = new NamedListenerSet<>(); + private final NamedListenerSet<OnBeforeSortListener> + mOnBeforeSortListeners = new NamedListenerSet<>(); + private final NamedListenerSet<OnBeforeFinalizeFilterListener> + mOnBeforeFinalizeFilterListeners = new NamedListenerSet<>(); + private final NamedListenerSet<OnBeforeRenderListListener> + mOnBeforeRenderListListeners = new NamedListenerSet<>(); @Nullable private OnRenderListListener mOnRenderListListener; private List<ListEntry> mReadOnlyNotifList = Collections.unmodifiableList(mNotifList); @@ -184,28 +185,28 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { Assert.isMainThread(); mPipelineState.requireState(STATE_IDLE); - mOnBeforeTransformGroupsListeners.add(listener); + mOnBeforeTransformGroupsListeners.addIfAbsent(listener); } void addOnBeforeSortListener(OnBeforeSortListener listener) { Assert.isMainThread(); mPipelineState.requireState(STATE_IDLE); - mOnBeforeSortListeners.add(listener); + mOnBeforeSortListeners.addIfAbsent(listener); } void addOnBeforeFinalizeFilterListener(OnBeforeFinalizeFilterListener listener) { Assert.isMainThread(); mPipelineState.requireState(STATE_IDLE); - mOnBeforeFinalizeFilterListeners.add(listener); + mOnBeforeFinalizeFilterListeners.addIfAbsent(listener); } void addOnBeforeRenderListListener(OnBeforeRenderListListener listener) { Assert.isMainThread(); mPipelineState.requireState(STATE_IDLE); - mOnBeforeRenderListListeners.add(listener); + mOnBeforeRenderListListeners.addIfAbsent(listener); } void addPreRenderInvalidator(Invalidator invalidator) { @@ -496,7 +497,9 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { mTempSectionMembers.add(entry); } } + Trace.beginSection(section.getLabel()); section.getSectioner().onEntriesUpdated(mTempSectionMembers); + Trace.endSection(); mTempSectionMembers.clear(); } Trace.endSection(); @@ -1430,33 +1433,33 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { private void dispatchOnBeforeTransformGroups(List<ListEntry> entries) { Trace.beginSection("ShadeListBuilder.dispatchOnBeforeTransformGroups"); - for (int i = 0; i < mOnBeforeTransformGroupsListeners.size(); i++) { - mOnBeforeTransformGroupsListeners.get(i).onBeforeTransformGroups(entries); - } + mOnBeforeTransformGroupsListeners.forEachTraced(listener -> { + listener.onBeforeTransformGroups(entries); + }); Trace.endSection(); } private void dispatchOnBeforeSort(List<ListEntry> entries) { Trace.beginSection("ShadeListBuilder.dispatchOnBeforeSort"); - for (int i = 0; i < mOnBeforeSortListeners.size(); i++) { - mOnBeforeSortListeners.get(i).onBeforeSort(entries); - } + mOnBeforeSortListeners.forEachTraced(listener -> { + listener.onBeforeSort(entries); + }); Trace.endSection(); } private void dispatchOnBeforeFinalizeFilter(List<ListEntry> entries) { Trace.beginSection("ShadeListBuilder.dispatchOnBeforeFinalizeFilter"); - for (int i = 0; i < mOnBeforeFinalizeFilterListeners.size(); i++) { - mOnBeforeFinalizeFilterListeners.get(i).onBeforeFinalizeFilter(entries); - } + mOnBeforeFinalizeFilterListeners.forEachTraced(listener -> { + listener.onBeforeFinalizeFilter(entries); + }); Trace.endSection(); } private void dispatchOnBeforeRenderList(List<ListEntry> entries) { Trace.beginSection("ShadeListBuilder.dispatchOnBeforeRenderList"); - for (int i = 0; i < mOnBeforeRenderListListeners.size(); i++) { - mOnBeforeRenderListListeners.get(i).onBeforeRenderList(entries); - } + mOnBeforeRenderListListeners.forEachTraced(listener -> { + listener.onBeforeRenderList(entries); + }); Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java index 6500ff7fa210..73decfc326a4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java @@ -23,6 +23,7 @@ import static java.util.Objects.requireNonNull; import android.annotation.IntDef; import android.os.RemoteException; +import android.os.Trace; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; import android.util.ArraySet; @@ -342,11 +343,13 @@ public class PreparationCoordinator implements Coordinator { private void inflateEntry(NotificationEntry entry, NotifUiAdjustment newAdjustment, String reason) { + Trace.beginSection("PrepCoord.inflateEntry"); abortInflation(entry, reason); mInflationAdjustments.put(entry, newAdjustment); mInflatingNotifs.add(entry); NotifInflater.Params params = getInflaterParams(newAdjustment, reason); mNotifInflater.inflateViews(entry, params, this::onInflationFinished); + Trace.endSection(); } private void rebind(NotificationEntry entry, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt index e20f0e50af6d..e06e2d014329 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt @@ -22,6 +22,8 @@ import android.service.notification.NotificationListenerService.RankingMap import android.service.notification.StatusBarNotification import com.android.systemui.statusbar.notification.collection.NotifCollection import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.util.NamedListenerSet +import com.android.systemui.util.traceSection /** * Set of classes that represent the various events that [NotifCollection] can dispatch to @@ -30,10 +32,10 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry * These events build up in a queue and are periodically emitted in chunks by the collection. */ -sealed class NotifEvent { - fun dispatchTo(listeners: List<NotifCollectionListener>) { - for (i in listeners.indices) { - dispatchToListener(listeners[i]) +sealed class NotifEvent(private val traceName: String) { + fun dispatchTo(listeners: NamedListenerSet<NotifCollectionListener>) { + traceSection(traceName) { + listeners.forEachTraced(::dispatchToListener) } } @@ -43,7 +45,7 @@ sealed class NotifEvent { data class BindEntryEvent( val entry: NotificationEntry, val sbn: StatusBarNotification -) : NotifEvent() { +) : NotifEvent("onEntryBind") { override fun dispatchToListener(listener: NotifCollectionListener) { listener.onEntryBind(entry, sbn) } @@ -51,7 +53,7 @@ data class BindEntryEvent( data class InitEntryEvent( val entry: NotificationEntry -) : NotifEvent() { +) : NotifEvent("onEntryInit") { override fun dispatchToListener(listener: NotifCollectionListener) { listener.onEntryInit(entry) } @@ -59,7 +61,7 @@ data class InitEntryEvent( data class EntryAddedEvent( val entry: NotificationEntry -) : NotifEvent() { +) : NotifEvent("onEntryAdded") { override fun dispatchToListener(listener: NotifCollectionListener) { listener.onEntryAdded(entry) } @@ -68,7 +70,7 @@ data class EntryAddedEvent( data class EntryUpdatedEvent( val entry: NotificationEntry, val fromSystem: Boolean -) : NotifEvent() { +) : NotifEvent(if (fromSystem) "onEntryUpdated" else "onEntryUpdated fromSystem=true") { override fun dispatchToListener(listener: NotifCollectionListener) { listener.onEntryUpdated(entry, fromSystem) } @@ -77,7 +79,7 @@ data class EntryUpdatedEvent( data class EntryRemovedEvent( val entry: NotificationEntry, val reason: Int -) : NotifEvent() { +) : NotifEvent("onEntryRemoved ${cancellationReasonDebugString(reason)}") { override fun dispatchToListener(listener: NotifCollectionListener) { listener.onEntryRemoved(entry, reason) } @@ -85,7 +87,7 @@ data class EntryRemovedEvent( data class CleanUpEntryEvent( val entry: NotificationEntry -) : NotifEvent() { +) : NotifEvent("onEntryCleanUp") { override fun dispatchToListener(listener: NotifCollectionListener) { listener.onEntryCleanUp(entry) } @@ -93,13 +95,13 @@ data class CleanUpEntryEvent( data class RankingUpdatedEvent( val rankingMap: RankingMap -) : NotifEvent() { +) : NotifEvent("onRankingUpdate") { override fun dispatchToListener(listener: NotifCollectionListener) { listener.onRankingUpdate(rankingMap) } } -class RankingAppliedEvent() : NotifEvent() { +class RankingAppliedEvent : NotifEvent("onRankingApplied") { override fun dispatchToListener(listener: NotifCollectionListener) { listener.onRankingApplied() } @@ -110,7 +112,7 @@ data class ChannelChangedEvent( val user: UserHandle, val channel: NotificationChannel, val modificationType: Int -) : NotifEvent() { +) : NotifEvent("onNotificationChannelModified") { override fun dispatchToListener(listener: NotifCollectionListener) { listener.onNotificationChannelModified(pkgName, user, channel, modificationType) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt index fd5bae151550..c873e6ad36d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt @@ -26,7 +26,6 @@ import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.util.Assert import com.android.systemui.util.ListenerSet -import com.android.systemui.util.isNotEmpty import java.io.PrintWriter import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt index d8965418b4c4..9d953423ec28 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt @@ -33,6 +33,7 @@ import com.android.systemui.statusbar.notification.InflationException import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener +import com.android.systemui.util.traceSection import javax.inject.Inject /** @@ -95,7 +96,7 @@ class IconManager @Inject constructor( * @throws InflationException Exception if required icons are not valid or specified */ @Throws(InflationException::class) - fun createIcons(entry: NotificationEntry) { + fun createIcons(entry: NotificationEntry) = traceSection("IconManager.createIcons") { // Construct the status bar icon view. val sbIcon = iconBuilder.createIconView(entry) sbIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE @@ -143,9 +144,9 @@ class IconManager @Inject constructor( * @throws InflationException Exception if required icons are not valid or specified */ @Throws(InflationException::class) - fun updateIcons(entry: NotificationEntry) { + fun updateIcons(entry: NotificationEntry) = traceSection("IconManager.updateIcons") { if (!entry.icons.areIconsAvailable) { - return + return@traceSection } entry.icons.smallIconDescriptor = null entry.icons.peopleAvatarDescriptor = null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 5e7e4be60104..1b790fdc35c1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -321,7 +321,8 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView protected void setBackgroundTintColor(int color) { if (color != mCurrentBackgroundTint) { mCurrentBackgroundTint = color; - if (color == mNormalColor) { + // TODO(282173943): re-enable this tinting optimization when Resources are thread-safe + if (false && color == mNormalColor) { // We don't need to tint a normal notification color = 0; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index a4e8c2ece894..80f5d1939ac0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -21,12 +21,16 @@ import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENAB import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.notification.NotificationUtils.logKey; +import android.net.Uri; +import android.os.UserHandle; +import android.provider.Settings; import android.util.Log; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.IStatusBarService; @@ -71,6 +75,10 @@ import javax.inject.Named; @NotificationRowScope public class ExpandableNotificationRowController implements NotifViewController { private static final String TAG = "NotifRowController"; + + static final Uri BUBBLES_SETTING_URI = + Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BUBBLES); + private static final String BUBBLES_SETTING_ENABLED_VALUE = "1"; private final ExpandableNotificationRow mView; private final NotificationListContainer mListContainer; private final RemoteInputViewSubcomponent.Factory mRemoteInputViewSubcomponentFactory; @@ -104,6 +112,23 @@ public class ExpandableNotificationRowController implements NotifViewController private final ExpandableNotificationRowDragController mDragController; private final NotificationDismissibilityProvider mDismissibilityProvider; private final IStatusBarService mStatusBarService; + + private final NotificationSettingsController mSettingsController; + + @VisibleForTesting + final NotificationSettingsController.Listener mSettingsListener = + new NotificationSettingsController.Listener() { + @Override + public void onSettingChanged(Uri setting, int userId, String value) { + if (BUBBLES_SETTING_URI.equals(setting)) { + final int viewUserId = mView.getEntry().getSbn().getUserId(); + if (viewUserId == UserHandle.USER_ALL || viewUserId == userId) { + mView.getPrivateLayout().setBubblesEnabledForUser( + BUBBLES_SETTING_ENABLED_VALUE.equals(value)); + } + } + } + }; private final ExpandableNotificationRow.ExpandableNotificationRowLogger mLoggerCallback = new ExpandableNotificationRow.ExpandableNotificationRowLogger() { @Override @@ -201,6 +226,7 @@ public class ExpandableNotificationRowController implements NotifViewController FeatureFlags featureFlags, PeopleNotificationIdentifier peopleNotificationIdentifier, Optional<BubblesManager> bubblesManagerOptional, + NotificationSettingsController settingsController, ExpandableNotificationRowDragController dragController, NotificationDismissibilityProvider dismissibilityProvider, IStatusBarService statusBarService) { @@ -229,6 +255,7 @@ public class ExpandableNotificationRowController implements NotifViewController mFeatureFlags = featureFlags; mPeopleNotificationIdentifier = peopleNotificationIdentifier; mBubblesManagerOptional = bubblesManagerOptional; + mSettingsController = settingsController; mDragController = dragController; mMetricsLogger = metricsLogger; mChildrenContainerLogger = childrenContainerLogger; @@ -298,12 +325,14 @@ public class ExpandableNotificationRowController implements NotifViewController NotificationMenuRowPlugin.class, false /* Allow multiple */); mView.setOnKeyguard(mStatusBarStateController.getState() == KEYGUARD); mStatusBarStateController.addCallback(mStatusBarStateListener); + mSettingsController.addCallback(BUBBLES_SETTING_URI, mSettingsListener); } @Override public void onViewDetachedFromWindow(View v) { mPluginManager.removePluginListener(mView); mStatusBarStateController.removeCallback(mStatusBarStateListener); + mSettingsController.removeCallback(BUBBLES_SETTING_URI, mSettingsListener); } }); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java index 6bbeebfdb431..0989df61a5e3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java @@ -16,11 +16,15 @@ package com.android.systemui.statusbar.notification.row; +import static android.graphics.PorterDuff.Mode.SRC_ATOP; + import android.annotation.ColorInt; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.ColorFilter; +import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.IndentingPrintWriter; @@ -157,10 +161,20 @@ public class FooterView extends StackScrollerDecorView { */ public void updateColors() { Resources.Theme theme = mContext.getTheme(); - int textColor = getResources().getColor(R.color.notif_pill_text, theme); - mClearAllButton.setBackground(theme.getDrawable(R.drawable.notif_footer_btn_background)); + final @ColorInt int textColor = getResources().getColor(R.color.notif_pill_text, theme); + final Drawable clearAllBg = theme.getDrawable(R.drawable.notif_footer_btn_background); + final Drawable manageBg = theme.getDrawable(R.drawable.notif_footer_btn_background); + // TODO(b/282173943): Remove redundant tinting once Resources are thread-safe + final @ColorInt int buttonBgColor = + Utils.getColorAttrDefaultColor(mContext, com.android.internal.R.attr.colorSurface); + final ColorFilter bgColorFilter = new PorterDuffColorFilter(buttonBgColor, SRC_ATOP); + if (buttonBgColor != 0) { + clearAllBg.setColorFilter(bgColorFilter); + manageBg.setColorFilter(bgColorFilter); + } + mClearAllButton.setBackground(clearAllBg); mClearAllButton.setTextColor(textColor); - mManageButton.setBackground(theme.getDrawable(R.drawable.notif_footer_btn_background)); + mManageButton.setBackground(manageBg); mManageButton.setTextColor(textColor); final @ColorInt int labelTextColor = Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 03225730619e..065828bee746 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -41,6 +41,8 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; +import androidx.annotation.MainThread; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.R; @@ -65,7 +67,6 @@ import com.android.systemui.statusbar.policy.SmartReplyStateInflaterKt; import com.android.systemui.statusbar.policy.SmartReplyView; import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent; import com.android.systemui.util.Compile; -import com.android.systemui.wmshell.BubblesManager; import java.io.PrintWriter; import java.util.ArrayList; @@ -134,6 +135,7 @@ public class NotificationContentView extends FrameLayout implements Notification private PeopleNotificationIdentifier mPeopleIdentifier; private RemoteInputViewSubcomponent.Factory mRemoteInputSubcomponentFactory; private IStatusBarService mStatusBarService; + private boolean mBubblesEnabledForUser; /** * List of listeners for when content views become inactive (i.e. not the showing view). @@ -1440,12 +1442,20 @@ public class NotificationContentView extends FrameLayout implements Notification } } + @MainThread + public void setBubblesEnabledForUser(boolean enabled) { + mBubblesEnabledForUser = enabled; + + applyBubbleAction(mExpandedChild, mNotificationEntry); + applyBubbleAction(mHeadsUpChild, mNotificationEntry); + } + @VisibleForTesting boolean shouldShowBubbleButton(NotificationEntry entry) { boolean isPersonWithShortcut = mPeopleIdentifier.getPeopleNotificationType(entry) >= PeopleNotificationIdentifier.TYPE_FULL_PERSON; - return BubblesManager.areBubblesEnabled(mContext, entry.getSbn().getUser()) + return mBubblesEnabledForUser && isPersonWithShortcut && entry.getBubbleMetadata() != null; } @@ -2079,6 +2089,7 @@ public class NotificationContentView extends FrameLayout implements Notification pw.print("null"); } pw.println(); + pw.println("mBubblesEnabledForUser: " + mBubblesEnabledForUser); pw.print("RemoteInputViews { "); pw.print(" visibleType: " + mVisibleType); 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 9bc03336c3b0..7134f15d4fec 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 @@ -48,6 +48,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; import android.os.UserHandle; +import android.os.UserManager; import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.transition.ChangeBounds; @@ -118,6 +119,8 @@ public class NotificationConversationInfo extends LinearLayout implements private NotificationGuts mGutsContainer; private OnConversationSettingsClickListener mOnConversationSettingsClickListener; + private UserManager mUm; + @VisibleForTesting boolean mSkipPost = false; private int mActualHeight; @@ -155,7 +158,9 @@ public class NotificationConversationInfo extends LinearLayout implements // People Tile add request. if (mSelectedAction == ACTION_FAVORITE && getPriority() != mSelectedAction) { mShadeController.animateCollapseShade(); - mPeopleSpaceWidgetManager.requestPinAppWidget(mShortcutInfo, new Bundle()); + if (mUm.isSameProfileGroup(UserHandle.USER_SYSTEM, mSbn.getNormalizedUserId())) { + mPeopleSpaceWidgetManager.requestPinAppWidget(mShortcutInfo, new Bundle()); + } } mGutsContainer.closeControls(v, /* save= */ true); }; @@ -188,6 +193,7 @@ public class NotificationConversationInfo extends LinearLayout implements public void bindNotification( ShortcutManager shortcutManager, PackageManager pm, + UserManager um, PeopleSpaceWidgetManager peopleSpaceWidgetManager, INotificationManager iNotificationManager, OnUserInteractionCallback onUserInteractionCallback, @@ -211,6 +217,7 @@ public class NotificationConversationInfo extends LinearLayout implements mEntry = entry; mSbn = entry.getSbn(); mPm = pm; + mUm = um; mAppName = mPackageName; mOnSettingsClickListener = onSettingsClick; mNotificationChannel = notificationChannel; 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 7dbca4276fcc..6f79ea8c543b 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 @@ -30,6 +30,7 @@ import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.UserHandle; +import android.os.UserManager; import android.provider.Settings; import android.service.notification.StatusBarNotification; import android.util.ArraySet; @@ -112,6 +113,9 @@ public class NotificationGutsManager implements NotifGutsViewManager { private Runnable mOpenRunnable; private final INotificationManager mNotificationManager; private final PeopleSpaceWidgetManager mPeopleSpaceWidgetManager; + + private final UserManager mUserManager; + private final LauncherApps mLauncherApps; private final ShortcutManager mShortcutManager; private final UserContextProvider mContextTracker; @@ -128,6 +132,7 @@ public class NotificationGutsManager implements NotifGutsViewManager { AccessibilityManager accessibilityManager, HighPriorityProvider highPriorityProvider, INotificationManager notificationManager, + UserManager userManager, PeopleSpaceWidgetManager peopleSpaceWidgetManager, LauncherApps launcherApps, ShortcutManager shortcutManager, @@ -150,6 +155,7 @@ public class NotificationGutsManager implements NotifGutsViewManager { mAccessibilityManager = accessibilityManager; mHighPriorityProvider = highPriorityProvider; mNotificationManager = notificationManager; + mUserManager = userManager; mPeopleSpaceWidgetManager = peopleSpaceWidgetManager; mLauncherApps = launcherApps; mShortcutManager = shortcutManager; @@ -471,6 +477,7 @@ public class NotificationGutsManager implements NotifGutsViewManager { notificationInfoView.bindNotification( mShortcutManager, pmUser, + mUserManager, mPeopleSpaceWidgetManager, mNotificationManager, mOnUserInteractionCallback, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java new file mode 100644 index 000000000000..51e4537d7348 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2023 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.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.HandlerExecutor; + +import androidx.annotation.MainThread; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.systemui.Dumpable; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.settings.UserTracker; +import com.android.systemui.util.settings.SecureSettings; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; + +import javax.inject.Inject; + +/** + * Centralized controller for listening to Secure Settings changes and informing in-process + * listeners, on a background thread. + */ +@SysUISingleton +public class NotificationSettingsController implements Dumpable { + + private final static String TAG = "NotificationSettingsController"; + private final UserTracker mUserTracker; + private final UserTracker.Callback mCurrentUserTrackerCallback; + private final Handler mMainHandler; + private final Handler mBackgroundHandler; + private final ContentObserver mContentObserver; + private final SecureSettings mSecureSettings; + private final HashMap<Uri, ArrayList<Listener>> mListeners = new HashMap<>(); + + @Inject + public NotificationSettingsController(UserTracker userTracker, + @Main Handler mainHandler, + @Background Handler backgroundHandler, + SecureSettings secureSettings, + DumpManager dumpManager) { + mUserTracker = userTracker; + mMainHandler = mainHandler; + mBackgroundHandler = backgroundHandler; + mSecureSettings = secureSettings; + mContentObserver = new ContentObserver(mBackgroundHandler) { + @Override + public void onChange(boolean selfChange, Uri uri) { + super.onChange(selfChange, uri); + synchronized (mListeners) { + if (mListeners.containsKey(uri)) { + int userId = mUserTracker.getUserId(); + String value = getCurrentSettingValue(uri, userId); + for (Listener listener : mListeners.get(uri)) { + mMainHandler.post(() -> listener.onSettingChanged(uri, userId, value)); + } + } + } + } + }; + + mCurrentUserTrackerCallback = new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, Context userContext) { + synchronized (mListeners) { + if (mListeners.size() > 0) { + mSecureSettings.unregisterContentObserver(mContentObserver); + for (Uri uri : mListeners.keySet()) { + mSecureSettings.registerContentObserverForUser( + uri, false, mContentObserver, newUser); + } + } + } + } + }; + mUserTracker.addCallback( + mCurrentUserTrackerCallback, new HandlerExecutor(mBackgroundHandler)); + + dumpManager.registerNormalDumpable(TAG, this); + } + + /** + * Register a callback whenever the given secure settings changes. + * + * On registration, will trigger the listener on the main thread with the current value of + * the setting. + */ + @Main + public void addCallback(@NonNull Uri uri, @NonNull Listener listener) { + if (uri == null || listener == null) { + return; + } + synchronized (mListeners) { + ArrayList<Listener> currentListeners = mListeners.get(uri); + if (currentListeners == null) { + currentListeners = new ArrayList<>(); + } + if (!currentListeners.contains(listener)) { + currentListeners.add(listener); + } + mListeners.put(uri, currentListeners); + if (currentListeners.size() == 1) { + mSecureSettings.registerContentObserverForUser( + uri, false, mContentObserver, mUserTracker.getUserId()); + } + } + mBackgroundHandler.post(() -> { + int userId = mUserTracker.getUserId(); + String value = getCurrentSettingValue(uri, userId); + mMainHandler.post(() -> listener.onSettingChanged(uri, userId, value)); + }); + + } + + public void removeCallback(Uri uri, Listener listener) { + synchronized (mListeners) { + ArrayList<Listener> currentListeners = mListeners.get(uri); + + if (currentListeners != null) { + currentListeners.remove(listener); + } + if (currentListeners == null || currentListeners.size() == 0) { + mListeners.remove(uri); + } + + if (mListeners.size() == 0) { + mSecureSettings.unregisterContentObserver(mContentObserver); + } + } + } + + @Override + public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { + synchronized (mListeners) { + pw.println("Settings Uri Listener List:"); + for (Uri uri : mListeners.keySet()) { + pw.println(" Uri=" + uri); + for (Listener listener : mListeners.get(uri)) { + pw.println(" Listener=" + listener.getClass().getName()); + } + } + } + } + + private String getCurrentSettingValue(Uri uri, int userId) { + final String setting = uri == null ? null : uri.getLastPathSegment(); + return mSecureSettings.getStringForUser(setting, userId); + } + + /** + * Listener invoked whenever settings are changed. + */ + public interface Listener { + @MainThread + void onSettingChanged(@NonNull Uri setting, int userId, @Nullable String value); + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt index dcd18dd7d1bf..2ccbc9f2f017 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt @@ -401,7 +401,7 @@ constructor( isActivityIntent: Boolean, showOverLockscreen: Boolean, ): Boolean { - // TODO(b/184121838): Support launch animations when occluded. + // TODO(b/294418322): Support launch animations when occluded. if (keyguardStateController.isOccluded) { return false } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt index 3a11635f75c3..c1af6df12bd1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt @@ -118,6 +118,11 @@ interface MobileConnectionRepository { /** The service provider name for this network connection, or the default name */ val networkName: StateFlow<NetworkNameModel> + /** + * True if this type of connection is allowed while airplane mode is on, and false otherwise. + */ + val isAllowedDuringAirplaneMode: StateFlow<Boolean> + companion object { /** The default number of levels to use for [numberOfLevels]. */ const val DEFAULT_NUM_LEVELS = 4 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt index 6b86432b8171..17d20c297861 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt @@ -186,6 +186,8 @@ class DemoMobileConnectionRepository( override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo network")) + override val isAllowedDuringAirplaneMode = MutableStateFlow(false) + /** * Process a new demo mobile event. Note that [resolvedNetworkType] must be passed in separately * from the event, due to the requirement to reverse the mobile mappings lookup in the top-level @@ -217,6 +219,8 @@ class DemoMobileConnectionRepository( (event.activity ?: TelephonyManager.DATA_ACTIVITY_NONE).toMobileDataActivityModel() _carrierNetworkChangeActive.value = event.carrierNetworkChange _resolvedNetworkType.value = resolvedNetworkType + + isAllowedDuringAirplaneMode.value = false } fun processCarrierMergedEvent(event: FakeWifiEventModel.CarrierMerged) { @@ -240,6 +244,7 @@ class DemoMobileConnectionRepository( _isInService.value = true _isGsm.value = false _carrierNetworkChangeActive.value = false + isAllowedDuringAirplaneMode.value = true } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt index a609917351d9..65f486683837 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt @@ -165,6 +165,13 @@ class CarrierMergedConnectionRepository( override val isGsm = MutableStateFlow(false).asStateFlow() override val carrierNetworkChangeActive = MutableStateFlow(false).asStateFlow() + /** + * Carrier merged connections happen over wifi but are displayed as a mobile triangle. Because + * they occur over wifi, it's possible to have a valid carrier merged connection even during + * airplane mode. See b/291993542. + */ + override val isAllowedDuringAirplaneMode = MutableStateFlow(true).asStateFlow() + override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled companion object { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt index 8869dfe02697..8ba7d2197c14 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt @@ -287,6 +287,15 @@ class FullMobileConnectionRepository( ) .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.networkName.value) + override val isAllowedDuringAirplaneMode = + activeRepo + .flatMapLatest { it.isAllowedDuringAirplaneMode } + .stateIn( + scope, + SharingStarted.WhileSubscribed(), + activeRepo.value.isAllowedDuringAirplaneMode.value, + ) + class Factory @Inject constructor( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt index b475183d98c6..aadc975a10de 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt @@ -59,8 +59,10 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.asExecutor import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map @@ -331,6 +333,9 @@ class MobileConnectionRepositoryImpl( .stateIn(scope, SharingStarted.WhileSubscribed(), initial) } + /** Typical mobile connections aren't available during airplane mode. */ + override val isAllowedDuringAirplaneMode = MutableStateFlow(false).asStateFlow() + class Factory @Inject constructor( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt index d42e30c9b1b9..1a138272d67c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt @@ -111,6 +111,9 @@ interface MobileIconInteractor { /** See [MobileIconsInteractor.isForceHidden]. */ val isForceHidden: Flow<Boolean> + /** See [MobileConnectionRepository.isAllowedDuringAirplaneMode]. */ + val isAllowedDuringAirplaneMode: StateFlow<Boolean> + /** True when in carrier network change mode */ val carrierNetworkChangeActive: StateFlow<Boolean> } @@ -267,4 +270,6 @@ class MobileIconInteractorImpl( .stateIn(scope, SharingStarted.WhileSubscribed(), false) override val isInService = connectionRepository.isInService + + override val isAllowedDuringAirplaneMode = connectionRepository.isAllowedDuringAirplaneMode } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt index 35f4f9aa4622..fe2481595ff4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt @@ -102,9 +102,16 @@ constructor( } else { combine( airplaneModeInteractor.isAirplaneMode, + iconInteractor.isAllowedDuringAirplaneMode, iconInteractor.isForceHidden, - ) { isAirplaneMode, isForceHidden -> - !isAirplaneMode && !isForceHidden + ) { isAirplaneMode, isAllowedDuringAirplaneMode, isForceHidden -> + if (isForceHidden) { + false + } else if (isAirplaneMode) { + isAllowedDuringAirplaneMode + } else { + true + } } } .distinctUntilChanged() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt index b11b4727c3c3..b29d46174bd1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt @@ -49,6 +49,9 @@ interface WifiRepository { const val COL_NAME_IS_ENABLED = "isEnabled" /** Column name to use for [isWifiDefault] for table logging. */ const val COL_NAME_IS_DEFAULT = "isDefault" + + const val CARRIER_MERGED_INVALID_SUB_ID_REASON = + "Wifi network was carrier merged but had invalid sub ID" } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt index 7d2501ca0e79..ab9b516b837f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt @@ -24,6 +24,7 @@ import com.android.systemui.demomode.DemoMode.COMMAND_NETWORK import com.android.systemui.demomode.DemoModeController import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted @@ -56,12 +57,14 @@ constructor( val activity = getString("activity").toActivity() val ssid = getString("ssid") val validated = getString("fully").toBoolean() + val hotspotDeviceType = getString("hotspot").toHotspotDeviceType() return FakeWifiEventModel.Wifi( level = level, activity = activity, ssid = ssid, validated = validated, + hotspotDeviceType, ) } @@ -82,6 +85,20 @@ constructor( else -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE } + private fun String?.toHotspotDeviceType(): WifiNetworkModel.HotspotDeviceType { + return when (this) { + null, + "none" -> WifiNetworkModel.HotspotDeviceType.NONE + "unknown" -> WifiNetworkModel.HotspotDeviceType.UNKNOWN + "phone" -> WifiNetworkModel.HotspotDeviceType.PHONE + "tablet" -> WifiNetworkModel.HotspotDeviceType.TABLET + "laptop" -> WifiNetworkModel.HotspotDeviceType.LAPTOP + "watch" -> WifiNetworkModel.HotspotDeviceType.WATCH + "auto" -> WifiNetworkModel.HotspotDeviceType.AUTO + else -> WifiNetworkModel.HotspotDeviceType.INVALID + } + } + companion object { const val DEFAULT_CARRIER_MERGED_SUB_ID = 10 } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt index a57be665f105..99b680056d7f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt @@ -97,6 +97,7 @@ constructor( isValidated = validated ?: true, level = level ?: 0, ssid = ssid ?: DEMO_NET_SSID, + hotspotDeviceType = hotspotDeviceType, // These fields below aren't supported in demo mode, since they aren't needed to satisfy // the interface. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt index f5035cbc0215..b2e843e283f7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model import android.telephony.Annotation +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel /** * Model for demo wifi commands, ported from [NetworkControllerImpl] @@ -29,6 +30,8 @@ sealed interface FakeWifiEventModel { @Annotation.DataActivityType val activity: Int, val ssid: String?, val validated: Boolean?, + val hotspotDeviceType: WifiNetworkModel.HotspotDeviceType = + WifiNetworkModel.HotspotDeviceType.NONE, ) : FakeWifiEventModel data class CarrierMerged( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt new file mode 100644 index 000000000000..f1b98b3972e1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2023 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.pipeline.wifi.data.repository.prod + +import android.net.wifi.WifiManager +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable +import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel +import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel +import java.util.concurrent.Executor +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn + +/** + * Object to provide shared helper functions between [WifiRepositoryImpl] and + * [WifiRepositoryViaTrackerLib]. + */ +object WifiRepositoryHelper { + /** Creates a flow that fetches the [DataActivityModel] from [WifiManager]. */ + fun createActivityFlow( + wifiManager: WifiManager, + @Main mainExecutor: Executor, + scope: CoroutineScope, + tableLogBuffer: TableLogBuffer, + inputLogger: (String) -> Unit, + ): StateFlow<DataActivityModel> { + return conflatedCallbackFlow { + val callback = + WifiManager.TrafficStateCallback { state -> + inputLogger.invoke(prettyPrintActivity(state)) + trySend(state.toWifiDataActivityModel()) + } + wifiManager.registerTrafficStateCallback(mainExecutor, callback) + awaitClose { wifiManager.unregisterTrafficStateCallback(callback) } + } + .logDiffsForTable( + tableLogBuffer, + columnPrefix = ACTIVITY_PREFIX, + initialValue = ACTIVITY_DEFAULT, + ) + .stateIn( + scope, + started = SharingStarted.WhileSubscribed(), + initialValue = ACTIVITY_DEFAULT, + ) + } + + // TODO(b/292534484): This print should only be done in [MessagePrinter] part of the log buffer. + private fun prettyPrintActivity(activity: Int): String { + return when (activity) { + WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE" + WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN -> "IN" + WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT" + WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT" + else -> "INVALID" + } + } + + private const val ACTIVITY_PREFIX = "wifiActivity" + val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt index 995de6d2fc61..afd15765d163 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt @@ -28,7 +28,6 @@ import android.net.NetworkCapabilities.TRANSPORT_WIFI import android.net.NetworkRequest import android.net.wifi.WifiInfo import android.net.wifi.WifiManager -import android.net.wifi.WifiManager.TrafficStateCallback import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow @@ -40,10 +39,10 @@ import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel -import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.CARRIER_MERGED_INVALID_SUB_ID_REASON import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_DEFAULT import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_ENABLED import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger @@ -218,29 +217,15 @@ constructor( ) override val wifiActivity: StateFlow<DataActivityModel> = - conflatedCallbackFlow { - val callback = TrafficStateCallback { state -> - logger.logActivity(prettyPrintActivity(state)) - trySend(state.toWifiDataActivityModel()) - } - wifiManager.registerTrafficStateCallback(mainExecutor, callback) - awaitClose { wifiManager.unregisterTrafficStateCallback(callback) } - } - .logDiffsForTable( - wifiTableLogBuffer, - columnPrefix = ACTIVITY_PREFIX, - initialValue = ACTIVITY_DEFAULT, - ) - .stateIn( - scope, - started = SharingStarted.WhileSubscribed(), - initialValue = ACTIVITY_DEFAULT, - ) + WifiRepositoryHelper.createActivityFlow( + wifiManager, + mainExecutor, + scope, + wifiTableLogBuffer, + logger::logActivity, + ) companion object { - private const val ACTIVITY_PREFIX = "wifiActivity" - - val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false) // Start out with no known wifi network. // Note: [WifiStatusTracker] (the old implementation of connectivity logic) does do an // initial fetch to get a starting wifi network. But, it uses a deprecated API @@ -277,6 +262,8 @@ constructor( isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED), level = wifiManager.calculateSignalLevel(wifiInfo.rssi), wifiInfo.ssid, + // This repository doesn't support any hotspot information. + WifiNetworkModel.HotspotDeviceType.NONE, wifiInfo.isPasspointAp, wifiInfo.isOsuAp, wifiInfo.passpointProviderFriendlyName @@ -284,16 +271,6 @@ constructor( } } - private fun prettyPrintActivity(activity: Int): String { - return when (activity) { - TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE" - TrafficStateCallback.DATA_ACTIVITY_IN -> "IN" - TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT" - TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT" - else -> "INVALID" - } - } - private val WIFI_NETWORK_CALLBACK_REQUEST: NetworkRequest = NetworkRequest.Builder() .clearCapabilities() @@ -301,9 +278,6 @@ constructor( .addTransportType(TRANSPORT_WIFI) .addTransportType(TRANSPORT_CELLULAR) .build() - - private const val CARRIER_MERGED_INVALID_SUB_ID_REASON = - "Wifi network was carrier merged but had invalid sub ID" } @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt index 127136789ba6..175563bb0764 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt @@ -17,12 +17,15 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod import android.net.wifi.WifiManager +import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel import com.android.systemui.log.table.TableLogBuffer @@ -31,20 +34,25 @@ import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory import com.android.systemui.statusbar.pipeline.dagger.WifiTrackerLibInputLog import com.android.systemui.statusbar.pipeline.dagger.WifiTrackerLibTableLog import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.CARRIER_MERGED_INVALID_SUB_ID_REASON import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_DEFAULT import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_ENABLED import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_STATE_DEFAULT import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive.toHotspotDeviceType +import com.android.wifitrackerlib.HotspotNetworkEntry import com.android.wifitrackerlib.MergedCarrierEntry import com.android.wifitrackerlib.WifiEntry +import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX +import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN +import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE import com.android.wifitrackerlib.WifiPickerTracker import java.util.concurrent.Executor import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.callbackFlow @@ -62,6 +70,7 @@ import kotlinx.coroutines.flow.stateIn class WifiRepositoryViaTrackerLib @Inject constructor( + featureFlags: FeatureFlags, @Application private val scope: CoroutineScope, @Main private val mainExecutor: Executor, private val wifiPickerTrackerFactory: WifiPickerTrackerFactory, @@ -75,6 +84,8 @@ constructor( mainExecutor.execute { it.currentState = Lifecycle.State.CREATED } } + private val isInstantTetherEnabled = featureFlags.isEnabled(Flags.INSTANT_TETHER) + private var wifiPickerTracker: WifiPickerTracker? = null private val wifiPickerTrackerInfo: StateFlow<WifiPickerTrackerInfo> = run { @@ -128,19 +139,21 @@ constructor( } } - // TODO(b/292591403): [WifiPickerTrackerFactory] currently scans to see all - // available wifi networks every 10s. Because SysUI only needs to display the - // **connected** network, we don't need scans to be running. We should disable these - // scans (ideal) or at least run them very infrequently. - wifiPickerTracker = wifiPickerTrackerFactory.create(lifecycle, callback) + wifiPickerTracker = + wifiPickerTrackerFactory.create(lifecycle, callback).apply { + // By default, [WifiPickerTracker] will scan to see all available wifi + // networks in the area. Because SysUI only needs to display the + // **connected** network, we don't need scans to be running (and in fact, + // running scans is costly and should be avoided whenever possible). + this?.disableScanning() + } // The lifecycle must be STARTED in order for the callback to receive events. mainExecutor.execute { lifecycle.currentState = Lifecycle.State.STARTED } awaitClose { mainExecutor.execute { lifecycle.currentState = Lifecycle.State.CREATED } } } - // TODO(b/292534484): Update to Eagerly once scans are disabled. (Here and other flows) - .stateIn(scope, SharingStarted.WhileSubscribed(), current) + .stateIn(scope, SharingStarted.Eagerly, current) } override val isWifiEnabled: StateFlow<Boolean> = @@ -153,7 +166,7 @@ constructor( columnName = COL_NAME_IS_ENABLED, initialValue = false, ) - .stateIn(scope, SharingStarted.WhileSubscribed(), false) + .stateIn(scope, SharingStarted.Eagerly, false) override val wifiNetwork: StateFlow<WifiNetworkModel> = wifiPickerTrackerInfo @@ -164,7 +177,7 @@ constructor( columnPrefix = "", initialValue = WIFI_NETWORK_DEFAULT, ) - .stateIn(scope, SharingStarted.WhileSubscribed(), WIFI_NETWORK_DEFAULT) + .stateIn(scope, SharingStarted.Eagerly, WIFI_NETWORK_DEFAULT) /** Converts WifiTrackerLib's [WifiEntry] into our internal model. */ private fun WifiEntry.toWifiNetworkModel(): WifiNetworkModel { @@ -172,30 +185,58 @@ constructor( return WIFI_NETWORK_DEFAULT } return if (this is MergedCarrierEntry) { + this.convertCarrierMergedToModel() + } else { + this.convertNormalToModel() + } + } + + private fun MergedCarrierEntry.convertCarrierMergedToModel(): WifiNetworkModel { + return if (this.subscriptionId == INVALID_SUBSCRIPTION_ID) { + WifiNetworkModel.Invalid(CARRIER_MERGED_INVALID_SUB_ID_REASON) + } else { WifiNetworkModel.CarrierMerged( networkId = NETWORK_ID, - // TODO(b/292534484): Fetch the real subscription ID from [MergedCarrierEntry]. - subscriptionId = TEMP_SUB_ID, + subscriptionId = this.subscriptionId, level = this.level, // WifiManager APIs to calculate the signal level start from 0, so // maxSignalLevel + 1 represents the total level buckets count. numberOfLevels = wifiManager.maxSignalLevel + 1, ) - } else { - WifiNetworkModel.Active( - networkId = NETWORK_ID, - isValidated = this.hasInternetAccess(), - level = this.level, - ssid = this.ssid, - // TODO(b/292534484): Fetch the real values from [WifiEntry] (#getTitle might be - // appropriate). - isPasspointAccessPoint = false, - isOnlineSignUpForPasspointAccessPoint = false, - passpointProviderFriendlyName = null, - ) } } + private fun WifiEntry.convertNormalToModel(): WifiNetworkModel { + if (this.level == WIFI_LEVEL_UNREACHABLE || this.level !in WIFI_LEVEL_MIN..WIFI_LEVEL_MAX) { + // If our level means the network is unreachable or the level is otherwise invalid, we + // don't have an active network. + return WifiNetworkModel.Inactive + } + + val hotspotDeviceType = + if (isInstantTetherEnabled && this is HotspotNetworkEntry) { + this.deviceType.toHotspotDeviceType() + } else { + WifiNetworkModel.HotspotDeviceType.NONE + } + + return WifiNetworkModel.Active( + networkId = NETWORK_ID, + isValidated = this.hasInternetAccess(), + level = this.level, + ssid = this.title, + hotspotDeviceType = hotspotDeviceType, + // With WifiTrackerLib, [WifiEntry.title] will appropriately fetch the SSID for + // typical wifi networks *and* passpoint/OSU APs. So, the AP-specific values can + // always be false/null in this repository. + // TODO(b/292534484): Remove these fields from the wifi network model once this + // repository is fully enabled. + isPasspointAccessPoint = false, + isOnlineSignUpForPasspointAccessPoint = false, + passpointProviderFriendlyName = null, + ) + } + override val isWifiDefault: StateFlow<Boolean> = wifiPickerTrackerInfo .map { it.isDefault } @@ -206,12 +247,16 @@ constructor( columnName = COL_NAME_IS_DEFAULT, initialValue = false, ) - .stateIn(scope, SharingStarted.WhileSubscribed(), false) + .stateIn(scope, SharingStarted.Eagerly, false) - // TODO(b/292534484): Re-use WifiRepositoryImpl code to implement wifi activity since - // WifiTrackerLib doesn't expose activity details. override val wifiActivity: StateFlow<DataActivityModel> = - MutableStateFlow(DataActivityModel(false, false)) + WifiRepositoryHelper.createActivityFlow( + wifiManager, + mainExecutor, + scope, + wifiTrackerLibTableLogBuffer, + this::logActivity, + ) private fun logOnWifiEntriesChanged(connectedEntry: WifiEntry?) { inputLogger.log( @@ -231,6 +276,10 @@ constructor( ) } + private fun logActivity(activity: String) { + inputLogger.log(TAG, LogLevel.DEBUG, { str1 = activity }, { "onActivityChanged: $str1" }) + } + /** * Data class storing all the information fetched from [WifiPickerTracker]. * @@ -249,6 +298,7 @@ constructor( class Factory @Inject constructor( + private val featureFlags: FeatureFlags, @Application private val scope: CoroutineScope, @Main private val mainExecutor: Executor, private val wifiPickerTrackerFactory: WifiPickerTrackerFactory, @@ -257,6 +307,7 @@ constructor( ) { fun create(wifiManager: WifiManager): WifiRepositoryViaTrackerLib { return WifiRepositoryViaTrackerLib( + featureFlags, scope, mainExecutor, wifiPickerTrackerFactory, @@ -283,13 +334,5 @@ constructor( * to [WifiRepositoryViaTrackerLib]. */ private const val NETWORK_ID = -1 - - /** - * A temporary subscription ID until WifiTrackerLib exposes a method to fetch the - * subscription ID. - * - * Use -2 because [SubscriptionManager.INVALID_SUBSCRIPTION_ID] is -1. - */ - private const val TEMP_SUB_ID = -2 } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt index 4b33c88cea30..7078a2e1728c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt @@ -17,11 +17,13 @@ package com.android.systemui.statusbar.pipeline.wifi.shared.model import android.net.wifi.WifiManager.UNKNOWN_SSID +import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo import android.telephony.SubscriptionManager import androidx.annotation.VisibleForTesting import com.android.systemui.log.table.Diffable import com.android.systemui.log.table.TableRowLogger import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository +import com.android.wifitrackerlib.HotspotNetworkEntry.DeviceType /** Provides information about the current wifi network. */ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { @@ -52,6 +54,7 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { row.logChange(COL_LEVEL, LEVEL_DEFAULT) row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT) row.logChange(COL_SSID, null) + row.logChange(COL_HOTSPOT, null) row.logChange(COL_PASSPOINT_ACCESS_POINT, false) row.logChange(COL_ONLINE_SIGN_UP, false) row.logChange(COL_PASSPOINT_NAME, null) @@ -83,6 +86,7 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { row.logChange(COL_LEVEL, LEVEL_DEFAULT) row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT) row.logChange(COL_SSID, null) + row.logChange(COL_HOTSPOT, null) row.logChange(COL_PASSPOINT_ACCESS_POINT, false) row.logChange(COL_ONLINE_SIGN_UP, false) row.logChange(COL_PASSPOINT_NAME, null) @@ -110,6 +114,7 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { row.logChange(COL_LEVEL, LEVEL_DEFAULT) row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT) row.logChange(COL_SSID, null) + row.logChange(COL_HOTSPOT, null) row.logChange(COL_PASSPOINT_ACCESS_POINT, false) row.logChange(COL_ONLINE_SIGN_UP, false) row.logChange(COL_PASSPOINT_NAME, null) @@ -184,6 +189,7 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { row.logChange(COL_LEVEL, level) row.logChange(COL_NUM_LEVELS, numberOfLevels) row.logChange(COL_SSID, null) + row.logChange(COL_HOTSPOT, null) row.logChange(COL_PASSPOINT_ACCESS_POINT, false) row.logChange(COL_ONLINE_SIGN_UP, false) row.logChange(COL_PASSPOINT_NAME, null) @@ -209,6 +215,12 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { /** See [android.net.wifi.WifiInfo.ssid]. */ val ssid: String? = null, + /** + * The type of device providing a hotspot connection, or [HotspotDeviceType.NONE] if this + * isn't a hotspot connection. + */ + val hotspotDeviceType: HotspotDeviceType = WifiNetworkModel.HotspotDeviceType.NONE, + /** See [android.net.wifi.WifiInfo.isPasspointAp]. */ val isPasspointAccessPoint: Boolean = false, @@ -247,6 +259,9 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { if (prevVal.ssid != ssid) { row.logChange(COL_SSID, ssid) } + if (prevVal.hotspotDeviceType != hotspotDeviceType) { + row.logChange(COL_HOTSPOT, hotspotDeviceType.name) + } // TODO(b/238425913): The passpoint-related values are frequently never used, so it // would be great to not log them when they're not used. @@ -272,6 +287,7 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { row.logChange(COL_LEVEL, level) row.logChange(COL_NUM_LEVELS, null) row.logChange(COL_SSID, ssid) + row.logChange(COL_HOTSPOT, hotspotDeviceType.name) row.logChange(COL_PASSPOINT_ACCESS_POINT, isPasspointAccessPoint) row.logChange(COL_ONLINE_SIGN_UP, isOnlineSignUpForPasspointAccessPoint) row.logChange(COL_PASSPOINT_NAME, passpointProviderFriendlyName) @@ -298,13 +314,51 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { } companion object { + // TODO(b/292534484): Use [com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX] instead + // once the migration to WifiTrackerLib is complete. @VisibleForTesting internal const val MAX_VALID_LEVEL = 4 } } companion object { + // TODO(b/292534484): Use [com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN] instead + // once the migration to WifiTrackerLib is complete. @VisibleForTesting internal const val MIN_VALID_LEVEL = 0 } + + /** + * Enum for the type of device providing the hotspot connection, or [NONE] if this connection + * isn't a hotspot. + */ + enum class HotspotDeviceType { + /* This wifi connection isn't a hotspot. */ + NONE, + /** The device type for this hotspot is unknown. */ + UNKNOWN, + PHONE, + TABLET, + LAPTOP, + WATCH, + AUTO, + /** The device type sent for this hotspot is invalid to SysUI. */ + INVALID, + } + + /** + * Converts a device type from [com.android.wifitrackerlib.HotspotNetworkEntry.deviceType] to + * our internal representation. + */ + fun @receiver:DeviceType Int.toHotspotDeviceType(): HotspotDeviceType { + return when (this) { + NetworkProviderInfo.DEVICE_TYPE_UNKNOWN -> HotspotDeviceType.UNKNOWN + NetworkProviderInfo.DEVICE_TYPE_PHONE -> HotspotDeviceType.PHONE + NetworkProviderInfo.DEVICE_TYPE_TABLET -> HotspotDeviceType.TABLET + NetworkProviderInfo.DEVICE_TYPE_LAPTOP -> HotspotDeviceType.LAPTOP + NetworkProviderInfo.DEVICE_TYPE_WATCH -> HotspotDeviceType.WATCH + NetworkProviderInfo.DEVICE_TYPE_AUTO -> HotspotDeviceType.AUTO + else -> HotspotDeviceType.INVALID + } + } } const val TYPE_CARRIER_MERGED = "CarrierMerged" @@ -319,6 +373,7 @@ const val COL_VALIDATED = "isValidated" const val COL_LEVEL = "level" const val COL_NUM_LEVELS = "maxLevel" const val COL_SSID = "ssid" +const val COL_HOTSPOT = "hotspot" const val COL_PASSPOINT_ACCESS_POINT = "isPasspointAccessPoint" const val COL_ONLINE_SIGN_UP = "isOnlineSignUpForPasspointAccessPoint" const val COL_PASSPOINT_NAME = "passpointProviderFriendlyName" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java index 7df083afcd19..37eda6490ec2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java @@ -162,6 +162,9 @@ public interface BatteryController extends DemoMode, default void onIsBatteryDefenderChanged(boolean isBatteryDefender) { } + default void onIsIncompatibleChargingChanged(boolean isIncompatibleCharging) { + } + @Override default void dump(@NonNull PrintWriter pw, @NonNull String[] args) { pw.println(this); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java index d5d8f4d7598e..4b515115dd77 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java @@ -29,6 +29,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.hardware.usb.UsbManager; import android.os.BatteryManager; import android.os.Bundle; import android.os.Handler; @@ -42,6 +43,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; +import com.android.settingslib.Utils; import com.android.settingslib.fuelgauge.BatterySaverUtils; import com.android.settingslib.fuelgauge.Estimate; import com.android.settingslib.utils.PowerUtil; @@ -97,6 +99,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC private boolean mAodPowerSave; private boolean mWirelessCharging; private boolean mIsBatteryDefender = false; + private boolean mIsIncompatibleCharging = false; private boolean mTestMode = false; @VisibleForTesting boolean mHasReceivedBattery = false; @@ -136,6 +139,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC filter.addAction(Intent.ACTION_BATTERY_CHANGED); filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); filter.addAction(ACTION_LEVEL_TEST); + filter.addAction(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED); mBroadcastDispatcher.registerReceiver(this, filter); } @@ -169,6 +173,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC ipw.print("mCharging="); ipw.println(mCharging); ipw.print("mCharged="); ipw.println(mCharged); ipw.print("mIsBatteryDefender="); ipw.println(mIsBatteryDefender); + ipw.print("mIsIncompatibleCharging="); ipw.println(mIsIncompatibleCharging); ipw.print("mPowerSave="); ipw.println(mPowerSave); ipw.print("mStateUnknown="); ipw.println(mStateUnknown); ipw.println("Callbacks:------------------"); @@ -214,6 +219,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC cb.onBatteryUnknownStateChanged(mStateUnknown); cb.onWirelessChargingChanged(mWirelessCharging); cb.onIsBatteryDefenderChanged(mIsBatteryDefender); + cb.onIsIncompatibleChargingChanged(mIsIncompatibleCharging); } @Override @@ -229,7 +235,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { if (mTestMode && !intent.getBooleanExtra("testmode", false)) return; mHasReceivedBattery = true; - mLevel = (int)(100f + mLevel = (int) (100f * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0) / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100)); mPluggedChargingSource = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0); @@ -262,6 +268,12 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC fireBatteryLevelChanged(); } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) { updatePowerSave(); + } else if (action.equals(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED)) { + boolean isIncompatibleCharging = Utils.containsIncompatibleChargers(mContext, TAG); + if (isIncompatibleCharging != mIsIncompatibleCharging) { + mIsIncompatibleCharging = isIncompatibleCharging; + fireIsIncompatibleChargingChanged(); + } } else if (action.equals(ACTION_LEVEL_TEST)) { mTestMode = true; mMainHandler.post(new Runnable() { @@ -270,6 +282,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC int mSavedLevel = mLevel; boolean mSavedPluggedIn = mPluggedIn; Intent mTestIntent = new Intent(Intent.ACTION_BATTERY_CHANGED); + @Override public void run() { if (mCurrentLevel < 0) { @@ -333,6 +346,13 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC return mIsBatteryDefender; } + /** + * Returns whether the charging adapter is incompatible. + */ + public boolean isIncompatibleCharging() { + return mIsIncompatibleCharging; + } + @Override public void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) { // Need to fetch or refresh the estimate, but it may involve binder calls so offload the @@ -453,6 +473,15 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC } } + private void fireIsIncompatibleChargingChanged() { + synchronized (mChangeCallbacks) { + final int n = mChangeCallbacks.size(); + for (int i = 0; i < n; i++) { + mChangeCallbacks.get(i).onIsIncompatibleChargingChanged(mIsIncompatibleCharging); + } + } + } + @Override public void dispatchDemoCommand(String command, Bundle args) { if (!mDemoModeController.isInDemoMode()) { @@ -464,6 +493,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC String powerSave = args.getString("powersave"); String present = args.getString("present"); String defender = args.getString("defender"); + String incompatible = args.getString("incompatible"); if (level != null) { mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100); } @@ -482,6 +512,10 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC mIsBatteryDefender = defender.equals("true"); fireIsBatteryDefenderChanged(); } + if (incompatible != null) { + mIsIncompatibleCharging = incompatible.equals("true"); + fireIsIncompatibleChargingChanged(); + } fireBatteryLevelChanged(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java index f8c36dcc90a1..518a9b3f61ec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java @@ -55,7 +55,6 @@ import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.DeviceConfigProxy; -import com.android.systemui.util.Utils; import com.android.systemui.util.settings.SecureSettings; import java.util.ArrayList; @@ -362,7 +361,8 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio private static final int MSG_ADD_CALLBACK = 3; private static final int MSG_REMOVE_CALLBACK = 4; - private ArrayList<LocationChangeCallback> mSettingsChangeCallbacks = new ArrayList<>(); + private final ArrayList<LocationChangeCallback> mSettingsChangeCallbacks = + new ArrayList<>(); H(Looper looper) { super(looper); @@ -388,14 +388,23 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio } private void locationActiveChanged() { - Utils.safeForeach(mSettingsChangeCallbacks, - cb -> cb.onLocationActiveChanged(mAreActiveLocationRequests)); + synchronized (mSettingsChangeCallbacks) { + final int n = mSettingsChangeCallbacks.size(); + for (int i = 0; i < n; i++) { + mSettingsChangeCallbacks.get(i) + .onLocationActiveChanged(mAreActiveLocationRequests); + } + } } private void locationSettingsChanged() { boolean isEnabled = isLocationEnabled(); - Utils.safeForeach(mSettingsChangeCallbacks, - cb -> cb.onLocationSettingsChanged(isEnabled)); + synchronized (mSettingsChangeCallbacks) { + final int n = mSettingsChangeCallbacks.size(); + for (int i = 0; i < n; i++) { + mSettingsChangeCallbacks.get(i).onLocationSettingsChanged(isEnabled); + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt index ae9d9ee445f2..cd1dcd585ed9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt @@ -64,7 +64,7 @@ class VariableDateView(context: Context, attrs: AttributeSet) : TextView(context override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { val availableWidth = MeasureSpec.getSize(widthMeasureSpec) - paddingStart - paddingEnd if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED && !freezeSwitching) { - onMeasureListener?.onMeasureAction(availableWidth) + onMeasureListener?.onMeasureAction(availableWidth, widthMeasureSpec) } super.onMeasure(widthMeasureSpec, heightMeasureSpec) } @@ -74,6 +74,6 @@ class VariableDateView(context: Context, attrs: AttributeSet) : TextView(context } interface OnMeasureListener { - fun onMeasureAction(availableWidth: Int) + fun onMeasureAction(availableWidth: Int, widthMeasureSpec: Int) } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt index f040d0a0efcf..4a31b8687069 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt @@ -28,9 +28,11 @@ import android.os.HandlerExecutor import android.os.UserHandle import android.text.TextUtils import android.util.Log +import android.view.View.MeasureSpec import androidx.annotation.VisibleForTesting import com.android.systemui.Dependency import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.util.ViewController import com.android.systemui.util.time.SystemClock import java.text.FieldPosition @@ -80,6 +82,7 @@ private const val TAG = "VariableDateViewController" class VariableDateViewController( private val systemClock: SystemClock, private val broadcastDispatcher: BroadcastDispatcher, + private val shadeExpansionStateManager: ShadeExpansionStateManager, private val timeTickHandler: Handler, view: VariableDateView ) : ViewController<VariableDateView>(view) { @@ -94,6 +97,7 @@ class VariableDateViewController( post(::updateClock) } } + private var isQsExpanded = false private var lastWidth = Integer.MAX_VALUE private var lastText = "" private var currentTime = Date() @@ -131,7 +135,11 @@ class VariableDateViewController( } private val onMeasureListener = object : VariableDateView.OnMeasureListener { - override fun onMeasureAction(availableWidth: Int) { + override fun onMeasureAction(availableWidth: Int, widthMeasureSpec: Int) { + if (!isQsExpanded && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) { + // ignore measured width from AT_MOST passes when in QQS (b/289489856) + return + } if (availableWidth != lastWidth) { // maybeChangeFormat will post if the pattern needs to change. maybeChangeFormat(availableWidth) @@ -140,6 +148,15 @@ class VariableDateViewController( } } + private fun onQsExpansionFractionChanged(qsExpansionFraction: Float) { + val newIsQsExpanded = qsExpansionFraction > 0.5 + if (newIsQsExpanded != isQsExpanded) { + isQsExpanded = newIsQsExpanded + // manually trigger a measure pass midway through the transition from QS to QQS + post { mView.measure(0, 0) } + } + } + override fun onViewAttached() { val filter = IntentFilter().apply { addAction(Intent.ACTION_TIME_TICK) @@ -151,6 +168,7 @@ class VariableDateViewController( broadcastDispatcher.registerReceiver(intentReceiver, filter, HandlerExecutor(timeTickHandler), UserHandle.SYSTEM) + shadeExpansionStateManager.addQsExpansionFractionListener(::onQsExpansionFractionChanged) post(::updateClock) mView.onAttach(onMeasureListener) } @@ -158,6 +176,7 @@ class VariableDateViewController( override fun onViewDetached() { dateFormat = null mView.onAttach(null) + shadeExpansionStateManager.removeQsExpansionFractionListener(::onQsExpansionFractionChanged) broadcastDispatcher.unregisterReceiver(intentReceiver) } @@ -211,12 +230,14 @@ class VariableDateViewController( class Factory @Inject constructor( private val systemClock: SystemClock, private val broadcastDispatcher: BroadcastDispatcher, + private val shadeExpansionStateManager: ShadeExpansionStateManager, @Named(Dependency.TIME_TICK_HANDLER_NAME) private val handler: Handler ) { fun create(view: VariableDateView): VariableDateViewController { return VariableDateViewController( systemClock, broadcastDispatcher, + shadeExpansionStateManager, handler, view ) diff --git a/packages/SystemUI/src/com/android/systemui/util/IListenerSet.kt b/packages/SystemUI/src/com/android/systemui/util/IListenerSet.kt new file mode 100644 index 000000000000..b0230b8eb0dd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/IListenerSet.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util + +/** + * A collection of listeners, observers, callbacks, etc. + * + * This container is optimized for infrequent mutation and frequent iteration, with thread safety + * and reentrant-safety guarantees as well. Specifically, to ensure that + * [ConcurrentModificationException] is never thrown, this iterator will not reflect changes made to + * the set after the iterator is constructed. + */ +interface IListenerSet<E : Any> : Set<E> { + /** + * A thread-safe, reentrant-safe method to add a listener. Does nothing if the listener is + * already in the set. + */ + fun addIfAbsent(element: E): Boolean + + /** A thread-safe, reentrant-safe method to remove a listener. */ + fun remove(element: E): Boolean +} diff --git a/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt b/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt index a47e61441c4c..f8e0b3dfe6d5 100644 --- a/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt +++ b/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt @@ -29,20 +29,12 @@ import java.util.concurrent.CopyOnWriteArrayList class ListenerSet<E : Any> /** Private constructor takes the internal list so that we can use auto-delegation */ private constructor(private val listeners: CopyOnWriteArrayList<E>) : - Collection<E> by listeners, Set<E> { + Collection<E> by listeners, IListenerSet<E> { /** Create a new instance */ constructor() : this(CopyOnWriteArrayList()) - /** - * A thread-safe, reentrant-safe method to add a listener. Does nothing if the listener is - * already in the set. - */ - fun addIfAbsent(element: E): Boolean = listeners.addIfAbsent(element) + override fun addIfAbsent(element: E): Boolean = listeners.addIfAbsent(element) - /** A thread-safe, reentrant-safe method to remove a listener. */ - fun remove(element: E): Boolean = listeners.remove(element) + override fun remove(element: E): Boolean = listeners.remove(element) } - -/** Extension to match Collection which is implemented to only be (easily) accessible in kotlin */ -fun <T : Any> ListenerSet<T>.isNotEmpty(): Boolean = !isEmpty() diff --git a/packages/SystemUI/src/com/android/systemui/util/NamedListenerSet.kt b/packages/SystemUI/src/com/android/systemui/util/NamedListenerSet.kt new file mode 100644 index 000000000000..c90b57ed449f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/NamedListenerSet.kt @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util + +import java.util.concurrent.CopyOnWriteArrayList +import java.util.function.Consumer + +/** + * A collection of listeners, observers, callbacks, etc. + * + * This container is optimized for infrequent mutation and frequent iteration, with thread safety + * and reentrant-safety guarantees as well. Specifically, to ensure that + * [ConcurrentModificationException] is never thrown, this iterator will not reflect changes made to + * the set after the iterator is constructed. + * + * This class provides all the abilities of [ListenerSet], except that each listener has a name + * calculated at runtime which can be used for time-efficient tracing of listener invocations. + */ +class NamedListenerSet<E : Any>( + private val getName: (E) -> String = { it.javaClass.name }, +) : IListenerSet<E> { + private val listeners = CopyOnWriteArrayList<NamedListener>() + + override val size: Int + get() = listeners.size + + override fun isEmpty() = listeners.isEmpty() + + override fun iterator(): Iterator<E> = iterator { + listeners.iterator().forEach { yield(it.listener) } + } + + override fun containsAll(elements: Collection<E>) = + listeners.count { it.listener in elements } == elements.size + + override fun contains(element: E) = listeners.firstOrNull { it.listener == element } != null + + override fun addIfAbsent(element: E): Boolean = listeners.addIfAbsent(NamedListener(element)) + + override fun remove(element: E): Boolean = listeners.removeIf { it.listener == element } + + /** A wrapper for the listener with an associated name. */ + inner class NamedListener(val listener: E) { + val name: String = getName(listener) + + override fun hashCode(): Int { + return listener.hashCode() + } + + override fun equals(other: Any?): Boolean = + when { + other === null -> false + other === this -> true + other !is NamedListenerSet<*>.NamedListener -> false + listener == other.listener -> true + else -> false + } + } + + /** Iterate the listeners in the set, providing the name for each one as well. */ + inline fun forEachNamed(block: (String, E) -> Unit) = + namedIterator().forEach { element -> block(element.name, element.listener) } + + /** + * Iterate the listeners in the set, wrapping each call to the block with [traceSection] using + * the listener name. + */ + inline fun forEachTraced(block: (E) -> Unit) = forEachNamed { name, listener -> + traceSection(name) { block(listener) } + } + + /** + * Iterate the listeners in the set, wrapping each call to the block with [traceSection] using + * the listener name. + */ + fun forEachTraced(consumer: Consumer<E>) = forEachNamed { name, listener -> + traceSection(name) { consumer.accept(listener) } + } + + /** Iterate over the [NamedListener]s currently in the set. */ + fun namedIterator(): Iterator<NamedListener> = listeners.iterator() +} diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java index c2727fc32465..e0daa0706b86 100644 --- a/packages/SystemUI/src/com/android/systemui/util/Utils.java +++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java @@ -37,6 +37,10 @@ public class Utils { /** * Allows lambda iteration over a list. It is done in reverse order so it is safe * to add or remove items during the iteration. Skips over null items. + * + * @deprecated According to b/286841705, this is *not* safe: If an item is removed from the + * list, then list.get(i) could throw an IndexOutOfBoundsException. This method should not be + * used; try using `synchronized` or making a copy of the list instead. */ public static <T> void safeForeach(List<T> list, Consumer<T> c) { for (int i = list.size() - 1; i >= 0; i--) { diff --git a/packages/SystemUI/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt b/packages/SystemUI/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt index 7e105cf19e2b..0fe2283c8543 100644 --- a/packages/SystemUI/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt +++ b/packages/SystemUI/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt @@ -31,7 +31,7 @@ import org.junit.runner.RunWith */ @RunWith(AndroidTestingRunner::class) @SmallTest -@RunWithLooper(setAsMainLooper = true) +@RunWithLooper class AnimatorTestRuleIsolationTest : SysuiTestCase() { @get:Rule val animatorTestRule = AnimatorTestRule() diff --git a/packages/SystemUI/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt b/packages/SystemUI/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt index 6c4036852802..cc7f7e4067d9 100644 --- a/packages/SystemUI/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt +++ b/packages/SystemUI/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt @@ -28,7 +28,7 @@ import org.junit.runner.RunWith @RunWith(AndroidTestingRunner::class) @SmallTest -@RunWithLooper(setAsMainLooper = true) +@RunWithLooper class AnimatorTestRulePrecisionTest : SysuiTestCase() { @get:Rule val animatorTestRule = AnimatorTestRule() diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleIsolationTest.kt b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleIsolationTest.kt index d034093a71b3..2d84fbafcf6c 100644 --- a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleIsolationTest.kt +++ b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleIsolationTest.kt @@ -31,7 +31,7 @@ import org.junit.runner.RunWith */ @RunWith(AndroidTestingRunner::class) @SmallTest -@RunWithLooper(setAsMainLooper = true) +@RunWithLooper class AnimatorTestRuleIsolationTest : SysuiTestCase() { @get:Rule val animatorTestRule = AnimatorTestRule() diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java index e7d420bcb32b..9016220b40cf 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java @@ -419,7 +419,15 @@ public class CarrierTextManagerTest extends SysuiTestCase { assertFalse(mWifiRepository.isWifiConnectedWithValidSsid()); mWifiRepository.setWifiNetwork( - new WifiNetworkModel.Active(0, false, 0, "", false, false, null)); + new WifiNetworkModel.Active( + /* networkId= */ 0, + /* isValidated= */ false, + /* level= */ 0, + /* ssid= */ "", + /* hotspotDeviceType= */ WifiNetworkModel.HotspotDeviceType.NONE, + /* isPasspointAccessPoint= */ false, + /* isOnlineSignUpForPasspointAccessPoint= */ false, + /* passpointProviderFriendlyName= */ null)); assertTrue(mWifiRepository.isWifiConnectedWithValidSsid()); mKeyguardUpdateMonitor.mServiceStates = new HashMap<>(); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java index ac04bc4356ac..98d4d22d59b4 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java @@ -23,6 +23,7 @@ import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLE import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -157,6 +158,12 @@ public class KeyguardClockSwitchControllerBaseTest extends SysuiTestCase { when(mSmartspaceController.buildAndConnectDateView(any())).thenReturn(mFakeDateView); when(mSmartspaceController.buildAndConnectWeatherView(any())).thenReturn(mFakeWeatherView); when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView); + doAnswer(invocation -> { + removeView(mFakeDateView); + removeView(mFakeWeatherView); + removeView(mFakeSmartspaceView); + return null; + }).when(mSmartspaceController).removeViewsFromParent(any()); mExecutor = new FakeExecutor(new FakeSystemClock()); mFakeFeatureFlags = new FakeFeatureFlags(); mFakeFeatureFlags.set(FACE_AUTH_REFACTOR, false); @@ -201,6 +208,13 @@ public class KeyguardClockSwitchControllerBaseTest extends SysuiTestCase { when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(mStatusArea); } + private void removeView(View v) { + ViewGroup group = ((ViewGroup) v.getParent()); + if (group != null) { + group.removeView(v); + } + } + protected void init() { mController.init(); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index efb981e5cebb..9ba21da59361 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -27,6 +27,7 @@ import android.testing.TestableResources import android.view.Gravity import android.view.LayoutInflater import android.view.MotionEvent +import android.view.View import android.view.WindowInsetsController import android.widget.FrameLayout import androidx.test.filters.SmallTest @@ -50,6 +51,7 @@ import com.android.systemui.plugins.ActivityStarter.OnDismissAction import com.android.systemui.plugins.FalsingManager import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.statusbar.policy.ConfigurationController @@ -66,6 +68,8 @@ import com.google.common.truth.Truth import java.util.Optional import junit.framework.Assert import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -80,6 +84,7 @@ import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.spy import org.mockito.Mockito.verify @@ -139,6 +144,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { private lateinit var testableResources: TestableResources private lateinit var sceneTestUtils: SceneTestUtils private lateinit var sceneInteractor: SceneInteractor + private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState> private lateinit var underTest: KeyguardSecurityContainerController @@ -198,6 +204,9 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { whenever(userInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID) sceneTestUtils = SceneTestUtils(this) sceneInteractor = sceneTestUtils.sceneInteractor() + sceneTransitionStateFlow = + MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen)) + sceneInteractor.setTransitionState(sceneTransitionStateFlow) underTest = KeyguardSecurityContainerController( @@ -484,6 +493,30 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { } @Test + fun showNextSecurityScreenOrFinish_SimPinToAnotherSimPin_None() { + // GIVEN the current security method is SimPin + whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false) + whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)) + .thenReturn(false) + underTest.showSecurityScreen(SecurityMode.SimPin) + + // WHEN a request is made from the SimPin screens to show the next security method + whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID)) + .thenReturn(SecurityMode.SimPin) + whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true) + + underTest.showNextSecurityScreenOrFinish( + /* authenticated= */ true, + TARGET_USER_ID, + /* bypassSecondaryLockScreen= */ true, + SecurityMode.SimPin + ) + + // THEN the next security method of None will dismiss keyguard. + verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) + } + + @Test fun onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() { val registeredSwipeListener = registeredSwipeListener whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(false) @@ -733,20 +766,39 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { // is // not enough to trigger a dismissal of the keyguard. underTest.onViewAttached() - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason") + sceneTransitionStateFlow.value = + ObservableTransitionState.Transition( + SceneKey.Lockscreen, + SceneKey.Bouncer, + flowOf(.5f) + ) + runCurrent() + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason") + sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer) runCurrent() verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) // While listening, going from the bouncer scene to the gone scene, does dismiss the // keyguard. - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason") + sceneTransitionStateFlow.value = + ObservableTransitionState.Transition(SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f)) + runCurrent() + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason") + sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone) runCurrent() verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt()) // While listening, moving back to the bouncer scene does not dismiss the keyguard // again. clearInvocations(viewMediatorCallback) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason") + sceneTransitionStateFlow.value = + ObservableTransitionState.Transition(SceneKey.Gone, SceneKey.Bouncer, flowOf(.5f)) + runCurrent() + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason") + sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer) runCurrent() verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) @@ -754,12 +806,22 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { // scene // does not dismiss the keyguard while we're not listening. underTest.onViewDetached() - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason") + sceneTransitionStateFlow.value = + ObservableTransitionState.Transition(SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f)) + runCurrent() + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason") + sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone) runCurrent() verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) // While not listening, moving back to the bouncer does not dismiss the keyguard. - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason") + sceneTransitionStateFlow.value = + ObservableTransitionState.Transition(SceneKey.Gone, SceneKey.Bouncer, flowOf(.5f)) + runCurrent() + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason") + sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer) runCurrent() verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) @@ -767,11 +829,26 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { // gone // scene now does dismiss the keyguard again. underTest.onViewAttached() - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason") + sceneTransitionStateFlow.value = + ObservableTransitionState.Transition(SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f)) + runCurrent() + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason") + sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone) runCurrent() verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt()) } + @Test + fun testResetUserSwitcher() { + val userSwitcher = mock(View::class.java) + whenever(view.findViewById<View>(R.id.keyguard_bouncer_user_switcher)) + .thenReturn(userSwitcher) + + underTest.prepareToShow() + verify(userSwitcher).setAlpha(0f) + } + private val registeredSwipeListener: KeyguardSecurityContainer.SwipeListener get() { underTest.onViewAttached() diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java index 20d9ef1e86b1..7d23c800321a 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java @@ -16,6 +16,7 @@ package com.android.keyguard; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -119,8 +120,8 @@ public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControll @Test public void correctlyDump() { mController.onInit(); - verify(mDumpManager).registerDumpable(mController); + verify(mDumpManager).registerDumpable(eq(mController.getInstanceName()), eq(mController)); mController.onDestroy(); - verify(mDumpManager, times(1)).unregisterDumpable(KeyguardStatusViewController.TAG); + verify(mDumpManager, times(1)).unregisterDumpable(eq(mController.getInstanceName())); } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 5abab6239b1e..6f3322ab1b85 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -40,6 +40,7 @@ import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_T import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT; import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser; import static com.android.systemui.flags.Flags.FP_LISTEN_OCCLUDING_APPS; +import static com.android.systemui.flags.Flags.STOP_FACE_AUTH_ON_DISPLAY_OFF; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN; @@ -89,6 +90,7 @@ import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; +import android.hardware.display.DisplayManagerGlobal; import android.hardware.face.FaceAuthenticateOptions; import android.hardware.face.FaceManager; import android.hardware.face.FaceSensorProperties; @@ -121,6 +123,9 @@ import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.text.TextUtils; +import android.view.Display; +import android.view.DisplayAdjustments; +import android.view.DisplayInfo; import androidx.annotation.NonNull; @@ -143,6 +148,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; @@ -304,10 +310,12 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mFingerprintAuthenticatorsRegisteredCallback; private IFaceAuthenticatorsRegisteredCallback mFaceAuthenticatorsRegisteredCallback; private final InstanceId mKeyguardInstanceId = InstanceId.fakeInstanceId(999); + private FakeDisplayTracker mDisplayTracker; @Before public void setup() throws RemoteException { MockitoAnnotations.initMocks(this); + mDisplayTracker = new FakeDisplayTracker(mContext); when(mSessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(mKeyguardInstanceId); when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true); @@ -348,6 +356,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { allowTestableLooperAsMainThread(); mFeatureFlags = new FakeFeatureFlags(); mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, false); + mFeatureFlags.set(STOP_FACE_AUTH_ON_DISPLAY_OFF, false); when(mSecureSettings.getUriFor(anyString())).thenReturn(mURI); @@ -358,6 +367,11 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { anyInt()); mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext); + setupBiometrics(mKeyguardUpdateMonitor); + } + + private void setupBiometrics(KeyguardUpdateMonitor keyguardUpdateMonitor) + throws RemoteException { captureAuthenticatorsRegisteredCallbacks(); setupFaceAuth(/* isClass3 */ false); setupFingerprintAuth(/* isClass3 */ true); @@ -367,9 +381,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mBiometricEnabledOnKeyguardCallback = mBiometricEnabledCallbackArgCaptor.getValue(); biometricsEnabledForCurrentUser(); - mHandler = spy(mKeyguardUpdateMonitor.getHandler()); + mHandler = spy(keyguardUpdateMonitor.getHandler()); try { - FieldSetter.setField(mKeyguardUpdateMonitor, + FieldSetter.setField(keyguardUpdateMonitor, KeyguardUpdateMonitor.class.getDeclaredField("mHandler"), mHandler); } catch (NoSuchFieldException e) { @@ -3029,6 +3043,79 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { verify(callback).onBiometricEnrollmentStateChanged(BiometricSourceType.FACE); } + @Test + public void stopFaceAuthOnDisplayOffFlagNotEnabled_doNotRegisterForDisplayCallback() { + assertThat(mDisplayTracker.getDisplayCallbacks().size()).isEqualTo(0); + } + + @Test + public void onDisplayOn_nothingHappens() throws RemoteException { + // GIVEN + keyguardIsVisible(); + enableStopFaceAuthOnDisplayOff(); + + // WHEN the default display state changes to ON + triggerDefaultDisplayStateChangeToOn(); + + // THEN face auth is NOT started since we rely on STARTED_WAKING_UP to start face auth, + // NOT the display on event + verifyFaceAuthenticateNeverCalled(); + verifyFaceDetectNeverCalled(); + } + + @Test + public void onDisplayOff_stopFaceAuth() throws RemoteException { + enableStopFaceAuthOnDisplayOff(); + + // GIVEN device is listening for face + mKeyguardUpdateMonitor.setKeyguardShowing(true, false); + mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); + mTestableLooper.processAllMessages(); + verifyFaceAuthenticateCall(); + + final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal); + mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel; + KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class); + mKeyguardUpdateMonitor.registerCallback(callback); + + // WHEN the default display state changes to OFF + triggerDefaultDisplayStateChangeToOff(); + + // THEN face listening is stopped. + verify(faceCancel).cancel(); + verify(callback).onBiometricRunningStateChanged( + eq(false), eq(BiometricSourceType.FACE)); // beverlyt + + } + + private void triggerDefaultDisplayStateChangeToOn() { + triggerDefaultDisplayStateChangeTo(true); + } + + private void triggerDefaultDisplayStateChangeToOff() { + triggerDefaultDisplayStateChangeTo(false); + } + + /** + * @param on true for Display.STATE_ON, else Display.STATE_OFF + */ + private void triggerDefaultDisplayStateChangeTo(boolean on) { + DisplayManagerGlobal displayManagerGlobal = mock(DisplayManagerGlobal.class); + DisplayInfo displayInfoWithDisplayState = new DisplayInfo(); + displayInfoWithDisplayState.state = on ? Display.STATE_ON : Display.STATE_OFF; + when(displayManagerGlobal.getDisplayInfo(mDisplayTracker.getDefaultDisplayId())) + .thenReturn(displayInfoWithDisplayState); + mDisplayTracker.setAllDisplays(new Display[]{ + new Display( + displayManagerGlobal, + mDisplayTracker.getDefaultDisplayId(), + displayInfoWithDisplayState, + DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS + ) + }); + mDisplayTracker.triggerOnDisplayChanged(mDisplayTracker.getDefaultDisplayId()); + } + private void verifyFingerprintAuthenticateNeverCalled() { verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any()); verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(), @@ -3297,6 +3384,18 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mTestableLooper.processAllMessages(); } + private void enableStopFaceAuthOnDisplayOff() throws RemoteException { + cleanupKeyguardUpdateMonitor(); + clearInvocations(mFaceManager); + clearInvocations(mFingerprintManager); + clearInvocations(mBiometricManager); + clearInvocations(mStatusBarStateController); + mFeatureFlags.set(STOP_FACE_AUTH_ON_DISPLAY_OFF, true); + mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext); + setupBiometrics(mKeyguardUpdateMonitor); + assertThat(mDisplayTracker.getDisplayCallbacks().size()).isEqualTo(1); + } + private Intent putPhoneInfo(Intent intent, Bundle data, Boolean simInited) { int subscription = simInited ? 1/* mock subid=1 */ : SubscriptionManager.PLACEHOLDER_SUBSCRIPTION_ID_BASE; @@ -3374,7 +3473,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager, mFaceWakeUpTriggersConfig, mDevicePostureController, Optional.of(mInteractiveToAuthProvider), mFeatureFlags, - mTaskStackChangeListeners, mActivityTaskManager); + mTaskStackChangeListeners, mActivityTaskManager, mDisplayTracker); setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java index ed6a891a6094..45021ba1b300 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java @@ -20,7 +20,9 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRIN import static com.android.keyguard.LockIconView.ICON_LOCK; import static com.android.keyguard.LockIconView.ICON_UNLOCK; +import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.eq; @@ -33,11 +35,13 @@ import android.hardware.biometrics.BiometricSourceType; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.Pair; +import android.view.HapticFeedbackConstants; import android.view.View; import androidx.test.filters.SmallTest; import com.android.settingslib.udfps.UdfpsOverlayParams; +import com.android.systemui.biometrics.UdfpsController; import com.android.systemui.doze.util.BurnInHelperKt; import org.junit.Test; @@ -339,4 +343,59 @@ public class LockIconViewControllerTest extends LockIconViewControllerBaseTest { // THEN the lock icon is shown verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); } + + @Test + public void playHaptic_onTouchExploration_NoOneWayHaptics_usesVibrate() { + mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false); + + // WHEN request to vibrate on touch exploration + mUnderTest.vibrateOnTouchExploration(); + + // THEN vibrates + verify(mVibrator).vibrate( + anyInt(), + any(), + eq(UdfpsController.EFFECT_CLICK), + eq("lock-icon-down"), + any()); + } + + @Test + public void playHaptic_onTouchExploration_withOneWayHaptics_performHapticFeedback() { + mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); + + // WHEN request to vibrate on touch exploration + mUnderTest.vibrateOnTouchExploration(); + + // THEN performHapticFeedback is used + verify(mVibrator).performHapticFeedback(any(), eq(HapticFeedbackConstants.CONTEXT_CLICK)); + } + + @Test + public void playHaptic_onLongPress_NoOneWayHaptics_usesVibrate() { + mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false); + + // WHEN request to vibrate on long press + mUnderTest.vibrateOnLongPress(); + + // THEN uses vibrate + verify(mVibrator).vibrate( + anyInt(), + any(), + eq(UdfpsController.EFFECT_CLICK), + eq("lock-screen-lock-icon-longpress"), + any()); + } + + @Test + public void playHaptic_onLongPress_withOneWayHaptics_performHapticFeedback() { + mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); + + // WHEN request to vibrate on long press + mUnderTest.vibrateOnLongPress(); + + // THEN uses perform haptic feedback + verify(mVibrator).performHapticFeedback(any(), eq(UdfpsController.LONG_PRESS)); + + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java index 44a2b682bf37..ba27fcd49fac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java @@ -25,12 +25,12 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; -import androidx.core.animation.AnimatorTestRule; import androidx.core.animation.ObjectAnimator; import androidx.test.annotation.UiThreadTest; import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.systemui.animation.AnimatorTestRule; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.statusbar.NotificationMediaManager; diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt new file mode 100644 index 000000000000..a7e7dd074a33 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2023 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.animation + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.core.animation.doOnEnd +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.doOnEnd +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidTestingRunner::class) +@SmallTest +@RunWithLooper +class AnimatorTestRuleOrderTest : SysuiTestCase() { + + @get:Rule val animatorTestRule = AnimatorTestRule() + + var value1: Float = -1f + var value2: Float = -1f + + private inline fun animateThisX( + propertyName: String, + duration: Long, + startDelay: Long = 0, + crossinline onEndAction: () -> Unit, + ) { + androidx.core.animation.ObjectAnimator.ofFloat(this, propertyName, 0f, 1f).also { + it.interpolator = null + it.duration = duration + it.startDelay = startDelay + it.doOnEnd { onEndAction() } + it.start() + } + } + + private inline fun animateThisP( + propertyName: String, + duration: Long, + startDelay: Long = 0, + crossinline onEndAction: () -> Unit, + ) { + android.animation.ObjectAnimator.ofFloat(this, propertyName, 0f, 1f).also { + it.interpolator = null + it.duration = duration + it.startDelay = startDelay + it.doOnEnd { onEndAction() } + it.start() + } + } + + @Test + fun testTwoAnimators() { + var ended1 = false + var ended2 = false + animateThisP("value1", duration = 100) { ended1 = true } + animateThisX("value2", duration = 200) { ended2 = true } + assertThat(value1).isEqualTo(0f) + assertThat(value2).isEqualTo(0f) + assertThat(ended1).isFalse() + assertThat(ended2).isFalse() + + animatorTestRule.advanceTimeBy(99) + assertThat(value1).isEqualTo(0.99f) + assertThat(value2).isEqualTo(0.495f) + assertThat(ended1).isFalse() + assertThat(ended2).isFalse() + + animatorTestRule.advanceTimeBy(1) + assertThat(value1).isEqualTo(1f) + assertThat(value2).isEqualTo(0.5f) + assertThat(ended1).isTrue() + assertThat(ended2).isFalse() + + animatorTestRule.advanceTimeBy(99) + assertThat(value1).isEqualTo(1f) + assertThat(value2).isEqualTo(0.995f) + assertThat(ended1).isTrue() + assertThat(ended2).isFalse() + + animatorTestRule.advanceTimeBy(1) + assertThat(value1).isEqualTo(1f) + assertThat(value2).isEqualTo(1f) + assertThat(ended1).isTrue() + assertThat(ended2).isTrue() + } + + @Test + fun testChainedAnimatorsPlatformThenX() { + var ended1 = false + var ended2 = false + animateThisP("value1", duration = 100) { + ended1 = true + animateThisX("value2", duration = 100) { ended2 = true } + } + + assertThat(value1).isEqualTo(0f) + assertThat(value2).isEqualTo(-1f) + assertThat(ended1).isFalse() + assertThat(ended2).isFalse() + + animatorTestRule.advanceTimeBy(50) + assertThat(value1).isEqualTo(0.5f) + assertThat(value2).isEqualTo(-1f) + assertThat(ended1).isFalse() + assertThat(ended2).isFalse() + + animatorTestRule.advanceTimeBy(50) + assertThat(value1).isEqualTo(1f) + assertThat(value2).isEqualTo(0f) + assertThat(ended1).isTrue() + assertThat(ended2).isFalse() + + animatorTestRule.advanceTimeBy(50) + assertThat(value1).isEqualTo(1f) + assertThat(value2).isEqualTo(0.5f) + assertThat(ended1).isTrue() + assertThat(ended2).isFalse() + + animatorTestRule.advanceTimeBy(50) + assertThat(value1).isEqualTo(1f) + assertThat(value2).isEqualTo(1f) + assertThat(ended1).isTrue() + assertThat(ended2).isTrue() + } + + @Test + fun testChainedAnimatorsXThenPlatform() { + var ended1 = false + var ended2 = false + animateThisX("value1", duration = 100) { + ended1 = true + animateThisP("value2", duration = 100) { ended2 = true } + } + + assertThat(value1).isEqualTo(0f) + assertThat(value2).isEqualTo(-1f) + assertThat(ended1).isFalse() + assertThat(ended2).isFalse() + + animatorTestRule.advanceTimeBy(50) + assertThat(value1).isEqualTo(0.5f) + assertThat(value2).isEqualTo(-1f) + assertThat(ended1).isFalse() + assertThat(ended2).isFalse() + + animatorTestRule.advanceTimeBy(50) + assertThat(value1).isEqualTo(1f) + assertThat(value2).isEqualTo(0f) + assertThat(ended1).isTrue() + assertThat(ended2).isFalse() + + animatorTestRule.advanceTimeBy(50) + assertThat(value1).isEqualTo(1f) + assertThat(value2).isEqualTo(0.5f) + assertThat(ended1).isTrue() + assertThat(ended2).isFalse() + + animatorTestRule.advanceTimeBy(50) + assertThat(value1).isEqualTo(1f) + assertThat(value2).isEqualTo(1f) + assertThat(ended1).isTrue() + assertThat(ended2).isTrue() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt index 005697044c0f..d3a2a73959dd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt @@ -18,23 +18,30 @@ package com.android.systemui.authentication.data.repository +import android.app.admin.DevicePolicyManager +import android.content.Intent import android.content.pm.UserInfo import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.keyguard.KeyguardSecurityModel import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.model.AuthenticationMethodModel +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.scene.SceneTestUtils import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat +import java.util.function.Function import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest 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.MockitoAnnotations @@ -43,6 +50,7 @@ import org.mockito.MockitoAnnotations class AuthenticationRepositoryTest : SysuiTestCase() { @Mock private lateinit var lockPatternUtils: LockPatternUtils + @Mock private lateinit var getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode> private val testUtils = SceneTestUtils(this) private val testScope = testUtils.testScope @@ -50,24 +58,49 @@ class AuthenticationRepositoryTest : SysuiTestCase() { private lateinit var underTest: AuthenticationRepository + private var currentSecurityMode: KeyguardSecurityModel.SecurityMode = + KeyguardSecurityModel.SecurityMode.PIN + @Before fun setUp() { MockitoAnnotations.initMocks(this) userRepository.setUserInfos(USER_INFOS) runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[0]) } + whenever(getSecurityMode.apply(anyInt())).thenAnswer { currentSecurityMode } underTest = AuthenticationRepositoryImpl( applicationScope = testScope.backgroundScope, - getSecurityMode = { KeyguardSecurityModel.SecurityMode.PIN }, + getSecurityMode = getSecurityMode, backgroundDispatcher = testUtils.testDispatcher, userRepository = userRepository, keyguardRepository = testUtils.keyguardRepository, lockPatternUtils = lockPatternUtils, + broadcastDispatcher = fakeBroadcastDispatcher, ) } @Test + fun authenticationMethod() = + testScope.runTest { + val authMethod by collectLastValue(underTest.authenticationMethod) + runCurrent() + dispatchBroadcast() + assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Pin) + assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Pin) + + setSecurityModeAndDispatchBroadcast(KeyguardSecurityModel.SecurityMode.Pattern) + assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Pattern) + assertThat(underTest.getAuthenticationMethod()) + .isEqualTo(AuthenticationMethodModel.Pattern) + + setSecurityModeAndDispatchBroadcast(KeyguardSecurityModel.SecurityMode.None) + assertThat(authMethod).isEqualTo(AuthenticationMethodModel.None) + assertThat(underTest.getAuthenticationMethod()) + .isEqualTo(AuthenticationMethodModel.None) + } + + @Test fun isAutoConfirmEnabled() = testScope.runTest { whenever(lockPatternUtils.isAutoPinConfirmEnabled(USER_INFOS[0].id)).thenReturn(true) @@ -95,6 +128,20 @@ class AuthenticationRepositoryTest : SysuiTestCase() { assertThat(values.last()).isTrue() } + private fun setSecurityModeAndDispatchBroadcast( + securityMode: KeyguardSecurityModel.SecurityMode, + ) { + currentSecurityMode = securityMode + dispatchBroadcast() + } + + private fun dispatchBroadcast() { + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED) + ) + } + companion object { private val USER_INFOS = listOf( diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt index a86937fcad3c..d848cd46e509 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt @@ -19,9 +19,11 @@ package com.android.systemui.authentication.domain.interactor import android.app.admin.DevicePolicyManager import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel import com.android.systemui.authentication.data.repository.AuthenticationRepository import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils @@ -50,42 +52,61 @@ class AuthenticationInteractorTest : SysuiTestCase() { ) @Test - fun getAuthenticationMethod() = + fun authenticationMethod() = testScope.runTest { - assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Pin) + val authMethod by collectLastValue(underTest.authenticationMethod) + runCurrent() + assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Pin) + assertThat(underTest.getAuthenticationMethod()) + .isEqualTo(DomainLayerAuthenticationMethodModel.Pin) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password + DataLayerAuthenticationMethodModel.Password ) + assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Password) assertThat(underTest.getAuthenticationMethod()) - .isEqualTo(AuthenticationMethodModel.Password) + .isEqualTo(DomainLayerAuthenticationMethodModel.Password) } @Test - fun getAuthenticationMethod_noneTreatedAsSwipe_whenLockscreenEnabled() = + fun authenticationMethod_noneTreatedAsSwipe_whenLockscreenEnabled() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + val authMethod by collectLastValue(underTest.authenticationMethod) + runCurrent() + + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.None + ) utils.authenticationRepository.setLockscreenEnabled(true) + assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Swipe) assertThat(underTest.getAuthenticationMethod()) - .isEqualTo(AuthenticationMethodModel.Swipe) + .isEqualTo(DomainLayerAuthenticationMethodModel.Swipe) } @Test - fun getAuthenticationMethod_none_whenLockscreenDisabled() = + fun authenticationMethod_none_whenLockscreenDisabled() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + val authMethod by collectLastValue(underTest.authenticationMethod) + runCurrent() + + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.None + ) utils.authenticationRepository.setLockscreenEnabled(false) + assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.None) assertThat(underTest.getAuthenticationMethod()) - .isEqualTo(AuthenticationMethodModel.None) + .isEqualTo(DomainLayerAuthenticationMethodModel.None) } @Test fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.None + ) utils.authenticationRepository.setLockscreenEnabled(false) val isUnlocked by collectLastValue(underTest.isUnlocked) @@ -111,7 +132,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun isUnlocked_whenAuthMethodIsNoneAndLockscreenEnabled_isFalse() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.None + ) utils.authenticationRepository.setLockscreenEnabled(true) val isUnlocked by collectLastValue(underTest.isUnlocked) @@ -124,7 +147,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { utils.authenticationRepository.setUnlocked(false) runCurrent() utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password + DataLayerAuthenticationMethodModel.Password ) assertThat(underTest.isAuthenticationRequired()).isTrue() @@ -135,7 +158,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { utils.authenticationRepository.setUnlocked(false) runCurrent() - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.None + ) assertThat(underTest.isAuthenticationRequired()).isFalse() } @@ -146,7 +171,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { utils.authenticationRepository.setUnlocked(true) runCurrent() utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password + DataLayerAuthenticationMethodModel.Password ) assertThat(underTest.isAuthenticationRequired()).isFalse() @@ -157,7 +182,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { utils.authenticationRepository.setUnlocked(true) runCurrent() - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.None + ) assertThat(underTest.isAuthenticationRequired()).isFalse() } @@ -166,7 +193,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun authenticate_withCorrectPin_returnsTrue() = testScope.runTest { val isThrottled by collectLastValue(underTest.isThrottled) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue() assertThat(isThrottled).isFalse() } @@ -174,21 +203,27 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun authenticate_withIncorrectPin_returnsFalse() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) assertThat(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4))).isFalse() } @Test(expected = IllegalArgumentException::class) fun authenticate_withEmptyPin_throwsException() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) underTest.authenticate(listOf()) } @Test fun authenticate_withCorrectMaxLengthPin_returnsTrue() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) val pin = List(16) { 9 } utils.authenticationRepository.overrideCredential(pin) assertThat(underTest.authenticate(pin)).isTrue() @@ -203,7 +238,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { // If the policy changes, there is work to do in SysUI. assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) assertThat(underTest.authenticate(List(17) { 9 })).isFalse() } @@ -212,7 +249,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val isThrottled by collectLastValue(underTest.isThrottled) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password + DataLayerAuthenticationMethodModel.Password ) assertThat(underTest.authenticate("password".toList())).isTrue() @@ -223,7 +260,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun authenticate_withIncorrectPassword_returnsFalse() = testScope.runTest { utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password + DataLayerAuthenticationMethodModel.Password ) assertThat(underTest.authenticate("alohomora".toList())).isFalse() @@ -233,7 +270,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun authenticate_withCorrectPattern_returnsTrue() = testScope.runTest { utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pattern + DataLayerAuthenticationMethodModel.Pattern ) assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN)).isTrue() @@ -243,21 +280,21 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun authenticate_withIncorrectPattern_returnsFalse() = testScope.runTest { utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pattern + DataLayerAuthenticationMethodModel.Pattern ) assertThat( underTest.authenticate( listOf( - AuthenticationMethodModel.Pattern.PatternCoordinate( + AuthenticationPatternCoordinate( x = 2, y = 0, ), - AuthenticationMethodModel.Pattern.PatternCoordinate( + AuthenticationPatternCoordinate( x = 2, y = 1, ), - AuthenticationMethodModel.Pattern.PatternCoordinate( + AuthenticationPatternCoordinate( x = 2, y = 2, ), @@ -271,7 +308,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNullAndHasNoEffect() = testScope.runTest { val isThrottled by collectLastValue(underTest.isThrottled) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) utils.authenticationRepository.setAutoConfirmEnabled(true) assertThat( underTest.authenticate( @@ -289,7 +328,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalseAndDoesNotUnlockDevice() = testScope.runTest { val isUnlocked by collectLastValue(underTest.isUnlocked) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) utils.authenticationRepository.setAutoConfirmEnabled(true) assertThat( underTest.authenticate( @@ -305,7 +346,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalseAndDoesNotUnlockDevice() = testScope.runTest { val isUnlocked by collectLastValue(underTest.isUnlocked) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) utils.authenticationRepository.setAutoConfirmEnabled(true) assertThat( underTest.authenticate( @@ -321,7 +364,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrueAndUnlocksDevice() = testScope.runTest { val isUnlocked by collectLastValue(underTest.isUnlocked) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) utils.authenticationRepository.setAutoConfirmEnabled(true) assertThat( underTest.authenticate( @@ -337,7 +382,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNullAndHasNoEffects() = testScope.runTest { val isUnlocked by collectLastValue(underTest.isUnlocked) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) utils.authenticationRepository.setAutoConfirmEnabled(false) assertThat( underTest.authenticate( @@ -354,7 +401,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val isUnlocked by collectLastValue(underTest.isUnlocked) utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password + DataLayerAuthenticationMethodModel.Password ) assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true)).isNull() @@ -367,7 +414,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { val isUnlocked by collectLastValue(underTest.isUnlocked) val throttling by collectLastValue(underTest.throttling) val isThrottled by collectLastValue(underTest.isThrottled) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN) assertThat(isUnlocked).isTrue() assertThat(isThrottled).isFalse() @@ -456,7 +505,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun hintedPinLength_withoutAutoConfirm_isNull() = testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) utils.authenticationRepository.setAutoConfirmEnabled(false) assertThat(hintedPinLength).isNull() @@ -466,7 +517,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun hintedPinLength_withAutoConfirmPinTooShort_isNull() = testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) utils.authenticationRepository.overrideCredential( buildList { repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) } @@ -481,7 +534,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun hintedPinLength_withAutoConfirmPinAtRightLength_isSameLength() = testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) utils.authenticationRepository.setAutoConfirmEnabled(true) utils.authenticationRepository.overrideCredential( buildList { repeat(utils.authenticationRepository.hintedPinLength) { add(it + 1) } } @@ -494,7 +549,9 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun hintedPinLength_withAutoConfirmPinTooLong_isNull() = testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) utils.authenticationRepository.overrideCredential( buildList { repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java index 40b572934f74..ec8be8ef3047 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java @@ -35,6 +35,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.StatusBarLocation; import com.android.systemui.statusbar.policy.BatteryController; @@ -63,6 +64,7 @@ public class BatteryMeterViewControllerTest extends SysuiTestCase { private ContentResolver mContentResolver; @Mock private BatteryController mBatteryController; + private FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags(); private BatteryMeterViewController mController; @@ -160,6 +162,7 @@ public class BatteryMeterViewControllerTest extends SysuiTestCase { mTunerService, mHandler, mContentResolver, + mFakeFeatureFlags, mBatteryController ); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt index c84efac86db5..f0f4ca7f3e66 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt @@ -131,6 +131,16 @@ class BatteryMeterViewTest : SysuiTestCase() { } @Test + fun contentDescription_isIncompatibleCharging_notCharging() { + mBatteryMeterView.onBatteryLevelChanged(45, true) + mBatteryMeterView.onIsIncompatibleChargingChanged(true) + + assertThat(mBatteryMeterView.contentDescription).isEqualTo( + context.getString(R.string.accessibility_battery_level, 45) + ) + } + + @Test fun changesFromEstimateToPercent_textAndContentDescriptionChanges() { mBatteryMeterView.onBatteryLevelChanged(15, false) mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE) @@ -231,14 +241,33 @@ class BatteryMeterViewTest : SysuiTestCase() { assertThat(drawable.displayShield).isFalse() } + @Test + fun isIncompatibleChargingChanged_true_drawableGetsChargingFalse() { + mBatteryMeterView.onBatteryLevelChanged(45, true) + val drawable = getBatteryDrawable() + + mBatteryMeterView.onIsIncompatibleChargingChanged(true) + + assertThat(drawable.getCharging()).isFalse() + } + + @Test + fun isIncompatibleChargingChanged_false_drawableGetsChargingTrue() { + mBatteryMeterView.onBatteryLevelChanged(45, true) + val drawable = getBatteryDrawable() + + mBatteryMeterView.onIsIncompatibleChargingChanged(false) + + assertThat(drawable.getCharging()).isTrue() + } + private fun getBatteryDrawable(): AccessorizedBatteryDrawable { return (mBatteryMeterView.getChildAt(0) as ImageView) .drawable as AccessorizedBatteryDrawable } private class Fetcher : BatteryEstimateFetcher { - override fun fetchBatteryTimeRemainingEstimate( - completion: EstimateFetchCompletion) { + override fun fetchBatteryTimeRemainingEstimate(completion: EstimateFetchCompletion) { completion.onBatteryRemainingEstimateRetrieved(ESTIMATE) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt index 186df02536ea..38e5728f6e70 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt @@ -165,6 +165,28 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { assertFalse(bouncerRepository.alternateBouncerVisible.value) } + @Test + fun alternateBouncerUiAvailable_fromMultipleSources() { + assertFalse(bouncerRepository.alternateBouncerUIAvailable.value) + + // GIVEN there are two different sources indicating the alternate bouncer is available + underTest.setAlternateBouncerUIAvailable(true, "source1") + underTest.setAlternateBouncerUIAvailable(true, "source2") + assertTrue(bouncerRepository.alternateBouncerUIAvailable.value) + + // WHEN one of the sources no longer says the UI is available + underTest.setAlternateBouncerUIAvailable(false, "source1") + + // THEN alternate bouncer UI is still available (from the other source) + assertTrue(bouncerRepository.alternateBouncerUIAvailable.value) + + // WHEN all sources say the UI is not available + underTest.setAlternateBouncerUIAvailable(false, "source2") + + // THEN alternate boucer UI is not available + assertFalse(bouncerRepository.alternateBouncerUIAvailable.value) + } + private fun givenCanShowAlternateBouncer() { bouncerRepository.setAlternateBouncerUIAvailable(true) biometricSettingsRepository.setFingerprintEnrolled(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt index 14fc931522a4..86e0c751085b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt @@ -19,8 +19,9 @@ package com.android.systemui.bouncer.domain.interactor import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils @@ -69,10 +70,11 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun pinAuthMethod() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + runCurrent() utils.authenticationRepository.setUnlocked(false) underTest.showOrUnlockDevice() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -100,10 +102,11 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun pinAuthMethod_tryAutoConfirm_withAutoConfirmPin() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + runCurrent() utils.authenticationRepository.setAutoConfirmEnabled(true) utils.authenticationRepository.setUnlocked(false) underTest.showOrUnlockDevice() @@ -136,10 +139,11 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun pinAuthMethod_tryAutoConfirm_withoutAutoConfirmPin() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + runCurrent() utils.authenticationRepository.setUnlocked(false) underTest.showOrUnlockDevice() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -165,11 +169,12 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun passwordAuthMethod() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) + runCurrent() utils.authenticationRepository.setUnlocked(false) underTest.showOrUnlockDevice() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -197,11 +202,12 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun patternAuthMethod() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pattern ) + runCurrent() utils.authenticationRepository.setUnlocked(false) underTest.showOrUnlockDevice() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -214,11 +220,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN) // Wrong input. - assertThat( - underTest.authenticate( - listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(1, 2)) - ) - ) + assertThat(underTest.authenticate(listOf(AuthenticationPatternCoordinate(1, 2)))) .isFalse() assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -234,7 +236,7 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun showOrUnlockDevice_notLocked_switchesToGoneScene() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) runCurrent() @@ -247,8 +249,9 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun showOrUnlockDevice_authMethodNotSecure_switchesToGoneScene() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe) + val currentScene by collectLastValue(sceneInteractor.desiredScene) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + utils.authenticationRepository.setLockscreenEnabled(true) utils.authenticationRepository.setUnlocked(false) underTest.showOrUnlockDevice() @@ -259,11 +262,12 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun showOrUnlockDevice_customMessageShown() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) + runCurrent() utils.authenticationRepository.setUnlocked(false) val customMessage = "Hello there!" @@ -279,7 +283,7 @@ class BouncerInteractorTest : SysuiTestCase() { val isThrottled by collectLastValue(underTest.isThrottled) val throttling by collectLastValue(underTest.throttling) val message by collectLastValue(underTest.message) - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) runCurrent() underTest.showOrUnlockDevice() diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt index 2cc949326fa0..7af8a0425402 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt @@ -18,19 +18,17 @@ package com.android.systemui.bouncer.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 -@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(JUnit4::class) class AuthMethodBouncerViewModelTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt index 0df0a17931f4..2c96bcc9dd33 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt @@ -18,8 +18,9 @@ package com.android.systemui.bouncer.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.google.common.truth.Truth.assertThat @@ -55,6 +56,7 @@ class BouncerViewModelTest : SysuiTestCase() { private val underTest = utils.bouncerViewModel( bouncerInteractor = bouncerInteractor, + authenticationInteractor = authenticationInteractor, ) @Test @@ -86,7 +88,8 @@ class BouncerViewModelTest : SysuiTestCase() { @Test fun authMethod_reusesInstances() = testScope.runTest { - val seen = mutableMapOf<AuthenticationMethodModel, AuthMethodBouncerViewModel>() + val seen = + mutableMapOf<DomainLayerAuthenticationMethodModel, AuthMethodBouncerViewModel>() val authMethodViewModel: AuthMethodBouncerViewModel? by collectLastValue(underTest.authMethod) // First pass, populate our "seen" map: @@ -105,7 +108,7 @@ class BouncerViewModelTest : SysuiTestCase() { @Test fun authMethodsToTest_returnsCompleteSampleOfAllAuthMethodTypes() { assertThat(authMethodsToTest().map { it::class }.toSet()) - .isEqualTo(AuthenticationMethodModel::class.sealedSubclasses.toSet()) + .isEqualTo(DomainLayerAuthenticationMethodModel::class.sealedSubclasses.toSet()) } @Test @@ -113,7 +116,9 @@ class BouncerViewModelTest : SysuiTestCase() { testScope.runTest { val message by collectLastValue(underTest.message) val throttling by collectLastValue(bouncerInteractor.throttling) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) assertThat(message?.isUpdateAnimated).isTrue() repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { @@ -136,7 +141,9 @@ class BouncerViewModelTest : SysuiTestCase() { } ) val throttling by collectLastValue(bouncerInteractor.throttling) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) assertThat(isInputEnabled).isTrue() repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { @@ -153,7 +160,9 @@ class BouncerViewModelTest : SysuiTestCase() { fun throttlingDialogMessage() = testScope.runTest { val throttlingDialogMessage by collectLastValue(underTest.throttlingDialogMessage) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.Pin + ) repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { // Wrong PIN. @@ -166,13 +175,32 @@ class BouncerViewModelTest : SysuiTestCase() { assertThat(throttlingDialogMessage).isNull() } - private fun authMethodsToTest(): List<AuthenticationMethodModel> { + private fun authMethodsToTest(): List<DomainLayerAuthenticationMethodModel> { return listOf( - AuthenticationMethodModel.None, - AuthenticationMethodModel.Swipe, - AuthenticationMethodModel.Pin, - AuthenticationMethodModel.Password, - AuthenticationMethodModel.Pattern, + DomainLayerAuthenticationMethodModel.None, + DomainLayerAuthenticationMethodModel.Swipe, + DomainLayerAuthenticationMethodModel.Pin, + DomainLayerAuthenticationMethodModel.Password, + DomainLayerAuthenticationMethodModel.Pattern, + ) + } + + private fun FakeAuthenticationRepository.setAuthenticationMethod( + model: DomainLayerAuthenticationMethodModel, + ) { + setAuthenticationMethod( + when (model) { + is DomainLayerAuthenticationMethodModel.None, + is DomainLayerAuthenticationMethodModel.Swipe -> + DataLayerAuthenticationMethodModel.None + is DomainLayerAuthenticationMethodModel.Pin -> + DataLayerAuthenticationMethodModel.Pin + is DomainLayerAuthenticationMethodModel.Password -> + DataLayerAuthenticationMethodModel.Password + is DomainLayerAuthenticationMethodModel.Pattern -> + DataLayerAuthenticationMethodModel.Pattern + } ) + setLockscreenEnabled(model !is DomainLayerAuthenticationMethodModel.None) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index 7f8d54c43387..4380af80efbd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -19,7 +19,7 @@ package com.android.systemui.bouncer.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey @@ -55,6 +55,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { private val bouncerViewModel = utils.bouncerViewModel( bouncerInteractor = bouncerInteractor, + authenticationInteractor = authenticationInteractor, ) private val underTest = PasswordBouncerViewModel( @@ -72,14 +73,15 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onShown() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() @@ -92,14 +94,15 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onPasswordInputChanged() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() runCurrent() @@ -114,12 +117,13 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateKeyPressed_whenCorrect() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPasswordInputChanged("password") @@ -132,14 +136,15 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateKeyPressed_whenWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPasswordInputChanged("wrong") @@ -154,14 +159,15 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateKeyPressed_correctAfterWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPasswordInputChanged("wrong") diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index 57fcbe595fa6..ea2cad2ab517 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -19,8 +19,8 @@ package com.android.systemui.bouncer.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey @@ -57,6 +57,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { private val bouncerViewModel = utils.bouncerViewModel( bouncerInteractor = bouncerInteractor, + authenticationInteractor = authenticationInteractor, ) private val underTest = PatternBouncerViewModel( @@ -75,7 +76,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onShown() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) @@ -83,7 +84,8 @@ class PatternBouncerViewModelTest : SysuiTestCase() { AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() @@ -97,7 +99,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onDragStart() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) @@ -105,7 +107,8 @@ class PatternBouncerViewModelTest : SysuiTestCase() { AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() runCurrent() @@ -121,14 +124,15 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onDragEnd_whenCorrect() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onDragStart() @@ -168,7 +172,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onDragEnd_whenWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) @@ -176,7 +180,8 @@ class PatternBouncerViewModelTest : SysuiTestCase() { AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onDragStart() @@ -200,7 +205,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onDragEnd_correctAfterWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) @@ -208,7 +213,8 @@ class PatternBouncerViewModelTest : SysuiTestCase() { AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onDragStart() diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index 81c68ed2320f..531f86abdfbc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -19,8 +19,8 @@ package com.android.systemui.bouncer.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey @@ -57,6 +57,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { private val bouncerViewModel = utils.bouncerViewModel( bouncerInteractor = bouncerInteractor, + authenticationInteractor = authenticationInteractor, ) private val underTest = PinBouncerViewModel( @@ -75,11 +76,13 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onShown() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() @@ -92,12 +95,14 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onPinButtonClicked() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() runCurrent() @@ -112,12 +117,14 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onBackspaceButtonClicked() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() runCurrent() @@ -134,11 +141,13 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onPinEdit() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() @@ -156,12 +165,14 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onBackspaceButtonLongPressed() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() runCurrent() @@ -180,10 +191,12 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateButtonClicked_whenCorrect() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit -> @@ -198,12 +211,14 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateButtonClicked_whenWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPinButtonClicked(1) @@ -222,12 +237,14 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateButtonClicked_correctAfterWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPinButtonClicked(1) @@ -254,11 +271,13 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAutoConfirm_whenCorrect() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) utils.authenticationRepository.setAutoConfirmEnabled(true) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit -> @@ -271,13 +290,15 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAutoConfirm_whenWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) utils.authenticationRepository.setAutoConfirmEnabled(true) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() FakeAuthenticationRepository.DEFAULT_PIN.dropLast(1).forEach { digit -> diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt index 35f0f6c68798..37c70d8f25e0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt @@ -28,12 +28,12 @@ import org.junit.runner.RunWith @RunWith(AndroidTestingRunner::class) class FakeFeatureFlagsTest : SysuiTestCase() { - private val unreleasedFlag = UnreleasedFlag(-1000, "-1000", "test") - private val releasedFlag = ReleasedFlag(-1001, "-1001", "test") - private val stringFlag = StringFlag(-1002, "-1002", "test") - private val resourceBooleanFlag = ResourceBooleanFlag(-1003, "-1003", "test", resourceId = -1) - private val resourceStringFlag = ResourceStringFlag(-1004, "-1004", "test", resourceId = -1) - private val sysPropBooleanFlag = SysPropBooleanFlag(-1005, "test", "test") + private val unreleasedFlag = UnreleasedFlag("-1000", "test") + private val releasedFlag = ReleasedFlag("-1001", "test") + private val stringFlag = StringFlag("-1002", "test") + private val resourceBooleanFlag = ResourceBooleanFlag("-1003", "test", resourceId = -1) + private val resourceStringFlag = ResourceStringFlag("-1004", "test", resourceId = -1) + private val sysPropBooleanFlag = SysPropBooleanFlag("test", "test") /** * FakeFeatureFlags does not honor any default values. All flags which are accessed must be @@ -46,43 +46,43 @@ class FakeFeatureFlagsTest : SysuiTestCase() { assertThat(flags.isEnabled(Flags.TEAMFOOD)).isFalse() fail("Expected an exception when accessing an unspecified flag.") } catch (ex: IllegalStateException) { - assertThat(ex.message).contains("id=1") + assertThat(ex.message).contains("UNKNOWN(teamfood)") } try { assertThat(flags.isEnabled(unreleasedFlag)).isFalse() fail("Expected an exception when accessing an unspecified flag.") } catch (ex: IllegalStateException) { - assertThat(ex.message).contains("UNKNOWN(id=-1000)") + assertThat(ex.message).contains("UNKNOWN(-1000)") } try { assertThat(flags.isEnabled(releasedFlag)).isFalse() fail("Expected an exception when accessing an unspecified flag.") } catch (ex: IllegalStateException) { - assertThat(ex.message).contains("UNKNOWN(id=-1001)") + assertThat(ex.message).contains("UNKNOWN(-1001)") } try { assertThat(flags.isEnabled(resourceBooleanFlag)).isFalse() fail("Expected an exception when accessing an unspecified flag.") } catch (ex: IllegalStateException) { - assertThat(ex.message).contains("UNKNOWN(id=-1003)") + assertThat(ex.message).contains("UNKNOWN(-1003)") } try { assertThat(flags.isEnabled(sysPropBooleanFlag)).isFalse() fail("Expected an exception when accessing an unspecified flag.") } catch (ex: IllegalStateException) { - assertThat(ex.message).contains("UNKNOWN(id=-1005)") + assertThat(ex.message).contains("UNKNOWN(test)") } try { assertThat(flags.getString(stringFlag)).isEmpty() fail("Expected an exception when accessing an unspecified flag.") } catch (ex: IllegalStateException) { - assertThat(ex.message).contains("UNKNOWN(id=-1002)") + assertThat(ex.message).contains("UNKNOWN(-1002)") } try { assertThat(flags.getString(resourceStringFlag)).isEmpty() fail("Expected an exception when accessing an unspecified flag.") } catch (ex: IllegalStateException) { - assertThat(ex.message).contains("UNKNOWN(id=-1004)") + assertThat(ex.message).contains("UNKNOWN(-1004)") } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt index 18f7db18b1f4..ff15cb39b640 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt @@ -74,10 +74,10 @@ class FeatureFlagsDebugTest : SysuiTestCase() { private val serverFlagReader = ServerFlagReaderFake() private val teamfoodableFlagA = UnreleasedFlag( - 500, name = "a", namespace = "test", teamfood = true + name = "a", namespace = "test", teamfood = true ) private val teamfoodableFlagB = ReleasedFlag( - 501, name = "b", namespace = "test", teamfood = true + name = "b", namespace = "test", teamfood = true ) @Before @@ -119,7 +119,6 @@ class FeatureFlagsDebugTest : SysuiTestCase() { assertThat( featureFlagsDebug.isEnabled( ReleasedFlag( - 2, name = "2", namespace = "test" ) @@ -128,7 +127,6 @@ class FeatureFlagsDebugTest : SysuiTestCase() { assertThat( featureFlagsDebug.isEnabled( UnreleasedFlag( - 3, name = "3", namespace = "test" ) @@ -137,7 +135,6 @@ class FeatureFlagsDebugTest : SysuiTestCase() { assertThat( featureFlagsDebug.isEnabled( ReleasedFlag( - 4, name = "4", namespace = "test" ) @@ -146,7 +143,6 @@ class FeatureFlagsDebugTest : SysuiTestCase() { assertThat( featureFlagsDebug.isEnabled( UnreleasedFlag( - 5, name = "5", namespace = "test" ) @@ -208,23 +204,22 @@ class FeatureFlagsDebugTest : SysuiTestCase() { assertThat( featureFlagsDebug.isEnabled( ResourceBooleanFlag( - 1, "1", "test", 1001 ) ) ).isFalse() - assertThat(featureFlagsDebug.isEnabled(ResourceBooleanFlag(2, "2", "test", 1002))).isTrue() - assertThat(featureFlagsDebug.isEnabled(ResourceBooleanFlag(3, "3", "test", 1003))).isTrue() + assertThat(featureFlagsDebug.isEnabled(ResourceBooleanFlag("2", "test", 1002))).isTrue() + assertThat(featureFlagsDebug.isEnabled(ResourceBooleanFlag("3", "test", 1003))).isTrue() Assert.assertThrows(NameNotFoundException::class.java) { - featureFlagsDebug.isEnabled(ResourceBooleanFlag(4, "4", "test", 1004)) + featureFlagsDebug.isEnabled(ResourceBooleanFlag("4", "test", 1004)) } // Test that resource is loaded (and validated) even when the setting is set. // This prevents developers from not noticing when they reference an invalid resource. Assert.assertThrows(NameNotFoundException::class.java) { - featureFlagsDebug.isEnabled(ResourceBooleanFlag(5, "5", "test", 1005)) + featureFlagsDebug.isEnabled(ResourceBooleanFlag("5", "test", 1005)) } } @@ -237,30 +232,29 @@ class FeatureFlagsDebugTest : SysuiTestCase() { return@thenAnswer it.getArgument(1) } - assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag(1, "a", "test"))).isFalse() - assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag(2, "b", "test"))).isTrue() - assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag(3, "c", "test", true))).isTrue() + assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag("a", "test"))).isFalse() + assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag("b", "test"))).isTrue() + assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag("c", "test", true))).isTrue() assertThat( featureFlagsDebug.isEnabled( SysPropBooleanFlag( - 4, "d", "test", false ) ) ).isFalse() - assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag(5, "e", "test"))).isFalse() + assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag("e", "test"))).isFalse() } @Test fun readStringFlag() { whenever(flagManager.readFlagValue<String>(eq("3"), any())).thenReturn("foo") whenever(flagManager.readFlagValue<String>(eq("4"), any())).thenReturn("bar") - assertThat(featureFlagsDebug.getString(StringFlag(1, "1", "test", "biz"))).isEqualTo("biz") - assertThat(featureFlagsDebug.getString(StringFlag(2, "2", "test", "baz"))).isEqualTo("baz") - assertThat(featureFlagsDebug.getString(StringFlag(3, "3", "test", "buz"))).isEqualTo("foo") - assertThat(featureFlagsDebug.getString(StringFlag(4, "4", "test", "buz"))).isEqualTo("bar") + assertThat(featureFlagsDebug.getString(StringFlag("1", "test", "biz"))).isEqualTo("biz") + assertThat(featureFlagsDebug.getString(StringFlag("2", "test", "baz"))).isEqualTo("baz") + assertThat(featureFlagsDebug.getString(StringFlag("3", "test", "buz"))).isEqualTo("foo") + assertThat(featureFlagsDebug.getString(StringFlag("4", "test", "buz"))).isEqualTo("bar") } @Test @@ -279,7 +273,6 @@ class FeatureFlagsDebugTest : SysuiTestCase() { assertThat( featureFlagsDebug.getString( ResourceStringFlag( - 1, "1", "test", 1001 @@ -289,7 +282,6 @@ class FeatureFlagsDebugTest : SysuiTestCase() { assertThat( featureFlagsDebug.getString( ResourceStringFlag( - 2, "2", "test", 1002 @@ -299,7 +291,6 @@ class FeatureFlagsDebugTest : SysuiTestCase() { assertThat( featureFlagsDebug.getString( ResourceStringFlag( - 3, "3", "test", 1003 @@ -308,15 +299,15 @@ class FeatureFlagsDebugTest : SysuiTestCase() { ).isEqualTo("override3") Assert.assertThrows(NullPointerException::class.java) { - featureFlagsDebug.getString(ResourceStringFlag(4, "4", "test", 1004)) + featureFlagsDebug.getString(ResourceStringFlag("4", "test", 1004)) } Assert.assertThrows(NameNotFoundException::class.java) { - featureFlagsDebug.getString(ResourceStringFlag(5, "5", "test", 1005)) + featureFlagsDebug.getString(ResourceStringFlag("5", "test", 1005)) } // Test that resource is loaded (and validated) even when the setting is set. // This prevents developers from not noticing when they reference an invalid resource. Assert.assertThrows(NameNotFoundException::class.java) { - featureFlagsDebug.getString(ResourceStringFlag(6, "6", "test", 1005)) + featureFlagsDebug.getString(ResourceStringFlag("6", "test", 1005)) } } @@ -324,10 +315,10 @@ class FeatureFlagsDebugTest : SysuiTestCase() { fun readIntFlag() { whenever(flagManager.readFlagValue<Int>(eq("3"), any())).thenReturn(22) whenever(flagManager.readFlagValue<Int>(eq("4"), any())).thenReturn(48) - assertThat(featureFlagsDebug.getInt(IntFlag(1, "1", "test", 12))).isEqualTo(12) - assertThat(featureFlagsDebug.getInt(IntFlag(2, "2", "test", 93))).isEqualTo(93) - assertThat(featureFlagsDebug.getInt(IntFlag(3, "3", "test", 8))).isEqualTo(22) - assertThat(featureFlagsDebug.getInt(IntFlag(4, "4", "test", 234))).isEqualTo(48) + assertThat(featureFlagsDebug.getInt(IntFlag("1", "test", 12))).isEqualTo(12) + assertThat(featureFlagsDebug.getInt(IntFlag("2", "test", 93))).isEqualTo(93) + assertThat(featureFlagsDebug.getInt(IntFlag("3", "test", 8))).isEqualTo(22) + assertThat(featureFlagsDebug.getInt(IntFlag("4", "test", 234))).isEqualTo(48) } @Test @@ -339,30 +330,30 @@ class FeatureFlagsDebugTest : SysuiTestCase() { whenever(resources.getInteger(1005)).thenThrow(NotFoundException("unknown resource")) whenever(resources.getInteger(1006)).thenThrow(NotFoundException("unknown resource")) - whenever(flagManager.readFlagValue<Int>(eq(3), any())).thenReturn(20) - whenever(flagManager.readFlagValue<Int>(eq(4), any())).thenReturn(500) - whenever(flagManager.readFlagValue<Int>(eq(5), any())).thenReturn(9519) + whenever(flagManager.readFlagValue<Int>(eq("3"), any())).thenReturn(20) + whenever(flagManager.readFlagValue<Int>(eq("4"), any())).thenReturn(500) + whenever(flagManager.readFlagValue<Int>(eq("5"), any())).thenReturn(9519) - assertThat(featureFlagsDebug.getInt(ResourceIntFlag(1, "1", "test", 1001))).isEqualTo(88) - assertThat(featureFlagsDebug.getInt(ResourceIntFlag(2, "2", "test", 1002))).isEqualTo(61) - assertThat(featureFlagsDebug.getInt(ResourceIntFlag(3, "3", "test", 1003))).isEqualTo(20) + assertThat(featureFlagsDebug.getInt(ResourceIntFlag("1", "test", 1001))).isEqualTo(88) + assertThat(featureFlagsDebug.getInt(ResourceIntFlag("2", "test", 1002))).isEqualTo(61) + assertThat(featureFlagsDebug.getInt(ResourceIntFlag("3", "test", 1003))).isEqualTo(20) Assert.assertThrows(NotFoundException::class.java) { - featureFlagsDebug.getInt(ResourceIntFlag(4, "4", "test", 1004)) + featureFlagsDebug.getInt(ResourceIntFlag("4", "test", 1004)) } // Test that resource is loaded (and validated) even when the setting is set. // This prevents developers from not noticing when they reference an invalid resource. Assert.assertThrows(NotFoundException::class.java) { - featureFlagsDebug.getInt(ResourceIntFlag(5, "5", "test", 1005)) + featureFlagsDebug.getInt(ResourceIntFlag("5", "test", 1005)) } } @Test fun broadcastReceiver_IgnoresInvalidData() { - addFlag(UnreleasedFlag(1, "1", "test")) - addFlag(ResourceBooleanFlag(2, "2", "test", 1002)) - addFlag(StringFlag(3, "3", "test", "flag3")) - addFlag(ResourceStringFlag(4, "4", "test", 1004)) + addFlag(UnreleasedFlag("1", "test")) + addFlag(ResourceBooleanFlag("2", "test", 1002)) + addFlag(StringFlag("3", "test", "flag3")) + addFlag(ResourceStringFlag("4", "test", 1004)) broadcastReceiver.onReceive(mockContext, null) broadcastReceiver.onReceive(mockContext, Intent()) @@ -378,7 +369,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { @Test fun intentWithId_NoValueKeyClears() { - addFlag(UnreleasedFlag(1, name = "1", namespace = "test")) + addFlag(UnreleasedFlag(name = "1", namespace = "test")) // trying to erase an id not in the map does nothing broadcastReceiver.onReceive( @@ -397,10 +388,10 @@ class FeatureFlagsDebugTest : SysuiTestCase() { @Test fun setBooleanFlag() { - addFlag(UnreleasedFlag(1, "1", "test")) - addFlag(UnreleasedFlag(2, "2", "test")) - addFlag(ResourceBooleanFlag(3, "3", "test", 1003)) - addFlag(ResourceBooleanFlag(4, "4", "test", 1004)) + addFlag(UnreleasedFlag("1", "test")) + addFlag(UnreleasedFlag("2", "test")) + addFlag(ResourceBooleanFlag("3", "test", 1003)) + addFlag(ResourceBooleanFlag("4", "test", 1004)) setByBroadcast("1", false) verifyPutData("1", "{\"type\":\"boolean\",\"value\":false}") @@ -417,8 +408,8 @@ class FeatureFlagsDebugTest : SysuiTestCase() { @Test fun setStringFlag() { - addFlag(StringFlag(1, "1", "1", "test")) - addFlag(ResourceStringFlag(2, "2", "test", 1002)) + addFlag(StringFlag("1", "1", "test")) + addFlag(ResourceStringFlag("2", "test", 1002)) setByBroadcast("1", "override1") verifyPutData("1", "{\"type\":\"string\",\"value\":\"override1\"}") @@ -429,7 +420,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { @Test fun setFlag_ClearsCache() { - val flag1 = addFlag(StringFlag(1, "1", "test", "flag1")) + val flag1 = addFlag(StringFlag("1", "test", "flag1")) whenever(flagManager.readFlagValue<String>(eq("1"), any())).thenReturn("original") // gets the flag & cache it @@ -451,7 +442,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { @Test fun serverSide_Overrides_MakesFalse() { - val flag = ReleasedFlag(100, "100", "test") + val flag = ReleasedFlag("100", "test") serverFlagReader.setFlagValue(flag.namespace, flag.name, false) @@ -460,7 +451,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() { @Test fun serverSide_Overrides_MakesTrue() { - val flag = UnreleasedFlag(100, name = "100", namespace = "test") + val flag = UnreleasedFlag(name = "100", namespace = "test") serverFlagReader.setFlagValue(flag.namespace, flag.name, true) assertThat(featureFlagsDebug.isEnabled(flag)).isTrue() @@ -494,18 +485,18 @@ class FeatureFlagsDebugTest : SysuiTestCase() { @Test fun dumpFormat() { - val flag1 = ReleasedFlag(1, "1", "test") - val flag2 = ResourceBooleanFlag(2, "2", "test", 1002) - val flag3 = UnreleasedFlag(3, "3", "test") - val flag4 = StringFlag(4, "4", "test", "") - val flag5 = StringFlag(5, "5", "test", "flag5default") - val flag6 = ResourceStringFlag(6, "6", "test", 1006) - val flag7 = ResourceStringFlag(7, "7", "test", 1007) + val flag1 = ReleasedFlag("1", "test") + val flag2 = ResourceBooleanFlag("2", "test", 1002) + val flag3 = UnreleasedFlag("3", "test") + val flag4 = StringFlag("4", "test", "") + val flag5 = StringFlag("5", "test", "flag5default") + val flag6 = ResourceStringFlag("6", "test", 1006) + val flag7 = ResourceStringFlag("7", "test", 1007) whenever(resources.getBoolean(1002)).thenReturn(true) whenever(resources.getString(1006)).thenReturn("resource1006") whenever(resources.getString(1007)).thenReturn("resource1007") - whenever(flagManager.readFlagValue(eq(7), eq(StringFlagSerializer))) + whenever(flagManager.readFlagValue(eq("7"), eq(StringFlagSerializer))) .thenReturn("override7") // WHEN the flags have been accessed diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt index 917147b17517..16b459556cb9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt @@ -44,7 +44,7 @@ class FeatureFlagsReleaseTest : SysuiTestCase() { private val serverFlagReader = ServerFlagReaderFake() - private val flagA = ReleasedFlag(501, name = "a", namespace = "test") + private val flagA = ReleasedFlag(name = "a", namespace = "test") @Before fun setup() { @@ -62,11 +62,10 @@ class FeatureFlagsReleaseTest : SysuiTestCase() { @Test fun testBooleanResourceFlag() { - val flagId = 213 val flagResourceId = 3 val flagName = "213" val flagNamespace = "test" - val flag = ResourceBooleanFlag(flagId, flagName, flagNamespace, flagResourceId) + val flag = ResourceBooleanFlag(flagName, flagNamespace, flagResourceId) whenever(mResources.getBoolean(flagResourceId)).thenReturn(true) assertThat(featureFlagsRelease.isEnabled(flag)).isTrue() } @@ -79,33 +78,32 @@ class FeatureFlagsReleaseTest : SysuiTestCase() { whenever(mResources.getString(1004)).thenAnswer { throw NameNotFoundException() } assertThat(featureFlagsRelease.getString( - ResourceStringFlag(1, "1", "test", 1001))).isEqualTo("") + ResourceStringFlag("1", "test", 1001))).isEqualTo("") assertThat(featureFlagsRelease.getString( - ResourceStringFlag(2, "2", "test", 1002))).isEqualTo("res2") + ResourceStringFlag("2", "test", 1002))).isEqualTo("res2") assertThrows(NullPointerException::class.java) { - featureFlagsRelease.getString(ResourceStringFlag(3, "3", "test", 1003)) + featureFlagsRelease.getString(ResourceStringFlag("3", "test", 1003)) } assertThrows(NameNotFoundException::class.java) { - featureFlagsRelease.getString(ResourceStringFlag(4, "4", "test", 1004)) + featureFlagsRelease.getString(ResourceStringFlag("4", "test", 1004)) } } @Test fun testSysPropBooleanFlag() { - val flagId = 213 val flagName = "sys_prop_flag" val flagNamespace = "test" val flagDefault = true - val flag = SysPropBooleanFlag(flagId, flagName, flagNamespace, flagDefault) + val flag = SysPropBooleanFlag(flagName, flagNamespace, flagDefault) whenever(mSystemProperties.getBoolean(flagName, flagDefault)).thenReturn(flagDefault) assertThat(featureFlagsRelease.isEnabled(flag)).isEqualTo(flagDefault) } @Test fun serverSide_OverridesReleased_MakesFalse() { - val flag = ReleasedFlag(100, "100", "test") + val flag = ReleasedFlag("100", "test") serverFlagReader.setFlagValue(flag.namespace, flag.name, false) @@ -114,7 +112,7 @@ class FeatureFlagsReleaseTest : SysuiTestCase() { @Test fun serverSide_OverridesUnreleased_Ignored() { - val flag = UnreleasedFlag(100, "100", "test") + val flag = UnreleasedFlag("100", "test") serverFlagReader.setFlagValue(flag.namespace, flag.name, true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt index 28131b50f04c..b02baa7fd1b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt @@ -33,10 +33,10 @@ class FlagCommandTest : SysuiTestCase() { @Mock private lateinit var featureFlags: FeatureFlagsDebug @Mock private lateinit var pw: PrintWriter private val flagMap = mutableMapOf<String, Flag<*>>() - private val flagA = UnreleasedFlag(500, "500", "test") - private val flagB = ReleasedFlag(501, "501", "test") - private val stringFlag = StringFlag(502, "502", "test", "abracadabra") - private val intFlag = IntFlag(503, "503", "test", 12) + private val flagA = UnreleasedFlag("500", "test") + private val flagB = ReleasedFlag("501", "test") + private val stringFlag = StringFlag("502", "test", "abracadabra") + private val intFlag = IntFlag("503", "test", 12) private lateinit var cmd: FlagCommand diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt index e679d47537b5..303aaa128378 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt @@ -64,14 +64,14 @@ class FlagManagerTest : SysuiTestCase() { verifyNoMoreInteractions(mFlagSettingsHelper) // adding the first listener registers the observer - mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener1) + mFlagManager.addListener(ReleasedFlag("1", "test"), listener1) val observer = withArgCaptor<ContentObserver> { verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture()) } verifyNoMoreInteractions(mFlagSettingsHelper) // adding another listener does nothing - mFlagManager.addListener(ReleasedFlag(2, "2", "test"), listener2) + mFlagManager.addListener(ReleasedFlag("2", "test"), listener2) verifyNoMoreInteractions(mFlagSettingsHelper) // removing the original listener does nothing with second one still present @@ -89,7 +89,7 @@ class FlagManagerTest : SysuiTestCase() { val listener = mock<FlagListenable.Listener>() val clearCacheAction = mock<Consumer<String>>() mFlagManager.clearCacheAction = clearCacheAction - mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener) + mFlagManager.addListener(ReleasedFlag("1", "test"), listener) val observer = withArgCaptor<ContentObserver> { verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture()) } @@ -101,8 +101,8 @@ class FlagManagerTest : SysuiTestCase() { fun testObserverInvokesListeners() { val listener1 = mock<FlagListenable.Listener>() val listener10 = mock<FlagListenable.Listener>() - mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener1) - mFlagManager.addListener(ReleasedFlag(10, "10", "test"), listener10) + mFlagManager.addListener(ReleasedFlag("1", "test"), listener1) + mFlagManager.addListener(ReleasedFlag("10", "test"), listener10) val observer = withArgCaptor<ContentObserver> { verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture()) } @@ -127,8 +127,8 @@ class FlagManagerTest : SysuiTestCase() { fun testOnlySpecificFlagListenerIsInvoked() { val listener1 = mock<FlagListenable.Listener>() val listener10 = mock<FlagListenable.Listener>() - mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener1) - mFlagManager.addListener(ReleasedFlag(10, "10", "test"), listener10) + mFlagManager.addListener(ReleasedFlag("1", "test"), listener1) + mFlagManager.addListener(ReleasedFlag("10", "test"), listener10) mFlagManager.dispatchListenersAndMaybeRestart("1", null) val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> { @@ -148,8 +148,8 @@ class FlagManagerTest : SysuiTestCase() { @Test fun testSameListenerCanBeUsedForMultipleFlags() { val listener = mock<FlagListenable.Listener>() - mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener) - mFlagManager.addListener(ReleasedFlag(10, "10", "test"), listener) + mFlagManager.addListener(ReleasedFlag("1", "test"), listener) + mFlagManager.addListener(ReleasedFlag("10", "test"), listener) mFlagManager.dispatchListenersAndMaybeRestart("1", null) val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> { @@ -177,7 +177,7 @@ class FlagManagerTest : SysuiTestCase() { @Test fun testListenerCanSuppressRestart() { val restartAction = mock<Consumer<Boolean>>() - mFlagManager.addListener(ReleasedFlag(1, "1", "test")) { event -> + mFlagManager.addListener(ReleasedFlag("1", "test")) { event -> event.requestNoRestart() } mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction) @@ -188,7 +188,7 @@ class FlagManagerTest : SysuiTestCase() { @Test fun testListenerOnlySuppressesRestartForOwnFlag() { val restartAction = mock<Consumer<Boolean>>() - mFlagManager.addListener(ReleasedFlag(10, "10", "test")) { event -> + mFlagManager.addListener(ReleasedFlag("10", "test")) { event -> event.requestNoRestart() } mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction) @@ -199,10 +199,10 @@ class FlagManagerTest : SysuiTestCase() { @Test fun testRestartWhenNotAllListenersRequestSuppress() { val restartAction = mock<Consumer<Boolean>>() - mFlagManager.addListener(ReleasedFlag(10, "10", "test")) { event -> + mFlagManager.addListener(ReleasedFlag("10", "test")) { event -> event.requestNoRestart() } - mFlagManager.addListener(ReleasedFlag(10, "10", "test")) { + mFlagManager.addListener(ReleasedFlag("10", "test")) { // do not request } mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction) diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt index 953b7fb32d56..1d1949d12479 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt @@ -53,7 +53,7 @@ class ServerFlagReaderImplTest : SysuiTestCase() { @Test fun testChange_alertsListener() { - val flag = ReleasedFlag(1, "flag_1", "test") + val flag = ReleasedFlag("flag_1", "test") serverFlagReader.listenForChanges(listOf(flag), changeListener) deviceConfig.setProperty(NAMESPACE, "flag_1", "1", false) @@ -65,7 +65,7 @@ class ServerFlagReaderImplTest : SysuiTestCase() { @Test fun testChange_ignoresListenersDuringTest() { val serverFlagReader = ServerFlagReaderImpl(NAMESPACE, deviceConfig, executor, true) - val flag = ReleasedFlag(1, "1", " test") + val flag = ReleasedFlag("1", " test") serverFlagReader.listenForChanges(listOf(flag), changeListener) deviceConfig.setProperty(NAMESPACE, "flag_override_1", "1", false) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 28328c2e6fae..847d58b8cd83 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -634,6 +634,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { TestableLooper.get(this).processAllMessages(); assertFalse(mViewMediator.isShowingAndNotOccluded()); + verify(mKeyguardUnlockAnimationController).notifyFinishedKeyguardExitAnimation(false); } @Test @@ -650,6 +651,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { TestableLooper.get(this).processAllMessages(); assertTrue(mViewMediator.isShowingAndNotOccluded()); + verify(mKeyguardUnlockAnimationController).notifyFinishedKeyguardExitAnimation(true); } @Test @@ -658,6 +660,9 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { startMockKeyguardExitAnimation(); cancelMockKeyguardExitAnimation(); + // Calling cancel above results in keyguard not visible, as there is no pending lock + verify(mKeyguardUnlockAnimationController).notifyFinishedKeyguardExitAnimation(false); + mViewMediator.maybeHandlePendingLock(); TestableLooper.get(this).processAllMessages(); @@ -672,10 +677,15 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { @Test @TestableLooper.RunWithLooper(setAsMainLooper = true) - public void testStartKeyguardExitAnimation_expectSurfaceBehindRemoteAnimation() { + public void testStartKeyguardExitAnimation_expectSurfaceBehindRemoteAnimationAndExits() { startMockKeyguardExitAnimation(); assertTrue(mViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind()); + mViewMediator.mViewMediatorCallback.keyguardDonePending(true, + mUpdateMonitor.getCurrentUser()); + mViewMediator.mViewMediatorCallback.readyForKeyguardDone(); + TestableLooper.get(this).processAllMessages(); + verify(mKeyguardUnlockAnimationController).notifyFinishedKeyguardExitAnimation(false); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt index 7510373ed582..9d983b8c8943 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt @@ -137,27 +137,18 @@ class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() { } @Test - fun getPickerScreenState_enabledIfConfiguredOnDevice_canOpenCamera() = runTest { - whenever(controller.isAvailableOnDevice).thenReturn(true) - whenever(controller.isAbleToOpenCameraApp).thenReturn(true) + fun getPickerScreenState_enabledIfConfiguredOnDevice_isEnabledForPickerState() = runTest { + whenever(controller.isAllowedOnLockScreen).thenReturn(true) + whenever(controller.isAbleToLaunchScannerActivity).thenReturn(true) assertThat(underTest.getPickerScreenState()) .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default()) } @Test - fun getPickerScreenState_disabledIfConfiguredOnDevice_cannotOpenCamera() = runTest { - whenever(controller.isAvailableOnDevice).thenReturn(true) - whenever(controller.isAbleToOpenCameraApp).thenReturn(false) - - assertThat(underTest.getPickerScreenState()) - .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java) - } - - @Test - fun getPickerScreenState_unavailableIfNotConfiguredOnDevice() = runTest { - whenever(controller.isAvailableOnDevice).thenReturn(false) - whenever(controller.isAbleToOpenCameraApp).thenReturn(true) + fun getPickerScreenState_disabledIfConfiguredOnDevice_isDisabledForPickerState() = runTest { + whenever(controller.isAllowedOnLockScreen).thenReturn(true) + whenever(controller.isAbleToLaunchScannerActivity).thenReturn(false) assertThat(underTest.getPickerScreenState()) .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt index ec30732dda23..dcaafe8dd052 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt @@ -19,10 +19,10 @@ package com.android.systemui.keyguard.data.repository import android.graphics.Point import android.testing.AndroidTestingRunner import android.testing.TestableLooper -import androidx.core.animation.AnimatorTestRule import androidx.test.filters.SmallTest import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.AnimatorTestRule import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.BiometricUnlockSource diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt deleted file mode 100644 index 86e56bf1e131..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (C) 2023 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 androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.scene.SceneTestUtils -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.scene.shared.model.SceneModel -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 - -@OptIn(ExperimentalCoroutinesApi::class) -@SmallTest -@RunWith(JUnit4::class) -class LockscreenSceneInteractorTest : SysuiTestCase() { - - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val sceneInteractor = utils.sceneInteractor() - private val authenticationInteractor = - utils.authenticationInteractor( - repository = utils.authenticationRepository(), - ) - private val underTest = - utils.lockScreenSceneInteractor( - authenticationInteractor = authenticationInteractor, - bouncerInteractor = - utils.bouncerInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, - ), - ) - - @Test - fun isDeviceLocked() = - testScope.runTest { - val isDeviceLocked by collectLastValue(underTest.isDeviceLocked) - - utils.authenticationRepository.setUnlocked(false) - assertThat(isDeviceLocked).isTrue() - - utils.authenticationRepository.setUnlocked(true) - assertThat(isDeviceLocked).isFalse() - } - - @Test - fun isSwipeToDismissEnabled_deviceLockedAndAuthMethodSwipe_true() = - testScope.runTest { - val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled) - - utils.authenticationRepository.setUnlocked(false) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe) - - assertThat(isSwipeToDismissEnabled).isTrue() - } - - @Test - fun isSwipeToDismissEnabled_deviceUnlockedAndAuthMethodSwipe_false() = - testScope.runTest { - val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled) - - utils.authenticationRepository.setUnlocked(true) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe) - - assertThat(isSwipeToDismissEnabled).isFalse() - } - - @Test - fun dismissLockScreen_deviceLockedWithSecureAuthMethod_switchesToBouncer() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) - utils.authenticationRepository.setUnlocked(false) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) - - underTest.dismissLockscreen() - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - } - - @Test - fun dismissLockScreen_deviceUnlocked_switchesToGone() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) - utils.authenticationRepository.setUnlocked(true) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) - - underTest.dismissLockscreen() - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) - } - - @Test - fun dismissLockScreen_deviceLockedWithInsecureAuthMethod_switchesToGone() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) - utils.authenticationRepository.setUnlocked(false) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) - - underTest.dismissLockscreen() - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) - } - - @Test - fun switchFromLockScreenToGone_authMethodNotSwipe_doesNotUnlockDevice() = - testScope.runTest { - val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Lockscreen), "reason") - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - assertThat(isUnlocked).isFalse() - - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone), "reason") - - assertThat(isUnlocked).isFalse() - } - - @Test - fun switchFromNonLockScreenToGone_authMethodSwipe_doesNotUnlockDevice() = - testScope.runTest { - val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked) - runCurrent() - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade), "reason") - runCurrent() - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe) - runCurrent() - assertThat(isUnlocked).isFalse() - - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone), "reason") - - assertThat(isUnlocked).isFalse() - } - - @Test - fun authMethodChangedToNone_notOnLockScreenScene_doesNotDismissLockScreen() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe) - runCurrent() - sceneInteractor.setCurrentScene(SceneModel(SceneKey.QuickSettings), "reason") - runCurrent() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings)) - - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings)) - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt index 66631dc9977b..addb1815cead 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt @@ -17,6 +17,8 @@ package com.android.systemui.keyguard.ui.view.layout.blueprints +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -26,15 +28,17 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAr import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection +import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -@RunWith(JUnit4::class) +@RunWith(AndroidTestingRunner::class) +@RunWithLooper(setAsMainLooper = true) @SmallTest class DefaultKeyguardBlueprintTest : SysuiTestCase() { private lateinit var underTest: DefaultKeyguardBlueprint @@ -45,6 +49,8 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { @Mock private lateinit var defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection @Mock private lateinit var defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection + @Mock private lateinit var defaultStatusViewSection: DefaultStatusViewSection + @Mock private lateinit var splitShadeGuidelines: SplitShadeGuidelines @Before fun setup() { @@ -57,6 +63,8 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { defaultShortcutsSection, defaultAmbientIndicationAreaSection, defaultSettingsPopupMenuSection, + defaultStatusViewSection, + splitShadeGuidelines, ) } @@ -69,5 +77,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { verify(defaultShortcutsSection).apply(cs) verify(defaultAmbientIndicationAreaSection).apply(cs) verify(defaultSettingsPopupMenuSection).apply(cs) + verify(defaultStatusViewSection).apply(cs) + verify(splitShadeGuidelines).apply(cs) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt index 1444f8df4e4c..379c03c4353d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt @@ -25,6 +25,7 @@ import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController +import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index 63ee240fd2c6..45d7a5ebb60a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -19,7 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils @@ -49,14 +49,11 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { private val underTest = LockscreenSceneViewModel( applicationScope = testScope.backgroundScope, - interactor = - utils.lockScreenSceneInteractor( + authenticationInteractor = authenticationInteractor, + bouncerInteractor = + utils.bouncerInteractor( authenticationInteractor = authenticationInteractor, - bouncerInteractor = - utils.bouncerInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, - ), + sceneInteractor = sceneInteractor, ), ) @@ -87,17 +84,18 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { } @Test - fun upTransitionSceneKey_swipeToUnlockedEnabled_gone() = + fun upTransitionSceneKey_swipeToUnlockEnabled_gone() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + utils.authenticationRepository.setLockscreenEnabled(true) utils.authenticationRepository.setUnlocked(false) assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone) } @Test - fun upTransitionSceneKey_swipeToUnlockedNotEnabled_bouncer() = + fun upTransitionSceneKey_swipeToUnlockNotEnabled_bouncer() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) @@ -109,7 +107,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { @Test fun onLockButtonClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) runCurrent() @@ -122,7 +120,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { @Test fun onContentClicked_deviceUnlocked_switchesToGone() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) runCurrent() @@ -135,7 +133,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { @Test fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) runCurrent() @@ -148,7 +146,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { @Test fun onLockButtonClicked_deviceUnlocked_switchesToGone() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java index ab24c46825e4..db00e0927886 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java @@ -278,6 +278,21 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { } @Test + public void + whenNotBroadcasting_verifyLeBroadcastServiceCallBackIsUnregisteredIfProfileEnabled() { + when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn( + mLocalBluetoothLeBroadcast); + mIsBroadcasting = true; + + mMediaOutputBaseDialogImpl.start(); + verify(mLocalBluetoothLeBroadcast).registerServiceCallBack(any(), any()); + + mIsBroadcasting = false; + mMediaOutputBaseDialogImpl.stop(); + verify(mLocalBluetoothLeBroadcast).unregisterServiceCallBack(any()); + } + + @Test public void refresh_checkStopText() { mStopText = "test_string"; mMediaOutputBaseDialogImpl.refresh(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java index 3e69a29bd963..bfc8c83c1c2a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java @@ -256,6 +256,30 @@ public class MediaOutputDialogTest extends SysuiTestCase { } @Test + public void isBroadcastSupported_noBleDeviceAndEnabledBroadcast_returnsTrue() { + when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn( + mLocalBluetoothLeBroadcast); + when(mLocalBluetoothLeBroadcast.isEnabled(any())).thenReturn(true); + FeatureFlagUtils.setEnabled(mContext, + FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true); + when(mMediaDevice.isBLEDevice()).thenReturn(false); + + assertThat(mMediaOutputDialog.isBroadcastSupported()).isTrue(); + } + + @Test + public void isBroadcastSupported_noBleDeviceAndDisabledBroadcast_returnsFalse() { + when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn( + mLocalBluetoothLeBroadcast); + when(mLocalBluetoothLeBroadcast.isEnabled(any())).thenReturn(false); + FeatureFlagUtils.setEnabled(mContext, + FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true); + when(mMediaDevice.isBLEDevice()).thenReturn(false); + + assertThat(mMediaOutputDialog.isBroadcastSupported()).isFalse(); + } + + @Test public void getBroadcastIconVisibility_isBroadcasting_returnVisible() { when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn( mLocalBluetoothLeBroadcast); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java index 65210d63e5c5..e905e9cad459 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java @@ -132,7 +132,7 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { /* enableOnLockScreen */ true); verifyActivityDetails(null); assertThat(mController.isEnabledForLockScreenButton()).isFalse(); - assertThat(mController.isAbleToOpenCameraApp()).isFalse(); + assertThat(mController.isAbleToLaunchScannerActivity()).isFalse(); } @Test @@ -151,7 +151,7 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { /* enableOnLockScreen */ true); verifyActivityDetails("abc/.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); - assertThat(mController.isAbleToOpenCameraApp()).isTrue(); + assertThat(mController.isAbleToLaunchScannerActivity()).isTrue(); } @Test @@ -161,7 +161,7 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { /* enableOnLockScreen */ true); verifyActivityDetails("abc/.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); - assertThat(mController.isAbleToOpenCameraApp()).isTrue(); + assertThat(mController.isAbleToLaunchScannerActivity()).isTrue(); } @Test @@ -171,7 +171,7 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { /* enableOnLockScreen */ true); verifyActivityDetails("abc/.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); - assertThat(mController.isAbleToOpenCameraApp()).isTrue(); + assertThat(mController.isAbleToLaunchScannerActivity()).isTrue(); } @Test @@ -181,7 +181,7 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { /* enableOnLockScreen */ true); verifyActivityDetails("abc/abc.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); - assertThat(mController.isAbleToOpenCameraApp()).isTrue(); + assertThat(mController.isAbleToLaunchScannerActivity()).isTrue(); } @Test @@ -191,7 +191,7 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { /* enableOnLockScreen */ true); verifyActivityDetails(null); assertThat(mController.isEnabledForLockScreenButton()).isFalse(); - assertThat(mController.isAbleToOpenCameraApp()).isFalse(); + assertThat(mController.isAbleToLaunchScannerActivity()).isFalse(); } @Test @@ -201,24 +201,24 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { /* enableOnLockScreen */ true); verifyActivityDetails("abc/.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); - assertThat(mController.isAbleToOpenCameraApp()).isTrue(); + assertThat(mController.isAbleToLaunchScannerActivity()).isTrue(); mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER, "def/.ijk", false); verifyActivityDetails("def/.ijk"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); - assertThat(mController.isAbleToOpenCameraApp()).isTrue(); + assertThat(mController.isAbleToLaunchScannerActivity()).isTrue(); mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER, null, false); verifyActivityDetails("abc/.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); - assertThat(mController.isAbleToOpenCameraApp()).isTrue(); + assertThat(mController.isAbleToLaunchScannerActivity()).isTrue(); - // Once from setup + twice from this function - verify(mCallback, times(3)).onQRCodeScannerActivityChanged(); + // twice from this function + verify(mCallback, times(2)).onQRCodeScannerActivityChanged(); } @Test @@ -228,7 +228,7 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { /* enableOnLockScreen */ true); verifyActivityDetails(null); assertThat(mController.isEnabledForLockScreenButton()).isFalse(); - assertThat(mController.isAbleToOpenCameraApp()).isFalse(); + assertThat(mController.isAbleToLaunchScannerActivity()).isFalse(); mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER, @@ -236,14 +236,14 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { verifyActivityDetails("def/.ijk"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); - assertThat(mController.isAbleToOpenCameraApp()).isTrue(); + assertThat(mController.isAbleToLaunchScannerActivity()).isTrue(); mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER, null, false); verifyActivityDetails(null); assertThat(mController.isEnabledForLockScreenButton()).isFalse(); - assertThat(mController.isAbleToOpenCameraApp()).isFalse(); + assertThat(mController.isAbleToLaunchScannerActivity()).isFalse(); verify(mCallback, times(2)).onQRCodeScannerActivityChanged(); } @@ -295,19 +295,20 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { /* enableOnLockScreen */ true); verifyActivityDetails("abc/.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); - assertThat(mController.isAbleToOpenCameraApp()).isTrue(); + assertThat(mController.isAbleToLaunchScannerActivity()).isTrue(); mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0", UserHandle.USER_CURRENT); verifyActivityDetails("abc/.def"); assertThat(mController.isEnabledForLockScreenButton()).isFalse(); - assertThat(mController.isAbleToOpenCameraApp()).isTrue(); + assertThat(mController.isAllowedOnLockScreen()).isTrue(); + assertThat(mController.isAbleToLaunchScannerActivity()).isTrue(); mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "1", UserHandle.USER_CURRENT); verifyActivityDetails("abc/.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); - assertThat(mController.isAbleToOpenCameraApp()).isTrue(); + assertThat(mController.isAbleToLaunchScannerActivity()).isTrue(); // Once from setup + twice from this function verify(mCallback, times(3)).onQRCodeScannerPreferenceChanged(); } @@ -319,13 +320,13 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { /* enableOnLockScreen */ true); verifyActivityDetails("abc/.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); - assertThat(mController.isAbleToOpenCameraApp()).isTrue(); + assertThat(mController.isAbleToLaunchScannerActivity()).isTrue(); + // even if unregistered, intent and activity details are retained mController.unregisterQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE, QR_CODE_SCANNER_PREFERENCE_CHANGE); - verifyActivityDetails(null); - assertThat(mController.isEnabledForLockScreenButton()).isFalse(); - assertThat(mController.isAbleToOpenCameraApp()).isFalse(); + assertThat(mController.isAbleToLaunchScannerActivity()).isTrue(); + assertThat(mController.isAllowedOnLockScreen()).isTrue(); // Unregister once again and make sure it affects the next register event mController.unregisterQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE, @@ -334,7 +335,7 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { QR_CODE_SCANNER_PREFERENCE_CHANGE); verifyActivityDetails("abc/.def"); assertThat(mController.isEnabledForLockScreenButton()).isTrue(); - assertThat(mController.isAbleToOpenCameraApp()).isTrue(); + assertThat(mController.isAbleToLaunchScannerActivity()).isTrue(); } @Test @@ -344,7 +345,7 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { /* enableOnLockScreen */ false); assertThat(mController.getIntent()).isNotNull(); assertThat(mController.isEnabledForLockScreenButton()).isFalse(); - assertThat(mController.isAbleToOpenCameraApp()).isTrue(); + assertThat(mController.isAbleToLaunchScannerActivity()).isTrue(); assertThat(getSettingsQRCodeDefaultComponent()).isNull(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java index 6f2d904dda64..71aa7a8d0015 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java @@ -117,7 +117,7 @@ public class QRCodeScannerTileTest extends SysuiTestCase { @Test public void testQRCodeTileUnavailable() { - when(mController.isAbleToOpenCameraApp()).thenReturn(false); + when(mController.isAbleToLaunchScannerActivity()).thenReturn(false); QSTile.State state = new QSTile.State(); mTile.handleUpdateState(state, null); assertEquals(state.state, Tile.STATE_UNAVAILABLE); @@ -127,7 +127,7 @@ public class QRCodeScannerTileTest extends SysuiTestCase { @Test public void testQRCodeTileAvailable() { - when(mController.isAbleToOpenCameraApp()).thenReturn(true); + when(mController.isAbleToLaunchScannerActivity()).thenReturn(true); QSTile.State state = new QSTile.State(); mTile.handleUpdateState(state, null); assertEquals(state.state, Tile.STATE_INACTIVE); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index ee42a7011264..2cb02058ab03 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -18,7 +18,7 @@ package com.android.systemui.qs.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey @@ -46,21 +46,17 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { private val underTest = QuickSettingsSceneViewModel( - lockscreenSceneInteractor = - utils.lockScreenSceneInteractor( + bouncerInteractor = + utils.bouncerInteractor( authenticationInteractor = authenticationInteractor, - bouncerInteractor = - utils.bouncerInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, - ), + sceneInteractor = sceneInteractor, ), ) @Test fun onContentClicked_deviceUnlocked_switchesToGone() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) runCurrent() @@ -73,7 +69,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { @Test fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt index 56e3e9649fe7..181f8a7e3003 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt @@ -25,7 +25,6 @@ import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel -import com.android.systemui.scene.shared.model.SceneTransitionModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -39,6 +38,7 @@ import org.junit.runners.JUnit4 class SceneContainerRepositoryTest : SysuiTestCase() { private val utils = SceneTestUtils(this) + private val testScope = utils.testScope @Test fun allSceneKeys() { @@ -56,97 +56,82 @@ class SceneContainerRepositoryTest : SysuiTestCase() { } @Test - fun currentScene() = runTest { - val underTest = utils.fakeSceneContainerRepository() - val currentScene by collectLastValue(underTest.currentScene) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) + fun desiredScene() = + testScope.runTest { + val underTest = utils.fakeSceneContainerRepository() + val currentScene by collectLastValue(underTest.desiredScene) + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) - underTest.setCurrentScene(SceneModel(SceneKey.Shade)) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade)) - } + underTest.setDesiredScene(SceneModel(SceneKey.Shade)) + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade)) + } @Test(expected = IllegalStateException::class) - fun setCurrentScene_noSuchSceneInContainer_throws() { + fun setDesiredScene_noSuchSceneInContainer_throws() { val underTest = utils.fakeSceneContainerRepository( utils.fakeSceneContainerConfig(listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)), ) - underTest.setCurrentScene(SceneModel(SceneKey.Shade)) + underTest.setDesiredScene(SceneModel(SceneKey.Shade)) } @Test - fun isVisible() = runTest { - val underTest = utils.fakeSceneContainerRepository() - val isVisible by collectLastValue(underTest.isVisible) - assertThat(isVisible).isTrue() + fun isVisible() = + testScope.runTest { + val underTest = utils.fakeSceneContainerRepository() + val isVisible by collectLastValue(underTest.isVisible) + assertThat(isVisible).isTrue() - underTest.setVisible(false) - assertThat(isVisible).isFalse() + underTest.setVisible(false) + assertThat(isVisible).isFalse() - underTest.setVisible(true) - assertThat(isVisible).isTrue() - } + underTest.setVisible(true) + assertThat(isVisible).isTrue() + } @Test - fun transitionProgress() = runTest { - val underTest = utils.fakeSceneContainerRepository() - val sceneTransitionProgress by collectLastValue(underTest.transitionProgress) - assertThat(sceneTransitionProgress).isEqualTo(1f) - - val transitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(SceneKey.Lockscreen) - ) - underTest.setTransitionState(transitionState) - assertThat(sceneTransitionProgress).isEqualTo(1f) - - val progress = MutableStateFlow(1f) - transitionState.value = - ObservableTransitionState.Transition( - fromScene = SceneKey.Lockscreen, - toScene = SceneKey.Shade, - progress = progress, - ) - assertThat(sceneTransitionProgress).isEqualTo(1f) - - progress.value = 0.1f - assertThat(sceneTransitionProgress).isEqualTo(0.1f) - - progress.value = 0.9f - assertThat(sceneTransitionProgress).isEqualTo(0.9f) - - underTest.setTransitionState(null) - assertThat(sceneTransitionProgress).isEqualTo(1f) - } + fun transitionState_defaultsToIdle() = + testScope.runTest { + val underTest = utils.fakeSceneContainerRepository() + val transitionState by collectLastValue(underTest.transitionState) + + assertThat(transitionState) + .isEqualTo( + ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey) + ) + } @Test - fun setSceneTransition() = runTest { - val underTest = utils.fakeSceneContainerRepository() - val sceneTransition by collectLastValue(underTest.transitions) - assertThat(sceneTransition).isNull() + fun transitionState_reflectsUpdates() = + testScope.runTest { + val underTest = utils.fakeSceneContainerRepository() + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(SceneKey.Lockscreen) + ) + underTest.setTransitionState(transitionState) + val reflectedTransitionState by collectLastValue(underTest.transitionState) + assertThat(reflectedTransitionState).isEqualTo(transitionState.value) + + val progress = MutableStateFlow(1f) + transitionState.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.Lockscreen, + toScene = SceneKey.Shade, + progress = progress, + ) + assertThat(reflectedTransitionState).isEqualTo(transitionState.value) - underTest.setSceneTransition(SceneKey.Lockscreen, SceneKey.QuickSettings) - assertThat(sceneTransition) - .isEqualTo( - SceneTransitionModel(from = SceneKey.Lockscreen, to = SceneKey.QuickSettings) - ) - } + progress.value = 0.1f + assertThat(reflectedTransitionState).isEqualTo(transitionState.value) - @Test(expected = IllegalStateException::class) - fun setSceneTransition_noFromSceneInContainer_throws() { - val underTest = - utils.fakeSceneContainerRepository( - utils.fakeSceneContainerConfig(listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)), - ) - underTest.setSceneTransition(SceneKey.Shade, SceneKey.Lockscreen) - } + progress.value = 0.9f + assertThat(reflectedTransitionState).isEqualTo(transitionState.value) - @Test(expected = IllegalStateException::class) - fun setSceneTransition_noToSceneInContainer_throws() { - val underTest = - utils.fakeSceneContainerRepository( - utils.fakeSceneContainerConfig(listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)), - ) - underTest.setSceneTransition(SceneKey.Shade, SceneKey.Lockscreen) - } + underTest.setTransitionState(null) + assertThat(reflectedTransitionState) + .isEqualTo( + ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey) + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt index 4facc7a6a36d..0a93a7ca465f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt @@ -25,10 +25,12 @@ import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel -import com.android.systemui.scene.shared.model.SceneTransitionModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -39,6 +41,7 @@ import org.junit.runners.JUnit4 class SceneInteractorTest : SysuiTestCase() { private val utils = SceneTestUtils(this) + private val testScope = utils.testScope private val repository = utils.fakeSceneContainerRepository() private val underTest = utils.sceneInteractor(repository = repository) @@ -48,77 +51,156 @@ class SceneInteractorTest : SysuiTestCase() { } @Test - fun currentScene() = runTest { - val currentScene by collectLastValue(underTest.currentScene) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) + fun changeScene() = + testScope.runTest { + val desiredScene by collectLastValue(underTest.desiredScene) + assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) - underTest.setCurrentScene(SceneModel(SceneKey.Shade), "reason") - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade)) - } + underTest.changeScene(SceneModel(SceneKey.Shade), "reason") + assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Shade)) + } + + @Test + fun onSceneChanged() = + testScope.runTest { + val desiredScene by collectLastValue(underTest.desiredScene) + assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) + + underTest.onSceneChanged(SceneModel(SceneKey.Shade), "reason") + assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Shade)) + } @Test - fun sceneTransitionProgress() = runTest { - val transitionProgress by collectLastValue(underTest.transitionProgress) - assertThat(transitionProgress).isEqualTo(1f) + fun transitionState() = + testScope.runTest { + val underTest = utils.fakeSceneContainerRepository() + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(SceneKey.Lockscreen) + ) + underTest.setTransitionState(transitionState) + val reflectedTransitionState by collectLastValue(underTest.transitionState) + assertThat(reflectedTransitionState).isEqualTo(transitionState.value) - val progress = MutableStateFlow(0.55f) - repository.setTransitionState( - MutableStateFlow( + val progress = MutableStateFlow(1f) + transitionState.value = ObservableTransitionState.Transition( fromScene = SceneKey.Lockscreen, toScene = SceneKey.Shade, progress = progress, - ), - ) - ) - assertThat(transitionProgress).isEqualTo(0.55f) - } + ) + assertThat(reflectedTransitionState).isEqualTo(transitionState.value) + + progress.value = 0.1f + assertThat(reflectedTransitionState).isEqualTo(transitionState.value) + + progress.value = 0.9f + assertThat(reflectedTransitionState).isEqualTo(transitionState.value) + + underTest.setTransitionState(null) + assertThat(reflectedTransitionState) + .isEqualTo( + ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey) + ) + } @Test - fun isVisible() = runTest { - val isVisible by collectLastValue(underTest.isVisible) - assertThat(isVisible).isTrue() + fun isVisible() = + testScope.runTest { + val isVisible by collectLastValue(underTest.isVisible) + assertThat(isVisible).isTrue() - underTest.setVisible(false, "reason") - assertThat(isVisible).isFalse() + underTest.setVisible(false, "reason") + assertThat(isVisible).isFalse() - underTest.setVisible(true, "reason") - assertThat(isVisible).isTrue() - } + underTest.setVisible(true, "reason") + assertThat(isVisible).isTrue() + } @Test - fun sceneTransitions() = runTest { - val transitions by collectLastValue(underTest.transitions) - assertThat(transitions).isNull() - - val initialSceneKey = underTest.currentScene.value.key - underTest.setCurrentScene(SceneModel(SceneKey.Shade), "reason") - assertThat(transitions) - .isEqualTo( - SceneTransitionModel( - from = initialSceneKey, - to = SceneKey.Shade, + fun finishedSceneTransitions() = + testScope.runTest { + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(SceneKey.Lockscreen) ) - ) - - underTest.setCurrentScene(SceneModel(SceneKey.QuickSettings), "reason") - assertThat(transitions) - .isEqualTo( - SceneTransitionModel( - from = SceneKey.Shade, - to = SceneKey.QuickSettings, + underTest.setTransitionState(transitionState) + var transitionCount = 0 + val job = launch { + underTest + .finishedSceneTransitions( + from = SceneKey.Shade, + to = SceneKey.QuickSettings, + ) + .collect { transitionCount++ } + } + + assertThat(transitionCount).isEqualTo(0) + + underTest.changeScene(SceneModel(SceneKey.Shade), "reason") + transitionState.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.Lockscreen, + toScene = SceneKey.Shade, + progress = flowOf(0.5f), ) - ) - } + runCurrent() + underTest.onSceneChanged(SceneModel(SceneKey.Shade), "reason") + transitionState.value = ObservableTransitionState.Idle(SceneKey.Shade) + runCurrent() + assertThat(transitionCount).isEqualTo(0) + + underTest.changeScene(SceneModel(SceneKey.QuickSettings), "reason") + transitionState.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.Shade, + toScene = SceneKey.QuickSettings, + progress = flowOf(0.5f), + ) + runCurrent() + underTest.onSceneChanged(SceneModel(SceneKey.QuickSettings), "reason") + transitionState.value = ObservableTransitionState.Idle(SceneKey.QuickSettings) + runCurrent() + assertThat(transitionCount).isEqualTo(1) + + underTest.changeScene(SceneModel(SceneKey.Shade), "reason") + transitionState.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.QuickSettings, + toScene = SceneKey.Shade, + progress = flowOf(0.5f), + ) + runCurrent() + underTest.onSceneChanged(SceneModel(SceneKey.Shade), "reason") + transitionState.value = ObservableTransitionState.Idle(SceneKey.Shade) + runCurrent() + assertThat(transitionCount).isEqualTo(1) + + underTest.changeScene(SceneModel(SceneKey.QuickSettings), "reason") + transitionState.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.Shade, + toScene = SceneKey.QuickSettings, + progress = flowOf(0.5f), + ) + runCurrent() + underTest.onSceneChanged(SceneModel(SceneKey.QuickSettings), "reason") + transitionState.value = ObservableTransitionState.Idle(SceneKey.QuickSettings) + runCurrent() + assertThat(transitionCount).isEqualTo(2) - @Test - fun remoteUserInput() = runTest { - val remoteUserInput by collectLastValue(underTest.remoteUserInput) - assertThat(remoteUserInput).isNull() + job.cancel() + } - for (input in SceneTestUtils.REMOTE_INPUT_DOWN_GESTURE) { - underTest.onRemoteUserInput(input) - assertThat(remoteUserInput).isEqualTo(input) + @Test + fun remoteUserInput() = + testScope.runTest { + val remoteUserInput by collectLastValue(underTest.remoteUserInput) + assertThat(remoteUserInput).isNull() + + for (input in SceneTestUtils.REMOTE_INPUT_DOWN_GESTURE) { + underTest.onRemoteUserInput(input) + assertThat(remoteUserInput).isEqualTo(input) + } } - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 6be19b99dd3b..45db7a0b17f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -21,6 +21,7 @@ package com.android.systemui.scene.domain.startable import android.view.Display import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags import com.android.systemui.keyguard.shared.model.WakeSleepReason @@ -28,15 +29,17 @@ import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.model.SysUiState import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 @@ -76,59 +79,86 @@ class SceneContainerStartableTest : SysuiTestCase() { sceneLogger = mock(), ) - @Before - fun setUp() { - prepareState() - } - @Test fun hydrateVisibility_featureEnabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentDesiredSceneKey by + collectLastValue(sceneInteractor.desiredScene.map { it.key }) val isVisible by collectLastValue(sceneInteractor.isVisible) - prepareState( - isFeatureEnabled = true, - isDeviceUnlocked = true, - initialSceneKey = SceneKey.Gone, - ) - assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) + val transitionStateFlow = + prepareState( + isFeatureEnabled = true, + isDeviceUnlocked = true, + initialSceneKey = SceneKey.Gone, + ) + assertThat(currentDesiredSceneKey).isEqualTo(SceneKey.Gone) assertThat(isVisible).isTrue() underTest.start() - assertThat(isVisible).isFalse() - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "reason") + transitionStateFlow.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.Gone, + toScene = SceneKey.Shade, + progress = flowOf(0.5f), + ) + assertThat(isVisible).isTrue() + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason") + transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Shade) + assertThat(isVisible).isTrue() + + sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason") + transitionStateFlow.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.Shade, + toScene = SceneKey.Gone, + progress = flowOf(0.5f), + ) assertThat(isVisible).isTrue() + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason") + transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone) + assertThat(isVisible).isFalse() } @Test fun hydrateVisibility_featureDisabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentDesiredSceneKey by + collectLastValue(sceneInteractor.desiredScene.map { it.key }) val isVisible by collectLastValue(sceneInteractor.isVisible) - prepareState( - isFeatureEnabled = false, - isDeviceUnlocked = true, - initialSceneKey = SceneKey.Lockscreen, - ) - assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + val transitionStateFlow = + prepareState( + isFeatureEnabled = false, + isDeviceUnlocked = true, + initialSceneKey = SceneKey.Gone, + ) + assertThat(currentDesiredSceneKey).isEqualTo(SceneKey.Gone) assertThat(isVisible).isTrue() underTest.start() + assertThat(isVisible).isTrue() - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "reason") + transitionStateFlow.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.Gone, + toScene = SceneKey.Shade, + progress = flowOf(0.5f), + ) assertThat(isVisible).isTrue() - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason") + transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Shade) assertThat(isVisible).isTrue() } @Test fun switchToLockscreenWhenDeviceLocks_featureEnabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = true, isDeviceUnlocked = true, @@ -145,7 +175,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchToLockscreenWhenDeviceLocks_featureDisabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = false, isDeviceUnlocked = false, @@ -162,7 +192,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchFromBouncerToGoneWhenDeviceUnlocked_featureEnabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = true, isDeviceUnlocked = false, @@ -179,7 +209,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchFromBouncerToGoneWhenDeviceUnlocked_featureDisabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = false, isDeviceUnlocked = false, @@ -196,7 +226,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOn() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = true, isBypassEnabled = true, @@ -213,7 +243,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOff() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = true, isBypassEnabled = false, @@ -230,7 +260,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOff_bypassOn() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = false, isBypassEnabled = true, @@ -245,43 +275,9 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test - fun switchToGoneWhenDeviceSleepsUnlocked_featureEnabled() = - testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) - prepareState( - isFeatureEnabled = true, - isDeviceUnlocked = true, - initialSceneKey = SceneKey.Shade, - ) - assertThat(currentSceneKey).isEqualTo(SceneKey.Shade) - underTest.start() - - keyguardRepository.setWakefulnessModel(ASLEEP) - - assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) - } - - @Test - fun switchToGoneWhenDeviceSleepsUnlocked_featureDisabled() = - testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) - prepareState( - isFeatureEnabled = false, - isDeviceUnlocked = true, - initialSceneKey = SceneKey.Shade, - ) - assertThat(currentSceneKey).isEqualTo(SceneKey.Shade) - underTest.start() - - keyguardRepository.setWakefulnessModel(ASLEEP) - - assertThat(currentSceneKey).isEqualTo(SceneKey.Shade) - } - - @Test fun switchToLockscreenWhenDeviceSleepsLocked_featureEnabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = true, isDeviceUnlocked = false, @@ -290,7 +286,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(SceneKey.Shade) underTest.start() - keyguardRepository.setWakefulnessModel(ASLEEP) + keyguardRepository.setWakefulnessModel(STARTING_TO_SLEEP) assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) } @@ -298,7 +294,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchToLockscreenWhenDeviceSleepsLocked_featureDisabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = false, isDeviceUnlocked = false, @@ -307,7 +303,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(SceneKey.Shade) underTest.start() - keyguardRepository.setWakefulnessModel(ASLEEP) + keyguardRepository.setWakefulnessModel(STARTING_TO_SLEEP) assertThat(currentSceneKey).isEqualTo(SceneKey.Shade) } @@ -315,6 +311,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun hydrateSystemUiState() = testScope.runTest { + val transitionStateFlow = prepareState() underTest.start() runCurrent() clearInvocations(sysUiState) @@ -327,29 +324,110 @@ class SceneContainerStartableTest : SysuiTestCase() { SceneKey.QuickSettings, ) .forEachIndexed { index, sceneKey -> - sceneInteractor.setCurrentScene(SceneModel(sceneKey), "reason") + sceneInteractor.changeScene(SceneModel(sceneKey), "reason") + runCurrent() + verify(sysUiState, times(index)).commitUpdate(Display.DEFAULT_DISPLAY) + + sceneInteractor.onSceneChanged(SceneModel(sceneKey), "reason") runCurrent() + verify(sysUiState, times(index)).commitUpdate(Display.DEFAULT_DISPLAY) + transitionStateFlow.value = ObservableTransitionState.Idle(sceneKey) + runCurrent() verify(sysUiState, times(index + 1)).commitUpdate(Display.DEFAULT_DISPLAY) } } + @Test + fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone_featureEnabled() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) + prepareState( + isFeatureEnabled = true, + initialSceneKey = SceneKey.Lockscreen, + authenticationMethod = AuthenticationMethodModel.None, + ) + assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + underTest.start() + + keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE) + + assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) + } + + @Test + fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNotNone_featureEnabled() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) + prepareState( + isFeatureEnabled = true, + initialSceneKey = SceneKey.Lockscreen, + authenticationMethod = AuthenticationMethodModel.Pin, + ) + assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + underTest.start() + + keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE) + + assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + } + + @Test + fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone_featureDisabled() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) + prepareState( + isFeatureEnabled = false, + initialSceneKey = SceneKey.Lockscreen, + authenticationMethod = AuthenticationMethodModel.None, + ) + assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + underTest.start() + + keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE) + + assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + } + private fun prepareState( isFeatureEnabled: Boolean = true, isDeviceUnlocked: Boolean = false, isBypassEnabled: Boolean = false, initialSceneKey: SceneKey? = null, - ) { + authenticationMethod: AuthenticationMethodModel? = null, + ): MutableStateFlow<ObservableTransitionState> { featureFlags.set(Flags.SCENE_CONTAINER, isFeatureEnabled) authenticationRepository.setUnlocked(isDeviceUnlocked) keyguardRepository.setBypassEnabled(isBypassEnabled) - initialSceneKey?.let { sceneInteractor.setCurrentScene(SceneModel(it), "reason") } + val transitionStateFlow = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(SceneKey.Lockscreen) + ) + sceneInteractor.setTransitionState(transitionStateFlow) + initialSceneKey?.let { + transitionStateFlow.value = ObservableTransitionState.Idle(it) + sceneInteractor.changeScene(SceneModel(it), "reason") + sceneInteractor.onSceneChanged(SceneModel(it), "reason") + } + authenticationMethod?.let { + authenticationRepository.setAuthenticationMethod(authenticationMethod) + authenticationRepository.setLockscreenEnabled( + authenticationMethod != AuthenticationMethodModel.None + ) + } + return transitionStateFlow } companion object { - private val ASLEEP = + private val STARTING_TO_SLEEP = + WakefulnessModel( + state = WakefulnessState.STARTING_TO_SLEEP, + lastWakeReason = WakeSleepReason.POWER_BUTTON, + lastSleepReason = WakeSleepReason.POWER_BUTTON + ) + private val STARTING_TO_WAKE = WakefulnessModel( - state = WakefulnessState.ASLEEP, + state = WakefulnessState.STARTING_TO_WAKE, lastWakeReason = WakeSleepReason.POWER_BUTTON, lastSleepReason = WakeSleepReason.POWER_BUTTON ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt index 9f3b12bd2042..da6c42694666 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt @@ -69,7 +69,8 @@ class SceneContainerViewModelTest : SysuiTestCase() { val currentScene by collectLastValue(underTest.currentScene) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) - underTest.setCurrentScene(SceneModel(SceneKey.Shade)) + underTest.onSceneChanged(SceneModel(SceneKey.Shade)) + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade)) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt index 7ba2cf7f6374..2d3ee0e5cff9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt @@ -20,12 +20,13 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.net.Uri +import androidx.test.ext.truth.content.IntentSubject.assertThat import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock -import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.assertWithMessage import org.junit.Test import org.mockito.Mockito.`when` as whenever @@ -33,113 +34,132 @@ import org.mockito.Mockito.`when` as whenever class ActionIntentCreatorTest : SysuiTestCase() { @Test - fun testCreateShareIntent() { + fun testCreateShare() { val uri = Uri.parse("content://fake") - val output = ActionIntentCreator.createShareIntent(uri) + val output = ActionIntentCreator.createShare(uri) - assertThat(output.action).isEqualTo(Intent.ACTION_CHOOSER) - assertFlagsSet( - Intent.FLAG_ACTIVITY_NEW_TASK or - Intent.FLAG_ACTIVITY_CLEAR_TASK or - Intent.FLAG_GRANT_READ_URI_PERMISSION, - output.flags - ) + assertThat(output).hasAction(Intent.ACTION_CHOOSER) + assertThat(output) + .hasFlags( + Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TASK or + Intent.FLAG_GRANT_READ_URI_PERMISSION + ) + assertThat(output).extras().parcelable<Intent>(Intent.EXTRA_INTENT).isNotNull() val wrappedIntent = output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java) - assertThat(wrappedIntent?.action).isEqualTo(Intent.ACTION_SEND) - assertThat(wrappedIntent?.data).isEqualTo(uri) - assertThat(wrappedIntent?.type).isEqualTo("image/png") - assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_SUBJECT)).isNull() - assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_TEXT)).isNull() - assertThat(wrappedIntent?.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)) - .isEqualTo(uri) + + assertThat(wrappedIntent).hasAction(Intent.ACTION_SEND) + assertThat(wrappedIntent).hasData(uri) + assertThat(wrappedIntent).hasType("image/png") + assertThat(wrappedIntent).extras().doesNotContainKey(Intent.EXTRA_SUBJECT) + assertThat(wrappedIntent).extras().doesNotContainKey(Intent.EXTRA_TEXT) + assertThat(wrappedIntent).extras().parcelable<Uri>(Intent.EXTRA_STREAM).isEqualTo(uri) } @Test - fun testCreateShareIntentWithSubject() { + fun testCreateShare_embeddedUserIdRemoved() { + val uri = Uri.parse("content://555@fake") + + val output = ActionIntentCreator.createShare(uri) + + assertThat(output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)) + .hasData(Uri.parse("content://fake")) + } + + @Test + fun testCreateShareWithSubject() { val uri = Uri.parse("content://fake") val subject = "Example subject" - val output = ActionIntentCreator.createShareIntentWithSubject(uri, subject) + val output = ActionIntentCreator.createShareWithSubject(uri, subject) - assertThat(output.action).isEqualTo(Intent.ACTION_CHOOSER) - assertFlagsSet( - Intent.FLAG_ACTIVITY_NEW_TASK or - Intent.FLAG_ACTIVITY_CLEAR_TASK or - Intent.FLAG_GRANT_READ_URI_PERMISSION, - output.flags - ) + assertThat(output).hasAction(Intent.ACTION_CHOOSER) + assertThat(output) + .hasFlags( + Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TASK or + Intent.FLAG_GRANT_READ_URI_PERMISSION + ) val wrappedIntent = output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java) - assertThat(wrappedIntent?.action).isEqualTo(Intent.ACTION_SEND) - assertThat(wrappedIntent?.data).isEqualTo(uri) - assertThat(wrappedIntent?.type).isEqualTo("image/png") - assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_SUBJECT)).isEqualTo(subject) - assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_TEXT)).isNull() - assertThat(wrappedIntent?.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)) - .isEqualTo(uri) + assertThat(wrappedIntent).hasAction(Intent.ACTION_SEND) + assertThat(wrappedIntent).hasData(uri) + assertThat(wrappedIntent).hasType("image/png") + assertThat(wrappedIntent).extras().string(Intent.EXTRA_SUBJECT).isEqualTo(subject) + assertThat(wrappedIntent).extras().doesNotContainKey(Intent.EXTRA_TEXT) + assertThat(wrappedIntent).extras().parcelable<Uri>(Intent.EXTRA_STREAM).isEqualTo(uri) } @Test - fun testCreateShareIntentWithExtraText() { + fun testCreateShareWithText() { val uri = Uri.parse("content://fake") val extraText = "Extra text" - val output = ActionIntentCreator.createShareIntentWithExtraText(uri, extraText) + val output = ActionIntentCreator.createShareWithText(uri, extraText) - assertThat(output.action).isEqualTo(Intent.ACTION_CHOOSER) - assertFlagsSet( - Intent.FLAG_ACTIVITY_NEW_TASK or - Intent.FLAG_ACTIVITY_CLEAR_TASK or - Intent.FLAG_GRANT_READ_URI_PERMISSION, - output.flags - ) + assertThat(output).hasAction(Intent.ACTION_CHOOSER) + assertThat(output) + .hasFlags( + Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TASK or + Intent.FLAG_GRANT_READ_URI_PERMISSION + ) val wrappedIntent = output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java) - assertThat(wrappedIntent?.action).isEqualTo(Intent.ACTION_SEND) - assertThat(wrappedIntent?.data).isEqualTo(uri) - assertThat(wrappedIntent?.type).isEqualTo("image/png") - assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_SUBJECT)).isNull() - assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_TEXT)).isEqualTo(extraText) - assertThat(wrappedIntent?.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)) - .isEqualTo(uri) + assertThat(wrappedIntent).hasAction(Intent.ACTION_SEND) + assertThat(wrappedIntent).hasData(uri) + assertThat(wrappedIntent).hasType("image/png") + assertThat(wrappedIntent).extras().doesNotContainKey(Intent.EXTRA_SUBJECT) + assertThat(wrappedIntent).extras().string(Intent.EXTRA_TEXT).isEqualTo(extraText) + assertThat(wrappedIntent).extras().parcelable<Uri>(Intent.EXTRA_STREAM).isEqualTo(uri) } @Test - fun testCreateEditIntent() { + fun testCreateEdit() { val uri = Uri.parse("content://fake") val context = mock<Context>() - val output = ActionIntentCreator.createEditIntent(uri, context) + whenever(context.getString(eq(R.string.config_screenshotEditor))).thenReturn("") + + val output = ActionIntentCreator.createEdit(uri, context) + + assertThat(output).hasAction(Intent.ACTION_EDIT) + assertThat(output).hasData(uri) + assertThat(output).hasType("image/png") + assertWithMessage("getComponent()").that(output.component).isNull() + assertThat(output) + .hasFlags( + Intent.FLAG_GRANT_READ_URI_PERMISSION or + Intent.FLAG_GRANT_WRITE_URI_PERMISSION or + Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TASK + ) + } - assertThat(output.action).isEqualTo(Intent.ACTION_EDIT) - assertThat(output.data).isEqualTo(uri) - assertThat(output.type).isEqualTo("image/png") - assertThat(output.component).isNull() - val expectedFlags = - Intent.FLAG_GRANT_READ_URI_PERMISSION or - Intent.FLAG_GRANT_WRITE_URI_PERMISSION or - Intent.FLAG_ACTIVITY_NEW_TASK or - Intent.FLAG_ACTIVITY_CLEAR_TASK - assertFlagsSet(expectedFlags, output.flags) + @Test + fun testCreateEdit_embeddedUserIdRemoved() { + val uri = Uri.parse("content://555@fake") + val context = mock<Context>() + whenever(context.getString(eq(R.string.config_screenshotEditor))).thenReturn("") + + val output = ActionIntentCreator.createEdit(uri, context) + + assertThat(output).hasData(Uri.parse("content://fake")) } @Test - fun testCreateEditIntent_withEditor() { + fun testCreateEdit_withEditor() { val uri = Uri.parse("content://fake") val context = mock<Context>() - var component = ComponentName("com.android.foo", "com.android.foo.Something") + val component = ComponentName("com.android.foo", "com.android.foo.Something") whenever(context.getString(eq(R.string.config_screenshotEditor))) .thenReturn(component.flattenToString()) - val output = ActionIntentCreator.createEditIntent(uri, context) - - assertThat(output.component).isEqualTo(component) - } + val output = ActionIntentCreator.createEdit(uri, context) - private fun assertFlagsSet(expected: Int, observed: Int) { - assertThat(observed and expected).isEqualTo(expected) + assertThat(output).hasComponent(component) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index c3540cfec72d..981e44bea846 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -424,6 +424,10 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame); when(mView.findViewById(R.id.keyguard_status_view)) .thenReturn(mock(KeyguardStatusView.class)); + View rootView = mock(View.class); + when(mView.getRootView()).thenReturn(rootView); + when(rootView.findViewById(R.id.keyguard_status_view)) + .thenReturn(mock(KeyguardStatusView.class)); mNotificationContainerParent = new NotificationsQuickSettingsContainer(getContext(), null); mNotificationContainerParent.addView(keyguardStatusView); mNotificationContainerParent.onFinishInflate(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt index 2bc112d68ae2..112a09bcfe62 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt @@ -144,27 +144,52 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { @Test fun testSmallScreen_updateResources_splitShadeHeightIsSet() { overrideResource(R.bool.config_use_large_screen_shade_header, false) - overrideResource(R.dimen.qs_header_height, 1) - overrideResource(R.dimen.large_screen_shade_header_height, 2) + overrideResource(R.dimen.qs_header_height, 10) + overrideResource(R.dimen.large_screen_shade_header_height, 20) + + // ensure the estimated height (would be 3 here) wouldn't impact this test case + overrideResource(R.dimen.large_screen_shade_header_min_height, 1) + overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 1) underTest.updateResources() val captor = ArgumentCaptor.forClass(ConstraintSet::class.java) verify(view).applyConstraints(capture(captor)) - assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(1) + assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(10) } @Test fun testLargeScreen_updateResources_splitShadeHeightIsSet() { overrideResource(R.bool.config_use_large_screen_shade_header, true) - overrideResource(R.dimen.qs_header_height, 1) - overrideResource(R.dimen.large_screen_shade_header_height, 2) + overrideResource(R.dimen.qs_header_height, 10) + overrideResource(R.dimen.large_screen_shade_header_height, 20) + + // ensure the estimated height (would be 3 here) wouldn't impact this test case + overrideResource(R.dimen.large_screen_shade_header_min_height, 1) + overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 1) underTest.updateResources() val captor = ArgumentCaptor.forClass(ConstraintSet::class.java) verify(view).applyConstraints(capture(captor)) - assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(2) + assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(20) + } + + @Test + fun testSmallScreen_estimatedHeightIsLargerThanDimenValue_shadeHeightIsSetToEstimatedHeight() { + overrideResource(R.bool.config_use_large_screen_shade_header, false) + overrideResource(R.dimen.qs_header_height, 10) + overrideResource(R.dimen.large_screen_shade_header_height, 20) + + // make the estimated height (would be 15 here) larger than qs_header_height + overrideResource(R.dimen.large_screen_shade_header_min_height, 5) + overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 5) + + underTest.updateResources() + + val captor = ArgumentCaptor.forClass(ConstraintSet::class.java) + verify(view).applyConstraints(capture(captor)) + assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(15) } @Test @@ -388,6 +413,10 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { val largeScreenHeaderHeight = 100 overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderHeight) + // ensure the estimated height (would be 30 here) wouldn't impact this test case + overrideResource(R.dimen.large_screen_shade_header_min_height, 10) + overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 10) + underTest.updateResources() assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt index a5048187b1b4..8d3c4b21aa26 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt @@ -143,27 +143,52 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { @Test fun testSmallScreen_updateResources_splitShadeHeightIsSet() { overrideResource(R.bool.config_use_large_screen_shade_header, false) - overrideResource(R.dimen.qs_header_height, 1) - overrideResource(R.dimen.large_screen_shade_header_height, 2) + overrideResource(R.dimen.qs_header_height, 10) + overrideResource(R.dimen.large_screen_shade_header_height, 20) + + // ensure the estimated height (would be 3 here) wouldn't impact this test case + overrideResource(R.dimen.large_screen_shade_header_min_height, 1) + overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 1) underTest.updateResources() val captor = ArgumentCaptor.forClass(ConstraintSet::class.java) verify(view).applyConstraints(capture(captor)) - assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(1) + assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(10) } @Test fun testLargeScreen_updateResources_splitShadeHeightIsSet() { overrideResource(R.bool.config_use_large_screen_shade_header, true) - overrideResource(R.dimen.qs_header_height, 1) - overrideResource(R.dimen.large_screen_shade_header_height, 2) + overrideResource(R.dimen.qs_header_height, 10) + overrideResource(R.dimen.large_screen_shade_header_height, 20) + + // ensure the estimated height (would be 3 here) wouldn't impact this test case + overrideResource(R.dimen.large_screen_shade_header_min_height, 1) + overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 1) underTest.updateResources() val captor = ArgumentCaptor.forClass(ConstraintSet::class.java) verify(view).applyConstraints(capture(captor)) - assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(2) + assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(20) + } + + @Test + fun testSmallScreen_estimatedHeightIsLargerThanDimenValue_shadeHeightIsSetToEstimatedHeight() { + overrideResource(R.bool.config_use_large_screen_shade_header, false) + overrideResource(R.dimen.qs_header_height, 10) + overrideResource(R.dimen.large_screen_shade_header_height, 20) + + // make the estimated height (would be 15 here) larger than qs_header_height + overrideResource(R.dimen.large_screen_shade_header_min_height, 5) + overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 5) + + underTest.updateResources() + + val captor = ArgumentCaptor.forClass(ConstraintSet::class.java) + verify(view).applyConstraints(capture(captor)) + assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(15) } @Test @@ -376,6 +401,10 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { val largeScreenHeaderHeight = 100 overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderHeight) + // ensure the estimated height (would be 30 here) wouldn't impact this test case + overrideResource(R.dimen.large_screen_shade_header_min_height, 10) + overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 10) + underTest.updateResources() assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index 8739b28c940e..7443097a2628 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -18,7 +18,7 @@ package com.android.systemui.shade.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey @@ -47,14 +47,11 @@ class ShadeSceneViewModelTest : SysuiTestCase() { private val underTest = ShadeSceneViewModel( applicationScope = testScope.backgroundScope, - lockscreenSceneInteractor = - utils.lockScreenSceneInteractor( + authenticationInteractor = authenticationInteractor, + bouncerInteractor = + utils.bouncerInteractor( authenticationInteractor = authenticationInteractor, - bouncerInteractor = - utils.bouncerInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, - ), + sceneInteractor = sceneInteractor, ), ) @@ -81,7 +78,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { @Test fun onContentClicked_deviceUnlocked_switchesToGone() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) runCurrent() @@ -94,7 +91,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { @Test fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shadow/DoubleShadowTextClockTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shadow/DoubleShadowTextClockTest.kt new file mode 100644 index 000000000000..f5a24ff0c731 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shadow/DoubleShadowTextClockTest.kt @@ -0,0 +1,88 @@ +/* + * 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.shadow + +import android.content.Context +import android.content.res.Resources +import android.content.res.TypedArray +import android.testing.AndroidTestingRunner +import android.util.AttributeSet +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.shared.shadow.DoubleShadowTextClock +import com.android.systemui.util.mockito.whenever +import junit.framework.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class DoubleShadowTextClockTest : SysuiTestCase() { + @get:Rule val mockito: MockitoRule = MockitoJUnit.rule() + + @Mock lateinit var resources: Resources + + @Mock lateinit var attributes: TypedArray + + private lateinit var context: Context + private var attrs: AttributeSet? = null + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + context = getContext() + whenever(attributes.getBoolean(R.styleable.DoubleShadowTextClock_removeTextDescent, false)) + .thenReturn(true) + } + + @Test + fun testAddingPaddingToBottomOfClockWhenConfigIsTrue() { + whenever(resources.getBoolean(R.bool.dream_overlay_complication_clock_bottom_padding)) + .thenReturn(true) + + val doubleShadowTextClock = + DoubleShadowTextClock( + resources = resources, + context = context, + attrs = attrs, + attributesInput = attributes + ) + assertTrue(doubleShadowTextClock.paddingBottom > 0) + } + + @Test + fun testRemovingPaddingToBottomOfClockWhenConfigIsFalse() { + whenever(resources.getBoolean(R.bool.dream_overlay_complication_clock_bottom_padding)) + .thenReturn(false) + + val doubleShadowTextClock = + DoubleShadowTextClock( + resources = resources, + context = context, + attrs = attrs, + attributesInput = attributes + ) + assertTrue(doubleShadowTextClock.paddingBottom < 0) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt index 0cfca614a256..2e223f6d8c1f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt @@ -24,9 +24,9 @@ import android.util.Pair import android.view.Gravity import android.view.View import android.widget.FrameLayout -import androidx.core.animation.AnimatorTestRule import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.AnimatorTestRule import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.statusbar.phone.StatusBarContentInsetsChangedListener import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt index 2af0cebf3519..414256fb1d5b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt @@ -22,9 +22,9 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.View import android.widget.FrameLayout -import androidx.core.animation.AnimatorTestRule import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.AnimatorTestRule import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt index 78c0982df414..40edea2149ce 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt @@ -18,9 +18,9 @@ package com.android.systemui.statusbar.notification import android.testing.AndroidTestingRunner import android.testing.TestableLooper -import androidx.core.animation.AnimatorTestRule import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.AnimatorTestRule import com.android.systemui.dump.DumpManager import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.plugins.statusbar.StatusBarStateController diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt index 764005b81a5d..0cc0b987aa34 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt @@ -17,6 +17,10 @@ package com.android.systemui.statusbar.notification.row +import android.app.Notification +import android.net.Uri +import android.os.UserHandle +import android.os.UserHandle.USER_ALL import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest @@ -29,13 +33,17 @@ import com.android.systemui.flags.FeatureFlags import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.PluginManager import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.statusbar.SbnBuilder import com.android.systemui.statusbar.SmartReplyController +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider import com.android.systemui.statusbar.notification.collection.render.FakeNodeController import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager import com.android.systemui.statusbar.notification.logging.NotificationLogger import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController.BUBBLES_SETTING_URI import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger import com.android.systemui.statusbar.notification.stack.NotificationListContainer @@ -46,9 +54,9 @@ import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.withArgCaptor import com.android.systemui.util.time.SystemClock import com.android.systemui.wmshell.BubblesManager -import java.util.Optional import junit.framework.Assert import org.junit.After import org.junit.Before @@ -56,9 +64,11 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito import org.mockito.Mockito.anyBoolean +import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever +import java.util.Optional @SmallTest @RunWith(AndroidTestingRunner::class) @@ -94,10 +104,10 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { private val featureFlags: FeatureFlags = mock() private val peopleNotificationIdentifier: PeopleNotificationIdentifier = mock() private val bubblesManager: BubblesManager = mock() + private val settingsController: NotificationSettingsController = mock() private val dragController: ExpandableNotificationRowDragController = mock() private val dismissibilityProvider: NotificationDismissibilityProvider = mock() private val statusBarService: IStatusBarService = mock() - private lateinit var controller: ExpandableNotificationRowController @Before @@ -134,11 +144,16 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { featureFlags, peopleNotificationIdentifier, Optional.of(bubblesManager), + settingsController, dragController, dismissibilityProvider, statusBarService ) whenever(view.childrenContainer).thenReturn(childrenContainer) + + val notification = Notification.Builder(mContext).build() + val sbn = SbnBuilder().setNotification(notification).build() + whenever(view.entry).thenReturn(NotificationEntryBuilder().setSbn(sbn).build()) } @After @@ -206,4 +221,74 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { verify(view).removeChildNotification(eq(childView)) verify(listContainer).notifyGroupChildRemoved(eq(childView), eq(childrenContainer)) } + + @Test + fun registerSettingsListener_forBubbles() { + controller.init(mock(NotificationEntry::class.java)) + val viewStateObserver = withArgCaptor { + verify(view).addOnAttachStateChangeListener(capture()); + } + viewStateObserver.onViewAttachedToWindow(view); + verify(settingsController).addCallback(any(), any()); + } + + @Test + fun unregisterSettingsListener_forBubbles() { + controller.init(mock(NotificationEntry::class.java)) + val viewStateObserver = withArgCaptor { + verify(view).addOnAttachStateChangeListener(capture()); + } + viewStateObserver.onViewDetachedFromWindow(view); + verify(settingsController).removeCallback(any(), any()); + } + + @Test + fun settingsListener_invalidUri() { + controller.mSettingsListener.onSettingChanged(Uri.EMPTY, view.entry.sbn.userId, "1") + + verify(view, never()).getPrivateLayout() + } + + @Test + fun settingsListener_invalidUserId() { + controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, -1000, "1") + controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, -1000, null) + + verify(view, never()).getPrivateLayout() + } + + @Test + fun settingsListener_validUserId() { + val childView: NotificationContentView = mock() + whenever(view.privateLayout).thenReturn(childView) + + controller.mSettingsListener.onSettingChanged( + BUBBLES_SETTING_URI, view.entry.sbn.userId, "1") + verify(childView).setBubblesEnabledForUser(true) + + controller.mSettingsListener.onSettingChanged( + BUBBLES_SETTING_URI, view.entry.sbn.userId, "9") + verify(childView).setBubblesEnabledForUser(false) + } + + @Test + fun settingsListener_userAll() { + val childView: NotificationContentView = mock() + whenever(view.privateLayout).thenReturn(childView) + + val notification = Notification.Builder(mContext).build() + val sbn = SbnBuilder().setNotification(notification) + .setUser(UserHandle.of(USER_ALL)) + .build() + whenever(view.entry).thenReturn(NotificationEntryBuilder() + .setSbn(sbn) + .setUser(UserHandle.of(USER_ALL)) + .build()) + + controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, 9, "1") + verify(childView).setBubblesEnabledForUser(true) + + controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, 1, "0") + verify(childView).setBubblesEnabledForUser(false) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt index 0b90ebec3ec6..c4baa691e612 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt @@ -250,6 +250,9 @@ class NotificationContentViewTest : SysuiTestCase() { .thenReturn(actionListMarginTarget) view.setContainingNotification(mockContainingNotification) + // Given: controller says bubbles are enabled for the user + view.setBubblesEnabledForUser(true); + // When: call NotificationContentView.setExpandedChild() to set the expandedChild view.expandedChild = mockExpandedChild @@ -305,6 +308,12 @@ class NotificationContentViewTest : SysuiTestCase() { // NotificationEntry, which should show bubble button view.onNotificationUpdated(createMockNotificationEntry(true)) + // Then: no bubble yet + assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget)) + + // Given: controller says bubbles are enabled for the user + view.setBubblesEnabledForUser(true); + // Then: bottom margin of actionListMarginTarget should not change, still be 20 assertEquals(0, getMarginBottom(actionListMarginTarget)) } @@ -405,7 +414,6 @@ class NotificationContentViewTest : SysuiTestCase() { val userMock: UserHandle = mock() whenever(this.sbn).thenReturn(sbnMock) whenever(sbnMock.user).thenReturn(userMock) - doReturn(showButton).whenever(view).shouldShowBubbleButton(this) } private fun createLinearLayoutWithBottomMargin(bottomMargin: Int): LinearLayout { 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 90adabfadd5d..596e9a2613d7 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 @@ -62,6 +62,7 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.Handler; import android.os.UserHandle; +import android.os.UserManager; import android.service.notification.StatusBarNotification; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; @@ -132,6 +133,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase { @Mock private PackageManager mMockPackageManager; @Mock + private UserManager mUserManager; + @Mock private OnUserInteractionCallback mOnUserInteractionCallback; @Mock private BubblesManager mBubblesManager; @@ -238,6 +241,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -262,6 +266,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -314,6 +319,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -339,6 +345,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -363,6 +370,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -398,6 +406,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -423,6 +432,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -452,6 +462,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -476,6 +487,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -504,6 +516,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -532,6 +545,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -563,6 +577,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -600,6 +615,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -628,6 +644,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -663,6 +680,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -691,6 +709,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -735,6 +754,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -778,6 +798,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -822,6 +843,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -860,6 +882,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -896,6 +919,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -936,6 +960,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -967,6 +992,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -996,6 +1022,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -1033,6 +1060,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -1069,6 +1097,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -1104,6 +1133,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -1143,6 +1173,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -1173,6 +1204,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -1198,6 +1230,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -1219,11 +1252,13 @@ public class NotificationConversationInfoTest extends SysuiTestCase { @Test public void testSelectPriorityRequestsPinPeopleTile() { + when(mUserManager.isSameProfileGroup(anyInt(), anyInt())).thenReturn(true); //WHEN channel is default importance mNotificationChannel.setImportantConversation(false); mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -1250,10 +1285,45 @@ public class NotificationConversationInfoTest extends SysuiTestCase { } @Test + public void testSelectPriorityRequestsPinPeopleTile_noMultiuser() { + when(mUserManager.isSameProfileGroup(anyInt(), anyInt())).thenReturn(false); + //WHEN channel is default importance + mNotificationChannel.setImportantConversation(false); + mNotificationInfo.bindNotification( + mShortcutManager, + mMockPackageManager, + mUserManager, + mPeopleSpaceWidgetManager, + mMockINotificationManager, + mOnUserInteractionCallback, + TEST_PACKAGE_NAME, + mNotificationChannel, + mEntry, + mBubbleMetadata, + null, + mIconFactory, + mContext, + true, + mTestHandler, + mTestHandler, null, Optional.of(mBubblesManager), + mShadeController); + + // WHEN user clicks "priority" + mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE); + + // and then done + mNotificationInfo.findViewById(R.id.done).performClick(); + + // No widget prompt; on a secondary user + verify(mPeopleSpaceWidgetManager, never()).requestPinAppWidget(any(), any()); + } + + @Test public void testSelectDefaultDoesNotRequestPinPeopleTile() { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, @@ -1288,6 +1358,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mNotificationInfo.bindNotification( mShortcutManager, mMockPackageManager, + mUserManager, mPeopleSpaceWidgetManager, mMockINotificationManager, mOnUserInteractionCallback, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index 3cefc9973d09..705d52bcf13f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -52,6 +52,7 @@ import android.content.pm.ShortcutManager; import android.graphics.Color; import android.os.Binder; import android.os.Handler; +import android.os.UserManager; import android.provider.Settings; import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; @@ -137,6 +138,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase { @Mock private HeadsUpManagerPhone mHeadsUpManagerPhone; @Mock private ActivityStarter mActivityStarter; + @Mock private UserManager mUserManager; + @Before public void setUp() { mTestableLooper = TestableLooper.get(this); @@ -147,7 +150,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { mGutsManager = new NotificationGutsManager(mContext, mHandler, mHandler, mAccessibilityManager, - mHighPriorityProvider, mINotificationManager, + mHighPriorityProvider, mINotificationManager, mUserManager, mPeopleSpaceWidgetManager, mLauncherApps, mShortcutManager, mChannelEditorDialogController, mContextTracker, mAssistantFeedbackController, Optional.of(mBubblesManager), new UiEventLoggerFake(), mOnUserInteractionCallback, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt new file mode 100644 index 000000000000..614995b9bf46 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2023 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.app.ActivityManager +import android.database.ContentObserver +import android.net.Uri +import android.os.Handler +import android.provider.Settings.Secure +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.settings.UserTracker +import com.android.systemui.statusbar.notification.row.NotificationSettingsController.Listener +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.settings.SecureSettings +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.anyBoolean +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class NotificationSettingsControllerTest : SysuiTestCase() { + + val setting1: String = Secure.NOTIFICATION_BUBBLES + val setting2: String = Secure.ACCESSIBILITY_ENABLED + val settingUri1: Uri = Secure.getUriFor(setting1) + val settingUri2: Uri = Secure.getUriFor(setting2) + + @Mock + private lateinit var userTracker: UserTracker + private lateinit var mainHandler: Handler + private lateinit var backgroundHandler: Handler + private lateinit var testableLooper: TestableLooper + @Mock + private lateinit var secureSettings: SecureSettings + @Mock + private lateinit var dumpManager: DumpManager + + @Captor + private lateinit var userTrackerCallbackCaptor: ArgumentCaptor<UserTracker.Callback> + @Captor + private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver> + + private lateinit var controller: NotificationSettingsController + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + testableLooper = TestableLooper.get(this) + mainHandler = Handler(testableLooper.looper) + backgroundHandler = Handler(testableLooper.looper) + allowTestableLooperAsMainThread() + controller = + NotificationSettingsController( + userTracker, + mainHandler, + backgroundHandler, + secureSettings, + dumpManager + ) + } + + @After + fun tearDown() { + disallowTestableLooperAsMainThread() + } + + @Test + fun creationRegistersCallbacks() { + verify(userTracker).addCallback(any(), any()) + verify(dumpManager).registerNormalDumpable(anyString(), eq(controller)) + } + @Test + fun updateContentObserverRegistration_onUserChange_noSettingsListeners() { + verify(userTracker).addCallback(capture(userTrackerCallbackCaptor), any()) + val userCallback = userTrackerCallbackCaptor.value + val userId = 9 + + // When: User is changed + userCallback.onUserChanged(userId, context) + + // Validate: Nothing to do, since we aren't monitoring settings + verify(secureSettings, never()).unregisterContentObserver(any()) + verify(secureSettings, never()).registerContentObserverForUser( + any(Uri::class.java), anyBoolean(), any(), anyInt()) + } + @Test + fun updateContentObserverRegistration_onUserChange_withSettingsListeners() { + // When: someone is listening to a setting + controller.addCallback(settingUri1, + Mockito.mock(Listener::class.java)) + + verify(userTracker).addCallback(capture(userTrackerCallbackCaptor), any()) + val userCallback = userTrackerCallbackCaptor.value + val userId = 9 + + // Then: User is changed + userCallback.onUserChanged(userId, context) + + // Validate: The tracker is unregistered and re-registered with the new user + verify(secureSettings).unregisterContentObserver(any()) + verify(secureSettings).registerContentObserverForUser( + eq(settingUri1), eq(false), any(), eq(userId)) + } + + @Test + fun addCallback_onlyFirstForUriRegistersObserver() { + controller.addCallback(settingUri1, + Mockito.mock(Listener::class.java)) + verify(secureSettings).registerContentObserverForUser( + eq(settingUri1), eq(false), any(), eq(ActivityManager.getCurrentUser())) + + controller.addCallback(settingUri1, + Mockito.mock(Listener::class.java)) + verify(secureSettings).registerContentObserverForUser( + any(Uri::class.java), anyBoolean(), any(), anyInt()) + } + + @Test + fun addCallback_secondUriRegistersObserver() { + controller.addCallback(settingUri1, + Mockito.mock(Listener::class.java)) + verify(secureSettings).registerContentObserverForUser( + eq(settingUri1), eq(false), any(), eq(ActivityManager.getCurrentUser())) + + controller.addCallback(settingUri2, + Mockito.mock(Listener::class.java)) + verify(secureSettings).registerContentObserverForUser( + eq(settingUri2), eq(false), any(), eq(ActivityManager.getCurrentUser())) + verify(secureSettings).registerContentObserverForUser( + eq(settingUri1), anyBoolean(), any(), anyInt()) + } + + @Test + fun removeCallback_lastUnregistersObserver() { + val listenerSetting1 : Listener = mock() + val listenerSetting2 : Listener = mock() + controller.addCallback(settingUri1, listenerSetting1) + verify(secureSettings).registerContentObserverForUser( + eq(settingUri1), eq(false), any(), eq(ActivityManager.getCurrentUser())) + + controller.addCallback(settingUri2, listenerSetting2) + verify(secureSettings).registerContentObserverForUser( + eq(settingUri2), anyBoolean(), any(), anyInt()) + + controller.removeCallback(settingUri2, listenerSetting2) + verify(secureSettings, never()).unregisterContentObserver(any()) + + controller.removeCallback(settingUri1, listenerSetting1) + verify(secureSettings).unregisterContentObserver(any()) + } + + @Test + fun addCallback_updatesCurrentValue() { + whenever(secureSettings.getStringForUser( + setting1, ActivityManager.getCurrentUser())).thenReturn("9") + whenever(secureSettings.getStringForUser( + setting2, ActivityManager.getCurrentUser())).thenReturn("5") + + val listenerSetting1a : Listener = mock() + val listenerSetting1b : Listener = mock() + val listenerSetting2 : Listener = mock() + + controller.addCallback(settingUri1, listenerSetting1a) + controller.addCallback(settingUri1, listenerSetting1b) + controller.addCallback(settingUri2, listenerSetting2) + + testableLooper.processAllMessages() + + verify(listenerSetting1a).onSettingChanged( + settingUri1, ActivityManager.getCurrentUser(), "9") + verify(listenerSetting1b).onSettingChanged( + settingUri1, ActivityManager.getCurrentUser(), "9") + verify(listenerSetting2).onSettingChanged( + settingUri2, ActivityManager.getCurrentUser(), "5") + } + + @Test + fun removeCallback_noMoreUpdates() { + whenever(secureSettings.getStringForUser( + setting1, ActivityManager.getCurrentUser())).thenReturn("9") + + val listenerSetting1a : Listener = mock() + val listenerSetting1b : Listener = mock() + + // First, register + controller.addCallback(settingUri1, listenerSetting1a) + controller.addCallback(settingUri1, listenerSetting1b) + testableLooper.processAllMessages() + + verify(secureSettings).registerContentObserverForUser( + any(Uri::class.java), anyBoolean(), capture(settingsObserverCaptor), anyInt()) + verify(listenerSetting1a).onSettingChanged( + settingUri1, ActivityManager.getCurrentUser(), "9") + verify(listenerSetting1b).onSettingChanged( + settingUri1, ActivityManager.getCurrentUser(), "9") + Mockito.clearInvocations(listenerSetting1b) + Mockito.clearInvocations(listenerSetting1a) + + // Remove one of them + controller.removeCallback(settingUri1, listenerSetting1a) + + // On update, only remaining listener should get the callback + settingsObserverCaptor.value.onChange(false, settingUri1) + testableLooper.processAllMessages() + + verify(listenerSetting1a, never()).onSettingChanged( + settingUri1, ActivityManager.getCurrentUser(), "9") + verify(listenerSetting1b).onSettingChanged( + settingUri1, ActivityManager.getCurrentUser(), "9") + } + +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 5dcb90144b70..823155b0d7e6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -45,12 +45,12 @@ import android.view.View; import android.view.ViewPropertyAnimator; import android.widget.FrameLayout; -import androidx.core.animation.AnimatorTestRule; import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.R; import com.android.systemui.SysuiBaseFragmentTest; +import com.android.systemui.animation.AnimatorTestRule; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.log.LogBuffer; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt index 2617613d1fc5..2ce060c5d097 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt @@ -19,9 +19,9 @@ package com.android.systemui.statusbar.phone.fragment import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View -import androidx.core.animation.AnimatorTestRule import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.AnimatorTestRule import junit.framework.Assert.assertEquals import org.junit.Before import org.junit.Rule diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt index 6306a36d9730..50ee6a31d389 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt @@ -55,6 +55,8 @@ class FakeMobileConnectionRepository( override val networkName = MutableStateFlow<NetworkNameModel>(NetworkNameModel.Default("default")) + override val isAllowedDuringAirplaneMode = MutableStateFlow(false) + fun setDataEnabled(enabled: Boolean) { _dataEnabled.value = enabled } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt index 441186acb6b7..a251c2850d41 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt @@ -20,6 +20,7 @@ import android.telephony.TelephonyManager import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel @@ -319,6 +320,14 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() { job.cancel() } + @Test + fun isAllowedDuringAirplaneMode_alwaysTrue() = + testScope.runTest { + val latest by collectLastValue(underTest.isAllowedDuringAirplaneMode) + + assertThat(latest).isTrue() + } + private companion object { const val SUB_ID = 123 const val NET_ID = 456 diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt index b701fbd66937..3dd2eaff7bce 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt @@ -22,6 +22,7 @@ import android.telephony.TelephonyCallback import android.telephony.TelephonyManager import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.TableLogBufferFactory import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel @@ -84,7 +85,11 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { @Before fun setUp() { mobileRepo = FakeMobileConnectionRepository(SUB_ID, tableLogBuffer) - carrierMergedRepo = FakeMobileConnectionRepository(SUB_ID, tableLogBuffer) + carrierMergedRepo = + FakeMobileConnectionRepository(SUB_ID, tableLogBuffer).apply { + // Mimicks the real carrier merged repository + this.isAllowedDuringAirplaneMode.value = true + } whenever( mobileFactory.build( @@ -300,6 +305,24 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { } @Test + fun isAllowedDuringAirplaneMode_updatesWhenCarrierMergedUpdates() = + testScope.runTest { + initializeRepo(startingIsCarrierMerged = false) + + val latest by collectLastValue(underTest.isAllowedDuringAirplaneMode) + + assertThat(latest).isFalse() + + underTest.setIsCarrierMerged(true) + + assertThat(latest).isTrue() + + underTest.setIsCarrierMerged(false) + + assertThat(latest).isFalse() + } + + @Test fun factory_reusesLogBuffersForSameConnection() = testScope.runTest { val realLoggerFactory = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt index cf832b4ab059..1ff737bfc137 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt @@ -53,6 +53,7 @@ import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN import androidx.test.filters.SmallTest import com.android.settingslib.mobile.MobileMappings import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState @@ -812,6 +813,14 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { job.cancel() } + @Test + fun isAllowedDuringAirplaneMode_alwaysFalse() = + testScope.runTest { + val latest by collectLastValue(underTest.isAllowedDuringAirplaneMode) + + assertThat(latest).isFalse() + } + private inline fun <reified T> getTelephonyCallbackForType(): T { return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt index c4e419366759..8d1da69d6877 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt @@ -77,6 +77,8 @@ class FakeMobileIconInteractor( override val isForceHidden = MutableStateFlow(false) + override val isAllowedDuringAirplaneMode = MutableStateFlow(false) + fun setIsEmergencyOnly(emergency: Boolean) { _isEmergencyOnly.value = emergency } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt index c2768654809b..58d3804b7155 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt @@ -23,6 +23,7 @@ import com.android.settingslib.mobile.MobileIconCarrierIdOverrides import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.CarrierMergedNetworkType @@ -473,6 +474,18 @@ class MobileIconInteractorTest : SysuiTestCase() { job.cancel() } + @Test + fun isAllowedDuringAirplaneMode_matchesRepo() = + testScope.runTest { + val latest by collectLastValue(underTest.isAllowedDuringAirplaneMode) + + connectionRepository.isAllowedDuringAirplaneMode.value = true + assertThat(latest).isTrue() + + connectionRepository.isAllowedDuringAirplaneMode.value = false + assertThat(latest).isFalse() + } + private fun createInteractor( overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl() ) = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt index b5ab29d6217e..72feec78a979 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt @@ -116,12 +116,13 @@ class MobileIconViewModelTest : SysuiTestCase() { } @Test - fun isVisible_airplane_false() = + fun isVisible_airplaneAndNotAllowed_false() = testScope.runTest { var latest: Boolean? = null val job = underTest.isVisible.onEach { latest = it }.launchIn(this) airplaneModeRepository.setIsAirplaneMode(true) + interactor.isAllowedDuringAirplaneMode.value = false interactor.isForceHidden.value = false assertThat(latest).isFalse() @@ -129,6 +130,22 @@ class MobileIconViewModelTest : SysuiTestCase() { job.cancel() } + /** Regression test for b/291993542. */ + @Test + fun isVisible_airplaneButAllowed_true() = + testScope.runTest { + var latest: Boolean? = null + val job = underTest.isVisible.onEach { latest = it }.launchIn(this) + + airplaneModeRepository.setIsAirplaneMode(true) + interactor.isAllowedDuringAirplaneMode.value = true + interactor.isForceHidden.value = false + + assertThat(latest).isTrue() + + job.cancel() + } + @Test fun isVisible_forceHidden_false() = testScope.runTest { @@ -157,7 +174,7 @@ class MobileIconViewModelTest : SysuiTestCase() { airplaneModeRepository.setIsAirplaneMode(true) assertThat(latest).isFalse() - airplaneModeRepository.setIsAirplaneMode(false) + interactor.isAllowedDuringAirplaneMode.value = true assertThat(latest).isTrue() interactor.isForceHidden.value = true diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt index 1bf431b4ea13..1c8dac14b089 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel -import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT +import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryHelper.ACTIVITY_DEFAULT import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt index fef042be65a8..2dbeb7aa7e90 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt @@ -489,6 +489,26 @@ class WifiRepositoryImplTest : SysuiTestCase() { } @Test + fun wifiNetwork_neverHasHotspot() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiInfo = + mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(SSID) + whenever(this.isPrimary).thenReturn(true) + } + val network = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) } + + getNetworkCallback() + .onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo)) + + assertThat(latest is WifiNetworkModel.Active).isTrue() + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.NONE) + } + + @Test fun wifiNetwork_isCarrierMerged_flowHasCarrierMerged() = testScope.runTest { val latest by collectLastValue(underTest.wifiNetwork) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt index 7002cbb6ab21..9959e00fd3f3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt @@ -18,13 +18,18 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod import android.net.wifi.WifiManager import android.net.wifi.WifiManager.UNKNOWN_SSID +import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo +import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.log.LogBuffer import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory +import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import com.android.systemui.util.concurrency.FakeExecutor @@ -34,8 +39,13 @@ import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock +import com.android.wifitrackerlib.HotspotNetworkEntry +import com.android.wifitrackerlib.HotspotNetworkEntry.DeviceType import com.android.wifitrackerlib.MergedCarrierEntry import com.android.wifitrackerlib.WifiEntry +import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX +import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN +import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE import com.android.wifitrackerlib.WifiPickerTracker import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -45,6 +55,7 @@ import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import org.mockito.Mockito.verify /** * Note: Most of these tests are duplicates of [WifiRepositoryImplTest] tests. @@ -57,10 +68,25 @@ import org.junit.Test @TestableLooper.RunWithLooper(setAsMainLooper = true) class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { - private lateinit var underTest: WifiRepositoryViaTrackerLib + // Using lazy means that the class will only be constructed once it's fetched. Because the + // repository internally sets some values on construction, we need to set up some test + // parameters (like feature flags) *before* construction. Using lazy allows us to do that setup + // inside each test case without needing to manually recreate the repository. + private val underTest: WifiRepositoryViaTrackerLib by lazy { + WifiRepositoryViaTrackerLib( + featureFlags, + testScope.backgroundScope, + executor, + wifiPickerTrackerFactory, + wifiManager, + logger, + tableLogger, + ) + } private val executor = FakeExecutor(FakeSystemClock()) private val logger = LogBuffer("name", maxSize = 100, logcatEchoTracker = mock()) + private val featureFlags = FakeFeatureFlags() private val tableLogger = mock<TableLogBuffer>() private val wifiManager = mock<WifiManager>().apply { whenever(this.maxSignalLevel).thenReturn(10) } @@ -74,12 +100,21 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { @Before fun setUp() { + featureFlags.set(Flags.INSTANT_TETHER, false) whenever(wifiPickerTrackerFactory.create(any(), capture(callbackCaptor))) .thenReturn(wifiPickerTracker) - underTest = createRepo() } @Test + fun wifiPickerTrackerCreation_scansDisabled() = + testScope.runTest { + collectLastValue(underTest.wifiNetwork) + testScope.runCurrent() + + verify(wifiPickerTracker).disableScanning() + } + + @Test fun isWifiEnabled_enabled_true() = testScope.runTest { val latest by collectLastValue(underTest.isWifiEnabled) @@ -238,7 +273,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.level).thenReturn(3) - whenever(this.ssid).thenReturn(SSID) + whenever(this.title).thenReturn(TITLE) } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -246,7 +281,240 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { assertThat(latest is WifiNetworkModel.Active).isTrue() val latestActive = latest as WifiNetworkModel.Active assertThat(latestActive.level).isEqualTo(3) - assertThat(latestActive.ssid).isEqualTo(SSID) + assertThat(latestActive.ssid).isEqualTo(TITLE) + } + + @Test + fun accessPointInfo_alwaysFalse() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(3) + whenever(this.title).thenReturn(TITLE) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.isPasspointAccessPoint).isFalse() + assertThat(latestActive.isOnlineSignUpForPasspointAccessPoint).isFalse() + assertThat(latestActive.passpointProviderFriendlyName).isNull() + } + + @Test + fun wifiNetwork_unreachableLevel_inactiveNetwork() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(WIFI_LEVEL_UNREACHABLE) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest).isEqualTo(WifiNetworkModel.Inactive) + } + + @Test + fun wifiNetwork_levelTooHigh_inactiveNetwork() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(WIFI_LEVEL_MAX + 1) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest).isEqualTo(WifiNetworkModel.Inactive) + } + + @Test + fun wifiNetwork_levelTooLow_inactiveNetwork() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(WIFI_LEVEL_MIN - 1) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest).isEqualTo(WifiNetworkModel.Inactive) + } + + @Test + fun wifiNetwork_levelIsMax_activeNetworkWithMaxLevel() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(WIFI_LEVEL_MAX) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest).isInstanceOf(WifiNetworkModel.Active::class.java) + assertThat((latest as WifiNetworkModel.Active).level).isEqualTo(WIFI_LEVEL_MAX) + } + + @Test + fun wifiNetwork_levelIsMin_activeNetworkWithMinLevel() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(WIFI_LEVEL_MIN) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest).isInstanceOf(WifiNetworkModel.Active::class.java) + assertThat((latest as WifiNetworkModel.Active).level).isEqualTo(WIFI_LEVEL_MIN) + } + + @Test + fun wifiNetwork_notHotspot_none() = + testScope.runTest { + featureFlags.set(Flags.INSTANT_TETHER, true) + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.NONE) + } + + @Test + fun wifiNetwork_hotspot_unknown() = + testScope.runTest { + featureFlags.set(Flags.INSTANT_TETHER, true) + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_UNKNOWN) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.UNKNOWN) + } + + @Test + fun wifiNetwork_hotspot_phone() = + testScope.runTest { + featureFlags.set(Flags.INSTANT_TETHER, true) + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_PHONE) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.PHONE) + } + + @Test + fun wifiNetwork_hotspot_tablet() = + testScope.runTest { + featureFlags.set(Flags.INSTANT_TETHER, true) + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_TABLET) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.TABLET) + } + + @Test + fun wifiNetwork_hotspot_laptop() = + testScope.runTest { + featureFlags.set(Flags.INSTANT_TETHER, true) + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_LAPTOP) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.LAPTOP) + } + + @Test + fun wifiNetwork_hotspot_watch() = + testScope.runTest { + featureFlags.set(Flags.INSTANT_TETHER, true) + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_WATCH) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.WATCH) + } + + @Test + fun wifiNetwork_hotspot_auto() = + testScope.runTest { + featureFlags.set(Flags.INSTANT_TETHER, true) + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_AUTO) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.AUTO) + } + + @Test + fun wifiNetwork_hotspot_invalid() = + testScope.runTest { + featureFlags.set(Flags.INSTANT_TETHER, true) + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = createHotspotWithType(1234) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.INVALID) + } + + @Test + fun wifiNetwork_hotspot_flagOff_valueNotUsed() = + testScope.runTest { + // WHEN the flag is off + featureFlags.set(Flags.INSTANT_TETHER, false) + + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_WATCH) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + // THEN NONE is always used, even if the wifi entry does have a hotspot device type + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.NONE) } @Test @@ -258,6 +526,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { mock<MergedCarrierEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.level).thenReturn(3) + whenever(this.subscriptionId).thenReturn(567) } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -265,7 +534,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue() val latestMerged = latest as WifiNetworkModel.CarrierMerged assertThat(latestMerged.level).isEqualTo(3) - // numberOfLevels = maxSignalLevel + 1 + assertThat(latestMerged.subscriptionId).isEqualTo(567) } @Test @@ -288,30 +557,23 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { assertThat(latestMerged.numberOfLevels).isEqualTo(6) } - /* TODO(b/292534484): Re-enable this test once WifiTrackerLib gives us the subscription ID. @Test fun wifiNetwork_carrierMergedButInvalidSubId_flowHasInvalid() = testScope.runTest { val latest by collectLastValue(underTest.wifiNetwork) - val wifiInfo = - mock<WifiInfo>().apply { - whenever(this.isPrimary).thenReturn(true) - whenever(this.isCarrierMerged).thenReturn(true) + val wifiEntry = + mock<MergedCarrierEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID) } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) - getNetworkCallback() - .onCapabilitiesChanged( - NETWORK, - createWifiNetworkCapabilities(wifiInfo), - ) + getCallback().onWifiEntriesChanged() assertThat(latest).isInstanceOf(WifiNetworkModel.Invalid::class.java) } - */ - @Test fun wifiNetwork_notValidated_networkNotValidated() = testScope.runTest { @@ -382,7 +644,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.level).thenReturn(3) - whenever(this.ssid).thenReturn("AB") + whenever(this.title).thenReturn("AB") } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -397,7 +659,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.level).thenReturn(4) - whenever(this.ssid).thenReturn("CD") + whenever(this.title).thenReturn("CD") } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(newWifiEntry) getCallback().onWifiEntriesChanged() @@ -430,12 +692,12 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { val wifiEntry = mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) - whenever(this.ssid).thenReturn(SSID) + whenever(this.title).thenReturn(TITLE) } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() - assertThat((latest as WifiNetworkModel.Active).ssid).isEqualTo(SSID) + assertThat((latest as WifiNetworkModel.Active).ssid).isEqualTo(TITLE) // WHEN we lose our current network whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null) @@ -480,7 +742,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.level).thenReturn(1) - whenever(this.ssid).thenReturn(SSID) + whenever(this.title).thenReturn(TITLE) } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -488,7 +750,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { assertThat(latest1 is WifiNetworkModel.Active).isTrue() val latest1Active = latest1 as WifiNetworkModel.Active assertThat(latest1Active.level).isEqualTo(1) - assertThat(latest1Active.ssid).isEqualTo(SSID) + assertThat(latest1Active.ssid).isEqualTo(TITLE) // WHEN we add a second subscriber after having already emitted a value val latest2 by collectLastValue(underTest.wifiNetwork) @@ -497,7 +759,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { assertThat(latest2 is WifiNetworkModel.Active).isTrue() val latest2Active = latest2 as WifiNetworkModel.Active assertThat(latest2Active.level).isEqualTo(1) - assertThat(latest2Active.ssid).isEqualTo(SSID) + assertThat(latest2Active.ssid).isEqualTo(TITLE) } @Test @@ -541,40 +803,32 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() } - /* TODO(b/292534484): Re-enable this test once WifiTrackerLib gives us the subscription ID. - @Test - fun isWifiConnectedWithValidSsid_invalidNetwork_false() = - testScope.runTest { - collectLastValue(underTest.wifiNetwork) - - val wifiInfo = - mock<WifiInfo>().apply { - whenever(this.isPrimary).thenReturn(true) - whenever(this.isCarrierMerged).thenReturn(true) - whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID) - } - - getNetworkCallback() - .onCapabilitiesChanged( - NETWORK, - createWifiNetworkCapabilities(wifiInfo), - ) - testScope.runCurrent() + @Test + fun isWifiConnectedWithValidSsid_invalidNetwork_false() = + testScope.runTest { + collectLastValue(underTest.wifiNetwork) - assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() - } + val wifiEntry = + mock<MergedCarrierEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + testScope.runCurrent() - */ + assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() + } @Test - fun isWifiConnectedWithValidSsid_activeNetwork_nullSsid_false() = + fun isWifiConnectedWithValidSsid_activeNetwork_nullTitle_false() = testScope.runTest { collectLastValue(underTest.wifiNetwork) val wifiEntry = mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) - whenever(this.ssid).thenReturn(null) + whenever(this.title).thenReturn(null) } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -584,14 +838,14 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { } @Test - fun isWifiConnectedWithValidSsid_activeNetwork_unknownSsid_false() = + fun isWifiConnectedWithValidSsid_activeNetwork_unknownTitle_false() = testScope.runTest { collectLastValue(underTest.wifiNetwork) val wifiEntry = mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) - whenever(this.ssid).thenReturn(UNKNOWN_SSID) + whenever(this.title).thenReturn(UNKNOWN_SSID) } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -601,14 +855,14 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { } @Test - fun isWifiConnectedWithValidSsid_activeNetwork_validSsid_true() = + fun isWifiConnectedWithValidSsid_activeNetwork_validTitle_true() = testScope.runTest { collectLastValue(underTest.wifiNetwork) val wifiEntry = mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) - whenever(this.ssid).thenReturn("fakeSsid") + whenever(this.title).thenReturn("fakeSsid") } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -626,7 +880,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { val wifiEntry = mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) - whenever(this.ssid).thenReturn("fakeSsid") + whenever(this.title).thenReturn("fakeSsid") } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -643,23 +897,74 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() } + @Test + fun wifiActivity_callbackGivesNone_activityFlowHasNone() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiActivity) + + getTrafficStateCallback() + .onStateChanged(WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE) + + assertThat(latest) + .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false)) + } + + @Test + fun wifiActivity_callbackGivesIn_activityFlowHasIn() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiActivity) + + getTrafficStateCallback() + .onStateChanged(WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN) + + assertThat(latest) + .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = false)) + } + + @Test + fun wifiActivity_callbackGivesOut_activityFlowHasOut() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiActivity) + + getTrafficStateCallback() + .onStateChanged(WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT) + + assertThat(latest) + .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = true)) + } + + @Test + fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiActivity) + + getTrafficStateCallback() + .onStateChanged(WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT) + + assertThat(latest) + .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true)) + } + private fun getCallback(): WifiPickerTracker.WifiPickerTrackerCallback { testScope.runCurrent() return callbackCaptor.value } - private fun createRepo(): WifiRepositoryViaTrackerLib { - return WifiRepositoryViaTrackerLib( - testScope.backgroundScope, - executor, - wifiPickerTrackerFactory, - wifiManager, - logger, - tableLogger, - ) + private fun getTrafficStateCallback(): WifiManager.TrafficStateCallback { + testScope.runCurrent() + val callbackCaptor = argumentCaptor<WifiManager.TrafficStateCallback>() + verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture()) + return callbackCaptor.value!! + } + + private fun createHotspotWithType(@DeviceType type: Int): HotspotNetworkEntry { + return mock<HotspotNetworkEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.deviceType).thenReturn(type) + } } private companion object { - const val SSID = "AB" + const val TITLE = "AB" } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt index 4e0c309512e8..ba035bec340c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt @@ -136,7 +136,8 @@ class WifiNetworkModelTest : SysuiTestCase() { networkId = 5, isValidated = true, level = 3, - ssid = "Test SSID" + ssid = "Test SSID", + hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.LAPTOP, ) activeNetwork.logDiffs(prevVal = WifiNetworkModel.Inactive, logger) @@ -146,6 +147,7 @@ class WifiNetworkModelTest : SysuiTestCase() { assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true")) assertThat(logger.changes).contains(Pair(COL_LEVEL, "3")) assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID")) + assertThat(logger.changes).contains(Pair(COL_HOTSPOT, "LAPTOP")) } @Test fun logDiffs_activeToInactive_resetsAllActiveFields() { @@ -165,6 +167,7 @@ class WifiNetworkModelTest : SysuiTestCase() { assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false")) assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString())) assertThat(logger.changes).contains(Pair(COL_SSID, "null")) + assertThat(logger.changes).contains(Pair(COL_HOTSPOT, "null")) } @Test @@ -175,7 +178,8 @@ class WifiNetworkModelTest : SysuiTestCase() { networkId = 5, isValidated = true, level = 3, - ssid = "Test SSID" + ssid = "Test SSID", + hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.AUTO, ) val prevVal = WifiNetworkModel.CarrierMerged( @@ -191,6 +195,7 @@ class WifiNetworkModelTest : SysuiTestCase() { assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true")) assertThat(logger.changes).contains(Pair(COL_LEVEL, "3")) assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID")) + assertThat(logger.changes).contains(Pair(COL_HOTSPOT, "AUTO")) } @Test fun logDiffs_activeToCarrierMerged_logsAllFields() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java index c886f9bee07e..cdeb59278851 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java @@ -29,6 +29,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Intent; +import android.hardware.usb.UsbManager; +import android.hardware.usb.UsbPort; +import android.hardware.usb.UsbPortStatus; import android.os.BatteryManager; import android.os.Handler; import android.os.PowerManager; @@ -56,6 +59,9 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.MockitoSession; +import java.util.ArrayList; +import java.util.List; + @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -65,8 +71,10 @@ public class BatteryControllerTest extends SysuiTestCase { @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock private DemoModeController mDemoModeController; @Mock private View mView; + @Mock private UsbPort mUsbPort; + @Mock private UsbManager mUsbManager; + @Mock private UsbPortStatus mUsbPortStatus; private BatteryControllerImpl mBatteryController; - private MockitoSession mMockitoSession; @Before @@ -255,4 +263,38 @@ public class BatteryControllerTest extends SysuiTestCase { Assert.assertFalse(mBatteryController.isBatteryDefender()); } + + @Test + public void complianceChanged_complianceIncompatible_outputsTrue() { + mContext.addMockSystemService(UsbManager.class, mUsbManager); + setupIncompatibleCharging(); + Intent intent = new Intent(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED); + + mBatteryController.onReceive(getContext(), intent); + + Assert.assertTrue(mBatteryController.isIncompatibleCharging()); + } + + @Test + public void complianceChanged_emptyComplianceWarnings_outputsFalse() { + mContext.addMockSystemService(UsbManager.class, mUsbManager); + setupIncompatibleCharging(); + when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[1]); + Intent intent = new Intent(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED); + + mBatteryController.onReceive(getContext(), intent); + + Assert.assertFalse(mBatteryController.isIncompatibleCharging()); + } + + private void setupIncompatibleCharging() { + final List<UsbPort> usbPorts = new ArrayList<>(); + usbPorts.add(mUsbPort); + when(mUsbManager.getPorts()).thenReturn(usbPorts); + when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus); + when(mUsbPort.supportsComplianceWarnings()).thenReturn(true); + when(mUsbPortStatus.isConnected()).thenReturn(true); + when(mUsbPortStatus.getComplianceWarnings()) + .thenReturn(new int[]{UsbPortStatus.COMPLIANCE_WARNING_OTHER}); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java index 7c285b8aa1a9..ef39ff8ed521 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java @@ -62,7 +62,6 @@ import android.window.OnBackInvokedDispatcher; import android.window.WindowOnBackInvokedDispatcher; import androidx.annotation.NonNull; -import androidx.core.animation.AnimatorTestRule; import androidx.test.filters.SmallTest; import com.android.internal.logging.UiEventLogger; @@ -70,6 +69,7 @@ import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.animation.AnimatorTestRule; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.statusbar.NotificationRemoteInputManager; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt index 871a48c503be..b698e70eb379 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt @@ -19,9 +19,11 @@ package com.android.systemui.statusbar.policy import android.os.Handler import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import android.view.View import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.capture import com.android.systemui.util.time.FakeSystemClock @@ -59,6 +61,7 @@ class VariableDateViewControllerTest : SysuiTestCase() { private var lastText: String? = null + private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager private lateinit var systemClock: FakeSystemClock private lateinit var testableLooper: TestableLooper private lateinit var testableHandler: Handler @@ -76,6 +79,8 @@ class VariableDateViewControllerTest : SysuiTestCase() { systemClock = FakeSystemClock() systemClock.setCurrentTimeMillis(TIME_STAMP) + shadeExpansionStateManager = ShadeExpansionStateManager() + `when`(view.longerPattern).thenReturn(LONG_PATTERN) `when`(view.shorterPattern).thenReturn(SHORT_PATTERN) `when`(view.handler).thenReturn(testableHandler) @@ -99,6 +104,7 @@ class VariableDateViewControllerTest : SysuiTestCase() { controller = VariableDateViewController( systemClock, broadcastDispatcher, + shadeExpansionStateManager, testableHandler, view ) @@ -121,7 +127,7 @@ class VariableDateViewControllerTest : SysuiTestCase() { @Test fun testLotsOfSpaceUseLongText() { - onMeasureListenerCaptor.value.onMeasureAction(10000) + onMeasureListenerCaptor.value.onMeasureAction(10000, View.MeasureSpec.EXACTLY) testableLooper.processAllMessages() assertThat(lastText).isEqualTo(longText) @@ -129,7 +135,7 @@ class VariableDateViewControllerTest : SysuiTestCase() { @Test fun testSmallSpaceUseEmpty() { - onMeasureListenerCaptor.value.onMeasureAction(1) + onMeasureListenerCaptor.value.onMeasureAction(1, View.MeasureSpec.EXACTLY) testableLooper.processAllMessages() assertThat(lastText).isEmpty() @@ -139,7 +145,7 @@ class VariableDateViewControllerTest : SysuiTestCase() { fun testSpaceInBetweenUseShortText() { val average = ((getTextLength(longText) + getTextLength(shortText)) / 2).toInt() - onMeasureListenerCaptor.value.onMeasureAction(average) + onMeasureListenerCaptor.value.onMeasureAction(average, View.MeasureSpec.EXACTLY) testableLooper.processAllMessages() assertThat(lastText).isEqualTo(shortText) @@ -147,10 +153,10 @@ class VariableDateViewControllerTest : SysuiTestCase() { @Test fun testSwitchBackToLonger() { - onMeasureListenerCaptor.value.onMeasureAction(1) + onMeasureListenerCaptor.value.onMeasureAction(1, View.MeasureSpec.EXACTLY) testableLooper.processAllMessages() - onMeasureListenerCaptor.value.onMeasureAction(10000) + onMeasureListenerCaptor.value.onMeasureAction(10000, View.MeasureSpec.EXACTLY) testableLooper.processAllMessages() assertThat(lastText).isEqualTo(longText) @@ -161,11 +167,41 @@ class VariableDateViewControllerTest : SysuiTestCase() { `when`(view.freezeSwitching).thenReturn(true) val average = ((getTextLength(longText) + getTextLength(shortText)) / 2).toInt() - onMeasureListenerCaptor.value.onMeasureAction(average) + onMeasureListenerCaptor.value.onMeasureAction(average, View.MeasureSpec.EXACTLY) testableLooper.processAllMessages() assertThat(lastText).isEqualTo(longText) - onMeasureListenerCaptor.value.onMeasureAction(1) + onMeasureListenerCaptor.value.onMeasureAction(1, View.MeasureSpec.EXACTLY) + testableLooper.processAllMessages() + assertThat(lastText).isEqualTo(longText) + } + + @Test + fun testQsExpansionTrue_ignoreAtMostMeasureRequests() { + shadeExpansionStateManager.onQsExpansionFractionChanged(0f) + + onMeasureListenerCaptor.value.onMeasureAction( + getTextLength(shortText).toInt(), + View.MeasureSpec.EXACTLY + ) + testableLooper.processAllMessages() + + onMeasureListenerCaptor.value.onMeasureAction(10000, View.MeasureSpec.AT_MOST) + testableLooper.processAllMessages() + assertThat(lastText).isEqualTo(shortText) + } + + @Test + fun testQsExpansionFalse_acceptAtMostMeasureRequests() { + shadeExpansionStateManager.onQsExpansionFractionChanged(1f) + + onMeasureListenerCaptor.value.onMeasureAction( + getTextLength(shortText).toInt(), + View.MeasureSpec.EXACTLY + ) + testableLooper.processAllMessages() + + onMeasureListenerCaptor.value.onMeasureAction(10000, View.MeasureSpec.AT_MOST) testableLooper.processAllMessages() assertThat(lastText).isEqualTo(longText) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt index 2662da201460..1404a4fdbdae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt @@ -16,43 +16,128 @@ package com.android.systemui.util -import android.test.suitebuilder.annotation.SmallTest -import androidx.test.runner.AndroidJUnit4 +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) -class ListenerSetTest : SysuiTestCase() { +open class ListenerSetTest : SysuiTestCase() { - var runnableSet: ListenerSet<Runnable> = ListenerSet() + private val runnableSet: IListenerSet<Runnable> = makeRunnableListenerSet() - @Before - fun setup() { - runnableSet = ListenerSet() - } + open fun makeRunnableListenerSet(): IListenerSet<Runnable> = ListenerSet() @Test fun addIfAbsent_doesNotDoubleAdd() { // setup & preconditions val runnable1 = Runnable { } val runnable2 = Runnable { } - assertThat(runnableSet.toList()).isEmpty() + assertThat(runnableSet).isEmpty() // Test that an element can be added assertThat(runnableSet.addIfAbsent(runnable1)).isTrue() - assertThat(runnableSet.toList()).containsExactly(runnable1) + assertThat(runnableSet).containsExactly(runnable1) // Test that a second element can be added assertThat(runnableSet.addIfAbsent(runnable2)).isTrue() - assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2) + assertThat(runnableSet).containsExactly(runnable1, runnable2) // Test that re-adding the first element does nothing and returns false assertThat(runnableSet.addIfAbsent(runnable1)).isFalse() - assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2) + assertThat(runnableSet).containsExactly(runnable1, runnable2) + } + + @Test + fun isEmpty_changes() { + val runnable = Runnable { } + assertThat(runnableSet).isEmpty() + assertThat(runnableSet.isEmpty()).isTrue() + assertThat(runnableSet.isNotEmpty()).isFalse() + + assertThat(runnableSet.addIfAbsent(runnable)).isTrue() + assertThat(runnableSet).isNotEmpty() + assertThat(runnableSet.isEmpty()).isFalse() + assertThat(runnableSet.isNotEmpty()).isTrue() + + assertThat(runnableSet.remove(runnable)).isTrue() + assertThat(runnableSet).isEmpty() + assertThat(runnableSet.isEmpty()).isTrue() + assertThat(runnableSet.isNotEmpty()).isFalse() + } + + @Test + fun size_changes() { + assertThat(runnableSet).isEmpty() + assertThat(runnableSet.size).isEqualTo(0) + + assertThat(runnableSet.addIfAbsent(Runnable { })).isTrue() + assertThat(runnableSet.size).isEqualTo(1) + + assertThat(runnableSet.addIfAbsent(Runnable { })).isTrue() + assertThat(runnableSet.size).isEqualTo(2) + } + + @Test + fun contains_worksAsExpected() { + val runnable1 = Runnable { } + val runnable2 = Runnable { } + assertThat(runnableSet).isEmpty() + assertThat(runnable1 in runnableSet).isFalse() + assertThat(runnable2 in runnableSet).isFalse() + assertThat(runnableSet).doesNotContain(runnable1) + assertThat(runnableSet).doesNotContain(runnable2) + + assertThat(runnableSet.addIfAbsent(runnable1)).isTrue() + assertThat(runnable1 in runnableSet).isTrue() + assertThat(runnable2 in runnableSet).isFalse() + assertThat(runnableSet).contains(runnable1) + assertThat(runnableSet).doesNotContain(runnable2) + + assertThat(runnableSet.addIfAbsent(runnable2)).isTrue() + assertThat(runnable1 in runnableSet).isTrue() + assertThat(runnable2 in runnableSet).isTrue() + assertThat(runnableSet).contains(runnable1) + assertThat(runnableSet).contains(runnable2) + + assertThat(runnableSet.remove(runnable1)).isTrue() + assertThat(runnable1 in runnableSet).isFalse() + assertThat(runnable2 in runnableSet).isTrue() + assertThat(runnableSet).doesNotContain(runnable1) + assertThat(runnableSet).contains(runnable2) + } + + @Test + fun containsAll_worksAsExpected() { + val runnable1 = Runnable { } + val runnable2 = Runnable { } + + assertThat(runnableSet).isEmpty() + assertThat(runnableSet.containsAll(listOf())).isTrue() + assertThat(runnableSet.containsAll(listOf(runnable1))).isFalse() + assertThat(runnableSet.containsAll(listOf(runnable2))).isFalse() + assertThat(runnableSet.containsAll(listOf(runnable1, runnable2))).isFalse() + + assertThat(runnableSet.addIfAbsent(runnable1)).isTrue() + assertThat(runnableSet.containsAll(listOf())).isTrue() + assertThat(runnableSet.containsAll(listOf(runnable1))).isTrue() + assertThat(runnableSet.containsAll(listOf(runnable2))).isFalse() + assertThat(runnableSet.containsAll(listOf(runnable1, runnable2))).isFalse() + + assertThat(runnableSet.addIfAbsent(runnable2)).isTrue() + assertThat(runnableSet.containsAll(listOf())).isTrue() + assertThat(runnableSet.containsAll(listOf(runnable1))).isTrue() + assertThat(runnableSet.containsAll(listOf(runnable2))).isTrue() + assertThat(runnableSet.containsAll(listOf(runnable1, runnable2))).isTrue() + + assertThat(runnableSet.remove(runnable1)).isTrue() + assertThat(runnableSet.containsAll(listOf())).isTrue() + assertThat(runnableSet.containsAll(listOf(runnable1))).isFalse() + assertThat(runnableSet.containsAll(listOf(runnable2))).isTrue() + assertThat(runnableSet.containsAll(listOf(runnable1, runnable2))).isFalse() } @Test @@ -60,22 +145,22 @@ class ListenerSetTest : SysuiTestCase() { // setup and preconditions val runnable1 = Runnable { } val runnable2 = Runnable { } - assertThat(runnableSet.toList()).isEmpty() + assertThat(runnableSet).isEmpty() runnableSet.addIfAbsent(runnable1) runnableSet.addIfAbsent(runnable2) - assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2) + assertThat(runnableSet).containsExactly(runnable1, runnable2) // Test that removing the first runnable only removes that one runnable assertThat(runnableSet.remove(runnable1)).isTrue() - assertThat(runnableSet.toList()).containsExactly(runnable2) + assertThat(runnableSet).containsExactly(runnable2) // Test that removing a non-present runnable does not error assertThat(runnableSet.remove(runnable1)).isFalse() - assertThat(runnableSet.toList()).containsExactly(runnable2) + assertThat(runnableSet).containsExactly(runnable2) // Test that removing the other runnable succeeds assertThat(runnableSet.remove(runnable2)).isTrue() - assertThat(runnableSet.toList()).isEmpty() + assertThat(runnableSet).isEmpty() } @Test @@ -92,17 +177,17 @@ class ListenerSetTest : SysuiTestCase() { val runnable2 = Runnable { runnablesCalled.add(2) } - assertThat(runnableSet.toList()).isEmpty() + assertThat(runnableSet).isEmpty() runnableSet.addIfAbsent(runnable1) runnableSet.addIfAbsent(runnable2) - assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2) + assertThat(runnableSet).containsExactly(runnable1, runnable2) // Test that both runnables are called and 1 was removed for (runnable in runnableSet) { runnable.run() } assertThat(runnablesCalled).containsExactly(1, 2) - assertThat(runnableSet.toList()).containsExactly(runnable2) + assertThat(runnableSet).containsExactly(runnable2) } @Test @@ -120,16 +205,16 @@ class ListenerSetTest : SysuiTestCase() { val runnable2 = Runnable { runnablesCalled.add(2) } - assertThat(runnableSet.toList()).isEmpty() + assertThat(runnableSet).isEmpty() runnableSet.addIfAbsent(runnable1) runnableSet.addIfAbsent(runnable2) - assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2) + assertThat(runnableSet).containsExactly(runnable1, runnable2) // Test that both original runnables are called and 99 was added but not called for (runnable in runnableSet) { runnable.run() } assertThat(runnablesCalled).containsExactly(1, 2) - assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2, runnable99) + assertThat(runnableSet).containsExactly(runnable1, runnable2, runnable99) } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/NamedListenerSetTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/NamedListenerSetTest.kt new file mode 100644 index 000000000000..c89e317a6ad9 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/NamedListenerSetTest.kt @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class NamedListenerSetTest : ListenerSetTest() { + override fun makeRunnableListenerSet(): IListenerSet<Runnable> = NamedListenerSet() + + private val runnableSet = NamedListenerSet(NamedRunnable::name) + + class NamedRunnable(val name: String, private val block: () -> Unit = {}) : Runnable { + override fun run() = block() + } + + @Test + fun addIfAbsent_addsMultipleWithSameName_onlyIfInstanceIsAbsent() { + // setup & preconditions + val runnable1 = NamedRunnable("A") + val runnable2 = NamedRunnable("A") + assertThat(runnableSet).isEmpty() + + // Test that an element can be added + assertThat(runnableSet.addIfAbsent(runnable1)).isTrue() + assertThat(runnableSet).containsExactly(runnable1) + + // Test that a second element can be added, even with the same name + assertThat(runnableSet.addIfAbsent(runnable2)).isTrue() + assertThat(runnableSet).containsExactly(runnable1, runnable2) + + // Test that re-adding the first element does nothing and returns false + assertThat(runnableSet.addIfAbsent(runnable1)).isFalse() + assertThat(runnableSet).containsExactly(runnable1, runnable2) + } + + @Test + fun forEachNamed_includesCorrectNames() { + val runnable1 = NamedRunnable("A") + val runnable2 = NamedRunnable("X") + val runnable3 = NamedRunnable("X") + assertThat(runnableSet).isEmpty() + + assertThat(runnableSet.addIfAbsent(runnable1)).isTrue() + assertThat(runnableSet.toNamedPairs()) + .containsExactly( + "A" to runnable1, + ) + + assertThat(runnableSet.addIfAbsent(runnable2)).isTrue() + assertThat(runnableSet.toNamedPairs()) + .containsExactly( + "A" to runnable1, + "X" to runnable2, + ) + + assertThat(runnableSet.addIfAbsent(runnable3)).isTrue() + assertThat(runnableSet.toNamedPairs()) + .containsExactly( + "A" to runnable1, + "X" to runnable2, + "X" to runnable3, + ) + + assertThat(runnableSet.remove(runnable1)).isTrue() + assertThat(runnableSet.toNamedPairs()) + .containsExactly( + "X" to runnable2, + "X" to runnable3, + ) + + assertThat(runnableSet.remove(runnable2)).isTrue() + assertThat(runnableSet.toNamedPairs()) + .containsExactly( + "X" to runnable3, + ) + } + + /** + * This private method uses [NamedListenerSet.forEachNamed] to produce a list of pairs in order + * to validate that method. + */ + private fun <T : Any> NamedListenerSet<T>.toNamedPairs() = + sequence { forEachNamed { name, listener -> yield(name to listener) } }.toList() +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index ee11cb63a375..fa18e575220c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -38,6 +38,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.animation.AnimatorTestRule; import android.app.KeyguardManager; import android.content.res.Configuration; import android.media.AudioManager; @@ -84,6 +85,7 @@ import java.util.function.Predicate; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper public class VolumeDialogImplTest extends SysuiTestCase { + private static final AnimatorTestRule sAnimatorTestRule = new AnimatorTestRule(); VolumeDialogImpl mDialog; View mActiveRinger; @@ -126,6 +128,7 @@ public class VolumeDialogImplTest extends SysuiTestCase { }; private FakeFeatureFlags mFeatureFlags; + private int mLongestHideShowAnimationDuration = 250; @Before public void setup() throws Exception { @@ -138,6 +141,13 @@ public class VolumeDialogImplTest extends SysuiTestCase { when(mPostureController.getDevicePosture()) .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED); + int hideDialogDuration = mContext.getResources() + .getInteger(R.integer.config_dialogHideAnimationDurationMs); + int showDialogDuration = mContext.getResources() + .getInteger(R.integer.config_dialogShowAnimationDurationMs); + + mLongestHideShowAnimationDuration = Math.max(hideDialogDuration, showDialogDuration); + mOriginalOrientation = mContext.getResources().getConfiguration().orientation; mConfigurationController = new FakeConfigurationController(); @@ -717,6 +727,8 @@ public class VolumeDialogImplTest extends SysuiTestCase { public void teardown() { cleanUp(mDialog); setOrientation(mOriginalOrientation); + sAnimatorTestRule.advanceTimeBy(mLongestHideShowAnimationDuration); + mTestableLooper.moveTimeForward(mLongestHideShowAnimationDuration); mTestableLooper.processAllMessages(); reset(mPostureController); } diff --git a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java index 6535f333f428..19c68e86e5dc 100644 --- a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java +++ b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java @@ -18,6 +18,7 @@ package android.animation; import android.animation.AnimationHandler.AnimationFrameCallback; import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Looper; import android.os.SystemClock; import android.util.AndroidRuntimeException; @@ -31,6 +32,7 @@ import org.junit.runners.model.Statement; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; /** * JUnit {@link TestRule} that can be used to run {@link Animator}s without actually waiting for the @@ -125,14 +127,40 @@ public final class AnimatorTestRule implements TestRule { * @param timeDelta the amount of milliseconds to advance */ public void advanceTimeBy(long timeDelta) { + advanceTimeBy(timeDelta, null); + } + + /** + * Advances the animation clock by the given amount of delta in milliseconds. This call will + * produce an animation frame to all the ongoing animations. This method needs to be + * called on the same thread as {@link Animator#start()}. + * <p> + * This method is not for test authors, but for rule authors to ensure that multiple animators + * can be advanced in sync. + * + * @param timeDelta the amount of milliseconds to advance + * @param preFrameAction a consumer to be passed the timeDelta following the time advancement + * but prior to the frame production. + */ + public void advanceTimeBy(long timeDelta, @Nullable Consumer<Long> preFrameAction) { Preconditions.checkArgumentNonnegative(timeDelta, "timeDelta must not be negative"); requireLooper("AnimationTestRule#advanceTimeBy(long)"); - // before advancing time, start new animators with the current time - initNewAnimators(); + if (timeDelta == 0) { + // If time is not being advanced, all animators will get a tick; don't double tick these + mTestHandler.mNewCallbacks.clear(); + } else { + // before advancing time, start new animators with the current time + initNewAnimators(); + } synchronized (mLock) { // advance time mTotalTimeDelta += timeDelta; } + if (preFrameAction != null) { + preFrameAction.accept(timeDelta); + // After letting other code run, clear any new callbacks to avoid double-ticking them + mTestHandler.mNewCallbacks.clear(); + } // produce a frame mTestHandler.doFrame(); } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt new file mode 100644 index 000000000000..0ced19e3d5f3 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 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.animation + +import java.util.function.Consumer +import org.junit.rules.RuleChain +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +/** + * A rule that wraps both [androidx.core.animation.AnimatorTestRule] and + * [android.animation.AnimatorTestRule] such that the clocks of the two animation handlers can be + * advanced together. + */ +class AnimatorTestRule : TestRule { + private val androidxRule = androidx.core.animation.AnimatorTestRule() + private val platformRule = android.animation.AnimatorTestRule() + private val advanceAndroidXTimeBy = + Consumer<Long> { timeDelta -> androidxRule.advanceTimeBy(timeDelta) } + + /** + * Chain is for simplicity not to force a particular order; order should not matter, because + * each rule affects a different AnimationHandler classes, and no callbacks to code under test + * should be triggered by these rules + */ + private val ruleChain = RuleChain.emptyRuleChain().around(androidxRule).around(platformRule) + + override fun apply(base: Statement, description: Description): Statement = + ruleChain.apply(base, description) + + /** + * Advances the animation clock by the given amount of delta in milliseconds. This call will + * produce an animation frame to all the ongoing animations. + * + * @param timeDelta the amount of milliseconds to advance + */ + fun advanceTimeBy(timeDelta: Long) { + // NOTE: To avoid errors with order, we have to ensure that we advance the time within both + // rules before either rule does its frame output. Failing to do this could cause the + // animation from one to start later than the other. + platformRule.advanceTimeBy(timeDelta, advanceAndroidXTimeBy) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt index c2e1ac70af80..7c98df6c6317 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt @@ -20,7 +20,8 @@ import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternView import com.android.internal.widget.LockscreenCredential import com.android.keyguard.KeyguardSecurityModel.SecurityMode -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.data.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.authentication.shared.model.AuthenticationResultModel import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import kotlinx.coroutines.flow.MutableStateFlow @@ -47,7 +48,7 @@ class FakeAuthenticationRepository( private val _authenticationMethod = MutableStateFlow<AuthenticationMethodModel>(DEFAULT_AUTHENTICATION_METHOD) - val authenticationMethod: StateFlow<AuthenticationMethodModel> = + override val authenticationMethod: StateFlow<AuthenticationMethodModel> = _authenticationMethod.asStateFlow() private var isLockscreenEnabled = true @@ -154,13 +155,13 @@ class FakeAuthenticationRepository( val DEFAULT_AUTHENTICATION_METHOD = AuthenticationMethodModel.Pin val PATTERN = listOf( - AuthenticationMethodModel.Pattern.PatternCoordinate(2, 0), - AuthenticationMethodModel.Pattern.PatternCoordinate(2, 1), - AuthenticationMethodModel.Pattern.PatternCoordinate(2, 2), - AuthenticationMethodModel.Pattern.PatternCoordinate(1, 1), - AuthenticationMethodModel.Pattern.PatternCoordinate(0, 0), - AuthenticationMethodModel.Pattern.PatternCoordinate(0, 1), - AuthenticationMethodModel.Pattern.PatternCoordinate(0, 2), + AuthenticationPatternCoordinate(2, 0), + AuthenticationPatternCoordinate(2, 1), + AuthenticationPatternCoordinate(2, 2), + AuthenticationPatternCoordinate(1, 1), + AuthenticationPatternCoordinate(0, 0), + AuthenticationPatternCoordinate(0, 1), + AuthenticationPatternCoordinate(0, 2), ) const val MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING = 5 const val THROTTLE_DURATION_MS = 30000 @@ -172,7 +173,6 @@ class FakeAuthenticationRepository( is AuthenticationMethodModel.Pin -> SecurityMode.PIN is AuthenticationMethodModel.Password -> SecurityMode.Password is AuthenticationMethodModel.Pattern -> SecurityMode.Pattern - is AuthenticationMethodModel.Swipe, is AuthenticationMethodModel.None -> SecurityMode.None } } @@ -208,8 +208,7 @@ class FakeAuthenticationRepository( } } - private fun List<AuthenticationMethodModel.Pattern.PatternCoordinate>.toCells(): - List<LockPatternView.Cell> { + private fun List<AuthenticationPatternCoordinate>.toCells(): List<LockPatternView.Cell> { return map { coordinate -> LockPatternView.Cell.of(coordinate.y, coordinate.x) } } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt index 36fa7e65d8ec..013dbb458c22 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt @@ -19,45 +19,45 @@ package com.android.systemui.flags import java.io.PrintWriter class FakeFeatureFlags : FeatureFlags { - private val booleanFlags = mutableMapOf<Int, Boolean>() - private val stringFlags = mutableMapOf<Int, String>() - private val intFlags = mutableMapOf<Int, Int>() - private val knownFlagNames = mutableMapOf<Int, String>() - private val flagListeners = mutableMapOf<Int, MutableSet<FlagListenable.Listener>>() - private val listenerFlagIds = mutableMapOf<FlagListenable.Listener, MutableSet<Int>>() + private val booleanFlags = mutableMapOf<String, Boolean>() + private val stringFlags = mutableMapOf<String, String>() + private val intFlags = mutableMapOf<String, Int>() + private val knownFlagNames = mutableMapOf<String, String>() + private val flagListeners = mutableMapOf<String, MutableSet<FlagListenable.Listener>>() + private val listenerflagNames = mutableMapOf<FlagListenable.Listener, MutableSet<String>>() init { FlagsFactory.knownFlags.forEach { entry: Map.Entry<String, Flag<*>> -> - knownFlagNames[entry.value.id] = entry.key + knownFlagNames[entry.value.name] = entry.key } } fun set(flag: BooleanFlag, value: Boolean) { - if (booleanFlags.put(flag.id, value)?.let { value != it } != false) { + if (booleanFlags.put(flag.name, value)?.let { value != it } != false) { notifyFlagChanged(flag) } } fun set(flag: ResourceBooleanFlag, value: Boolean) { - if (booleanFlags.put(flag.id, value)?.let { value != it } != false) { + if (booleanFlags.put(flag.name, value)?.let { value != it } != false) { notifyFlagChanged(flag) } } fun set(flag: SysPropBooleanFlag, value: Boolean) { - if (booleanFlags.put(flag.id, value)?.let { value != it } != false) { + if (booleanFlags.put(flag.name, value)?.let { value != it } != false) { notifyFlagChanged(flag) } } fun set(flag: StringFlag, value: String) { - if (stringFlags.put(flag.id, value)?.let { value != it } == null) { + if (stringFlags.put(flag.name, value)?.let { value != it } == null) { notifyFlagChanged(flag) } } fun set(flag: ResourceStringFlag, value: String) { - if (stringFlags.put(flag.id, value)?.let { value != it } == null) { + if (stringFlags.put(flag.name, value)?.let { value != it } == null) { notifyFlagChanged(flag) } } @@ -73,7 +73,7 @@ class FakeFeatureFlags : FeatureFlags { * and the flag value *does* matter, you'll notice when the flag is flipped and tests * start failing. */ - fun setDefault(flag: BooleanFlag) = booleanFlags.putIfAbsent(flag.id, flag.default) + fun setDefault(flag: BooleanFlag) = booleanFlags.putIfAbsent(flag.name, flag.default) /** * Set the given flag's default value if no other value has been set. @@ -86,10 +86,10 @@ class FakeFeatureFlags : FeatureFlags { * and the flag value *does* matter, you'll notice when the flag is flipped and tests * start failing. */ - fun setDefault(flag: SysPropBooleanFlag) = booleanFlags.putIfAbsent(flag.id, flag.default) + fun setDefault(flag: SysPropBooleanFlag) = booleanFlags.putIfAbsent(flag.name, flag.default) private fun notifyFlagChanged(flag: Flag<*>) { - flagListeners[flag.id]?.let { listeners -> + flagListeners[flag.name]?.let { listeners -> listeners.forEach { listener -> listener.onFlagChanged( object : FlagListenable.FlagEvent { @@ -101,30 +101,30 @@ class FakeFeatureFlags : FeatureFlags { } } - override fun isEnabled(flag: UnreleasedFlag): Boolean = requireBooleanValue(flag.id) + override fun isEnabled(flag: UnreleasedFlag): Boolean = requireBooleanValue(flag.name) - override fun isEnabled(flag: ReleasedFlag): Boolean = requireBooleanValue(flag.id) + override fun isEnabled(flag: ReleasedFlag): Boolean = requireBooleanValue(flag.name) - override fun isEnabled(flag: ResourceBooleanFlag): Boolean = requireBooleanValue(flag.id) + override fun isEnabled(flag: ResourceBooleanFlag): Boolean = requireBooleanValue(flag.name) - override fun isEnabled(flag: SysPropBooleanFlag): Boolean = requireBooleanValue(flag.id) + override fun isEnabled(flag: SysPropBooleanFlag): Boolean = requireBooleanValue(flag.name) - override fun getString(flag: StringFlag): String = requireStringValue(flag.id) + override fun getString(flag: StringFlag): String = requireStringValue(flag.name) - override fun getString(flag: ResourceStringFlag): String = requireStringValue(flag.id) + override fun getString(flag: ResourceStringFlag): String = requireStringValue(flag.name) - override fun getInt(flag: IntFlag): Int = requireIntValue(flag.id) + override fun getInt(flag: IntFlag): Int = requireIntValue(flag.name) - override fun getInt(flag: ResourceIntFlag): Int = requireIntValue(flag.id) + override fun getInt(flag: ResourceIntFlag): Int = requireIntValue(flag.name) override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) { - flagListeners.getOrPut(flag.id) { mutableSetOf() }.add(listener) - listenerFlagIds.getOrPut(listener) { mutableSetOf() }.add(flag.id) + flagListeners.getOrPut(flag.name) { mutableSetOf() }.add(listener) + listenerflagNames.getOrPut(listener) { mutableSetOf() }.add(flag.name) } override fun removeListener(listener: FlagListenable.Listener) { - listenerFlagIds.remove(listener)?.let { - flagIds -> flagIds.forEach { + listenerflagNames.remove(listener)?.let { + flagNames -> flagNames.forEach { id -> flagListeners[id]?.remove(listener) } } @@ -134,22 +134,22 @@ class FakeFeatureFlags : FeatureFlags { // no-op } - private fun flagName(flagId: Int): String { - return knownFlagNames[flagId] ?: "UNKNOWN(id=$flagId)" + private fun flagName(flagName: String): String { + return knownFlagNames[flagName] ?: "UNKNOWN($flagName)" } - private fun requireBooleanValue(flagId: Int): Boolean { - return booleanFlags[flagId] - ?: error("Flag ${flagName(flagId)} was accessed as boolean but not specified.") + private fun requireBooleanValue(flagName: String): Boolean { + return booleanFlags[flagName] + ?: error("Flag ${flagName(flagName)} was accessed as boolean but not specified.") } - private fun requireStringValue(flagId: Int): String { - return stringFlags[flagId] - ?: error("Flag ${flagName(flagId)} was accessed as string but not specified.") + private fun requireStringValue(flagName: String): String { + return stringFlags[flagName] + ?: error("Flag ${flagName(flagName)} was accessed as string but not specified.") } - private fun requireIntValue(flagId: Int): Int { - return intFlags[flagId] - ?: error("Flag ${flagName(flagId)} was accessed as int but not specified.") + private fun requireIntValue(flagName: String): Int { + return intFlags[flagName] + ?: error("Flag ${flagName(flagName)} was accessed as int but not specified.") } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 15ce055bcc63..faebcaac1be3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -146,7 +146,6 @@ class FakeKeyguardRepository : KeyguardRepository { _animateBottomAreaDozingTransitions.tryEmit(animate) } - @Deprecated("Deprecated as part of b/278057014") override fun setBottomAreaAlpha(alpha: Float) { _bottomAreaAlpha.value = alpha @@ -257,8 +256,11 @@ class FakeKeyguardRepository : KeyguardRepository { goingToFullShade: Boolean, occlusionTransitionRunning: Boolean ) { - _keyguardRootViewVisibility.value = KeyguardRootViewVisibilityState( - statusBarState, goingToFullShade, occlusionTransitionRunning - ) + _keyguardRootViewVisibility.value = + KeyguardRootViewVisibilityState( + statusBarState, + goingToFullShade, + occlusionTransitionRunning + ) } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt index 6cffb669d466..507267e2d185 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt @@ -32,7 +32,6 @@ import com.android.systemui.keyguard.data.repository.FakeCommandQueue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor import com.android.systemui.keyguard.shared.model.WakeSleepReason import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.keyguard.shared.model.WakefulnessState @@ -98,7 +97,7 @@ class SceneTestUtils( fun fakeSceneContainerRepository( containerConfig: SceneContainerConfig = fakeSceneContainerConfig(), ): SceneContainerRepository { - return SceneContainerRepository(containerConfig) + return SceneContainerRepository(applicationScope(), containerConfig) } fun fakeSceneKeys(): List<SceneKey> { @@ -176,23 +175,14 @@ class SceneTestUtils( fun bouncerViewModel( bouncerInteractor: BouncerInteractor, + authenticationInteractor: AuthenticationInteractor, ): BouncerViewModel { return BouncerViewModel( applicationContext = context, applicationScope = applicationScope(), - interactor = bouncerInteractor, - featureFlags = featureFlags, - ) - } - - fun lockScreenSceneInteractor( - authenticationInteractor: AuthenticationInteractor, - bouncerInteractor: BouncerInteractor, - ): LockscreenSceneInteractor { - return LockscreenSceneInteractor( - applicationScope = applicationScope(), - authenticationInteractor = authenticationInteractor, bouncerInteractor = bouncerInteractor, + authenticationInteractor = authenticationInteractor, + featureFlags = featureFlags, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt index 1403cea1c625..3fd11a1db96f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt @@ -26,7 +26,7 @@ class FakeDisplayTracker constructor(val context: Context) : DisplayTracker { override var defaultDisplayId: Int = Display.DEFAULT_DISPLAY override var allDisplays: Array<Display> = displayManager.displays - private val displayCallbacks: MutableList<DisplayTracker.Callback> = ArrayList() + val displayCallbacks: MutableList<DisplayTracker.Callback> = ArrayList() private val brightnessCallbacks: MutableList<DisplayTracker.Callback> = ArrayList() override fun addDisplayChangeCallback(callback: DisplayTracker.Callback, executor: Executor) { displayCallbacks.add(callback) @@ -43,12 +43,12 @@ class FakeDisplayTracker constructor(val context: Context) : DisplayTracker { brightnessCallbacks.remove(callback) } - fun setDefaultDisplay(displayId: Int) { - defaultDisplayId = displayId + override fun getDisplay(displayId: Int): Display { + return allDisplays.filter { display -> display.displayId == displayId }[0] } - fun setDisplays(displays: Array<Display>) { - allDisplays = displays + fun setDefaultDisplay(displayId: Int) { + defaultDisplayId = displayId } fun triggerOnDisplayAdded(displayId: Int) { diff --git a/services/Android.bp b/services/Android.bp index 53dc06805f14..9264172974e1 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -115,7 +115,6 @@ filegroup { ":services.profcollect-sources", ":services.restrictions-sources", ":services.searchui-sources", - ":services.selectiontoolbar-sources", ":services.smartspace-sources", ":services.soundtrigger-sources", ":services.systemcaptions-sources", @@ -174,7 +173,6 @@ java_library { "services.profcollect", "services.restrictions", "services.searchui", - "services.selectiontoolbar", "services.smartspace", "services.soundtrigger", "services.systemcaptions", diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 37abe1b1aee3..061b4221ecf7 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -4204,7 +4204,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // We don't send an empty response to IME so that it doesn't cause UI flicker // on the IME side if it arrives before the input view is finished on the IME. mInlineSessionController.resetInlineFillUiLocked(); - mCurrentViewId = null; + + if ((viewState.getState() & + ViewState.STATE_PENDING_CREATE_INLINE_REQUEST) != 0) { + // View was exited before Inline Request sent back, do not set it to + // null yet to let onHandleAssistData finish processing + } else { + mCurrentViewId = null; + } + mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED); diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java index 631b4531b9b9..fd45d2423227 100644 --- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java +++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java @@ -281,9 +281,9 @@ class AssociationRequestsProcessor { final long timestamp = System.currentTimeMillis(); final AssociationInfo association = new AssociationInfo(id, userId, packageName, - macAddress, displayName, deviceProfile, associatedDevice, selfManaged, - /* notifyOnDeviceNearby */ false, /* revoked */ false, timestamp, Long.MAX_VALUE, - /* systemDataSyncFlags */ 0); + /* tag */ null, macAddress, displayName, deviceProfile, associatedDevice, + selfManaged, /* notifyOnDeviceNearby */ false, /* revoked */ false, + timestamp, Long.MAX_VALUE, /* systemDataSyncFlags */ 0); if (deviceProfile != null) { // If the "Device Profile" is specified, make the companion application a holder of the diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index c5501f1d3e0d..e80cba7946df 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -668,7 +668,6 @@ public class CompanionDeviceManagerService extends SystemService { addOnAssociationsChangedListener_enforcePermission(); enforceCallerIsSystemOrCanInteractWithUserId(getContext(), userId); - mListeners.register(listener, userId); } @@ -1015,6 +1014,18 @@ public class CompanionDeviceManagerService extends SystemService { } @Override + public void setAssociationTag(int associationId, String tag) { + AssociationInfo association = getAssociationWithCallerChecks(associationId); + association = AssociationInfo.builder(association).setTag(tag).build(); + mAssociationStore.updateAssociation(association); + } + + @Override + public void clearAssociationTag(int associationId) { + setAssociationTag(associationId, null); + } + + @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) throws RemoteException { diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java index 54798f4f7fcc..b4b93793d549 100644 --- a/services/companion/java/com/android/server/companion/PersistentDataStore.java +++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java @@ -171,6 +171,7 @@ final class PersistentDataStore { private static final String XML_TAG_ASSOCIATION = "association"; private static final String XML_TAG_PREVIOUSLY_USED_IDS = "previously-used-ids"; private static final String XML_TAG_PACKAGE = "package"; + private static final String XML_TAG_TAG = "tag"; private static final String XML_TAG_ID = "id"; private static final String XML_ATTR_PERSISTENCE_VERSION = "persistence-version"; @@ -421,6 +422,7 @@ final class PersistentDataStore { requireStartOfTag(parser, XML_TAG_ASSOCIATION); final String appPackage = readStringAttribute(parser, XML_ATTR_PACKAGE); + final String tag = readStringAttribute(parser, XML_TAG_TAG); final String deviceAddress = readStringAttribute(parser, LEGACY_XML_ATTR_DEVICE); if (appPackage == null || deviceAddress == null) return; @@ -429,7 +431,7 @@ final class PersistentDataStore { final boolean notify = readBooleanAttribute(parser, XML_ATTR_NOTIFY_DEVICE_NEARBY); final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L); - out.add(new AssociationInfo(associationId, userId, appPackage, + out.add(new AssociationInfo(associationId, userId, appPackage, tag, MacAddress.fromString(deviceAddress), null, profile, null, /* managedByCompanionApp */ false, notify, /* revoked */ false, timeApproved, Long.MAX_VALUE, /* systemDataSyncFlags */ 0)); @@ -456,6 +458,7 @@ final class PersistentDataStore { final int associationId = readIntAttribute(parser, XML_ATTR_ID); final String profile = readStringAttribute(parser, XML_ATTR_PROFILE); final String appPackage = readStringAttribute(parser, XML_ATTR_PACKAGE); + final String tag = readStringAttribute(parser, XML_TAG_TAG); final MacAddress macAddress = stringToMacAddress( readStringAttribute(parser, XML_ATTR_MAC_ADDRESS)); final String displayName = readStringAttribute(parser, XML_ATTR_DISPLAY_NAME); @@ -469,7 +472,7 @@ final class PersistentDataStore { XML_ATTR_SYSTEM_DATA_SYNC_FLAGS, 0); final AssociationInfo associationInfo = createAssociationInfoNoThrow(associationId, userId, - appPackage, macAddress, displayName, profile, selfManaged, notify, revoked, + appPackage, tag, macAddress, displayName, profile, selfManaged, notify, revoked, timeApproved, lastTimeConnected, systemDataSyncFlags); if (associationInfo != null) { out.add(associationInfo); @@ -518,6 +521,7 @@ final class PersistentDataStore { writeIntAttribute(serializer, XML_ATTR_ID, a.getId()); writeStringAttribute(serializer, XML_ATTR_PROFILE, a.getDeviceProfile()); writeStringAttribute(serializer, XML_ATTR_PACKAGE, a.getPackageName()); + writeStringAttribute(serializer, XML_TAG_TAG, a.getTag()); writeStringAttribute(serializer, XML_ATTR_MAC_ADDRESS, a.getDeviceMacAddressAsString()); writeStringAttribute(serializer, XML_ATTR_DISPLAY_NAME, a.getDisplayName()); writeBooleanAttribute(serializer, XML_ATTR_SELF_MANAGED, a.isSelfManaged()); @@ -565,17 +569,17 @@ final class PersistentDataStore { } private static AssociationInfo createAssociationInfoNoThrow(int associationId, - @UserIdInt int userId, @NonNull String appPackage, @Nullable MacAddress macAddress, - @Nullable CharSequence displayName, @Nullable String profile, boolean selfManaged, - boolean notify, boolean revoked, long timeApproved, long lastTimeConnected, - int systemDataSyncFlags) { + @UserIdInt int userId, @NonNull String appPackage, @Nullable String tag, + @Nullable MacAddress macAddress, @Nullable CharSequence displayName, + @Nullable String profile, boolean selfManaged, boolean notify, boolean revoked, + long timeApproved, long lastTimeConnected, int systemDataSyncFlags) { AssociationInfo associationInfo = null; try { // We do not persist AssociatedDevice, which means that AssociationInfo retrieved from // datastore is not guaranteed to be identical to the one from initial association. - associationInfo = new AssociationInfo(associationId, userId, appPackage, macAddress, - displayName, profile, null, selfManaged, notify, revoked, - timeApproved, lastTimeConnected, systemDataSyncFlags); + associationInfo = new AssociationInfo(associationId, userId, appPackage, tag, + macAddress, displayName, profile, null, selfManaged, notify, + revoked, timeApproved, lastTimeConnected, systemDataSyncFlags); } catch (Exception e) { if (DEBUG) Log.w(TAG, "Could not create AssociationInfo", e); } diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java index f45a1c42e14f..8fea078c3183 100644 --- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java +++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java @@ -281,43 +281,30 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange case DEVICE_EVENT_BLE_APPEARED: case DEVICE_EVENT_BT_CONNECTED: case DEVICE_EVENT_SELF_MANAGED_APPEARED: - final boolean alreadyPresent = isDevicePresent(associationId); final boolean added = presentDevicesForSource.add(associationId); + if (!added) { Slog.w(TAG, "Association with id " + associationId + " is ALREADY reported as " + "present by this source, event=" + event); - return; - } - // For backward compatibility, do not send the onDeviceAppeared() callback - // if it already reported BLE device status. - if (event == DEVICE_EVENT_BT_CONNECTED && alreadyPresent) { - Slog.i(TAG, "Ignore sending onDeviceAppeared callback, " - + "device id (" + associationId + ") already present."); - } else { - mCallback.onDeviceAppeared(associationId); } + mCallback.onDeviceAppeared(associationId); + break; case DEVICE_EVENT_BLE_DISAPPEARED: case DEVICE_EVENT_BT_DISCONNECTED: case DEVICE_EVENT_SELF_MANAGED_DISAPPEARED: final boolean removed = presentDevicesForSource.remove(associationId); + if (!removed) { - Log.w(TAG, "Association with id " + associationId + " was NOT reported " + Slog.w(TAG, "Association with id " + associationId + " was NOT reported " + "as present by this source, event= " + event); return; } - final boolean stillPresent = isDevicePresent(associationId); - // For backward compatibility, do not send the onDeviceDisappeared() - // callback when ble scan still presenting. - if (stillPresent) { - Slog.i(TAG, "Ignore sending onDeviceDisappeared callback, " - + "device id (" + associationId + ") is still present."); - } else { - mCallback.onDeviceDisappeared(associationId); - } + + mCallback.onDeviceDisappeared(associationId); break; default: diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 402fbb812812..b61e62310e1a 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -106,10 +106,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private static final String TAG = "VirtualDeviceImpl"; private static final int DEFAULT_VIRTUAL_DISPLAY_FLAGS = - DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC - | DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT - | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY - | DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL + DisplayManager.VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED | DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS; @@ -286,6 +283,10 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub if (mParams.getLockState() == VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED) { flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED; } + if (mParams.getDevicePolicy(VirtualDeviceParams.POLICY_TYPE_RECENTS) + == VirtualDeviceParams.DEVICE_POLICY_CUSTOM) { + flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL; + } return flags; } diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 846283c38327..fc51e2ec0954 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -1369,6 +1369,13 @@ public abstract class PackageManagerInternal { public abstract void setPackageStoppedState(@NonNull String packageName, boolean stopped, @UserIdInt int userId); + /** + * Tells PackageManager when a component (except BroadcastReceivers) of the package is used + * and the package should get out of stopped state and be enabled. + */ + public abstract void notifyComponentUsed(@NonNull String packageName, + @UserIdInt int userId, @NonNull String recentCallingPackage); + /** @deprecated For legacy shell command only. */ @Deprecated public abstract void legacyDumpProfiles(@NonNull String packageName, diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING index 41cca4968f2c..03022b06dfe1 100644 --- a/services/core/java/com/android/server/TEST_MAPPING +++ b/services/core/java/com/android/server/TEST_MAPPING @@ -1,7 +1,13 @@ { "presubmit": [ { - "name": "CtsLocationFineTestCases" + "name": "CtsLocationFineTestCases", + "options": [ + { + // TODO: Wait for test to deflake - b/293934372 + "exclude-filter":"android.location.cts.fine.ScanningSettingsTest" + } + ] }, { "name": "CtsLocationCoarseTestCases" diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 73e827249569..36ef9b7ecc03 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -5069,7 +5069,7 @@ public final class ActiveServices { boolean whileRestarting, boolean permissionsReviewRequired, boolean packageFrozen, boolean enqueueOomAdj) throws TransactionTooLargeException { - if (r.app != null && r.app.getThread() != null) { + if (r.app != null && r.app.isThreadReady()) { sendServiceArgsLocked(r, execInFg, false); return null; } @@ -5116,10 +5116,9 @@ public final class ActiveServices { r.packageName, r.userId, UsageEvents.Event.APP_COMPONENT_USED); } - // Service is now being launched, its package can't be stopped. try { - mAm.mPackageManagerInt.setPackageStoppedState( - r.packageName, false, r.userId); + mAm.mPackageManagerInt.notifyComponentUsed( + r.packageName, r.userId, r.mRecentCallingPackage); } catch (IllegalArgumentException e) { Slog.w(TAG, "Failed trying to unstop package " + r.packageName + ": " + e); @@ -5141,7 +5140,7 @@ public final class ActiveServices { final IApplicationThread thread = app.getThread(); final int pid = app.getPid(); final UidRecord uidRecord = app.getUidRecord(); - if (thread != null) { + if (app.isThreadReady()) { try { if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, @@ -5173,7 +5172,7 @@ public final class ActiveServices { final int pid = app.getPid(); final UidRecord uidRecord = app.getUidRecord(); r.isolationHostProc = app; - if (thread != null) { + if (app.isThreadReady()) { try { if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, @@ -5573,7 +5572,7 @@ public final class ActiveServices { boolean oomAdjusted = false; // Tell the service that it has been unbound. - if (r.app != null && r.app.getThread() != null) { + if (r.app != null && r.app.isThreadReady()) { for (int i = r.bindings.size() - 1; i >= 0; i--) { IntentBindRecord ibr = r.bindings.valueAt(i); if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bringing down binding " + ibr @@ -5715,7 +5714,7 @@ public final class ActiveServices { mAm.mBatteryStatsService.noteServiceStopLaunch(r.appInfo.uid, r.name.getPackageName(), r.name.getClassName()); stopServiceAndUpdateAllowlistManagerLocked(r); - if (r.app.getThread() != null) { + if (r.app.isThreadReady()) { // Bump the process to the top of LRU list mAm.updateLruProcessLocked(r.app, false, null); updateServiceForegroundLocked(r.app.mServices, false); @@ -5879,7 +5878,7 @@ public final class ActiveServices { if (!c.serviceDead) { if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Disconnecting binding " + b.intent + ": shouldUnbind=" + b.intent.hasBound); - if (s.app != null && s.app.getThread() != null && b.intent.apps.size() == 0 + if (s.app != null && s.app.isThreadReady() && b.intent.apps.size() == 0 && b.intent.hasBound) { try { bumpServiceExecutingLocked(s, false, "unbind", OOM_ADJ_REASON_UNBIND_SERVICE); @@ -6381,7 +6380,7 @@ public final class ActiveServices { sr.pendingStarts.add(new ServiceRecord.StartItem(sr, true, sr.getLastStartId(), baseIntent, null, 0, null, null, ActivityManager.PROCESS_STATE_UNKNOWN)); - if (sr.app != null && sr.app.getThread() != null) { + if (sr.app != null && sr.app.isThreadReady()) { // We always run in the foreground, since this is called as // part of the "remove task" UI operation. try { @@ -6434,6 +6433,7 @@ public final class ActiveServices { } updateServiceConnectionActivitiesLocked(psr); psr.removeAllConnections(); + psr.removeAllSdkSandboxConnections(); psr.mAllowlistManager = false; diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 2058b4cff740..3fd7d7e6db46 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -22,6 +22,7 @@ import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_NONE; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER_QUICK; import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY; import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY; +import static com.android.server.am.BroadcastConstants.getDeviceConfigBoolean; import android.annotation.NonNull; import android.app.ActivityThread; @@ -154,6 +155,11 @@ final class ActivityManagerConstants extends ContentObserver { static final String KEY_TIERED_CACHED_ADJ_DECAY_TIME = "tiered_cached_adj_decay_time"; static final String KEY_USE_MODERN_TRIM = "use_modern_trim"; + /** + * Whether or not to enable the new oom adjuster implementation. + */ + static final String KEY_ENABLE_NEW_OOMADJ = "enable_new_oom_adj"; + private static final int DEFAULT_MAX_CACHED_PROCESSES = 1024; private static final boolean DEFAULT_PRIORITIZE_ALARM_BROADCASTS = true; private static final long DEFAULT_FGSERVICE_MIN_SHOWN_TIME = 2*1000; @@ -217,6 +223,11 @@ final class ActivityManagerConstants extends ContentObserver { private static final boolean DEFAULT_USE_MODERN_TRIM = true; /** + * The default value to {@link #KEY_ENABLE_NEW_OOMADJ}. + */ + private static final boolean DEFAULT_ENABLE_NEW_OOM_ADJ = false; + + /** * Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED} */ private static final int @@ -1052,6 +1063,9 @@ final class ActivityManagerConstants extends ContentObserver { /** @see #KEY_USE_MODERN_TRIM */ public boolean USE_MODERN_TRIM = DEFAULT_USE_MODERN_TRIM; + /** @see #KEY_ENABLE_NEW_OOMADJ */ + public boolean ENABLE_NEW_OOMADJ = DEFAULT_ENABLE_NEW_OOM_ADJ; + /** * Indicates whether PSS profiling in AppProfiler is disabled or not. */ @@ -1321,6 +1335,7 @@ final class ActivityManagerConstants extends ContentObserver { CUR_TRIM_EMPTY_PROCESSES = rawMaxEmptyProcesses / 2; CUR_TRIM_CACHED_PROCESSES = (Integer.min(CUR_MAX_CACHED_PROCESSES, MAX_CACHED_PROCESSES) - rawMaxEmptyProcesses) / 3; + loadNativeBootDeviceConfigConstants(); mDefaultDisableAppProfilerPssProfiling = context.getResources().getBoolean( R.bool.config_am_disablePssProfiling); APP_PROFILER_PSS_PROFILING_DISABLED = mDefaultDisableAppProfilerPssProfiling; @@ -1363,6 +1378,11 @@ final class ActivityManagerConstants extends ContentObserver { DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS)); } + private void loadNativeBootDeviceConfigConstants() { + ENABLE_NEW_OOMADJ = getDeviceConfigBoolean(KEY_ENABLE_NEW_OOMADJ, + DEFAULT_ENABLE_NEW_OOM_ADJ); + } + public void setOverrideMaxCachedProcesses(int value) { mOverrideMaxCachedProcesses = value; updateMaxCachedProcesses(); @@ -2013,6 +2033,13 @@ final class ActivityManagerConstants extends ContentObserver { DEFAULT_USE_MODERN_TRIM); } + private void updateEnableNewOomAdj() { + ENABLE_NEW_OOMADJ = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT, + KEY_ENABLE_NEW_OOMADJ, + DEFAULT_ENABLE_NEW_OOM_ADJ); + } + private void updateFGSPermissionEnforcementFlagsIfNecessary(@NonNull String name) { ForegroundServiceTypePolicy.getDefaultPolicy() .updatePermissionEnforcementFlagIfNecessary(name); @@ -2209,6 +2236,9 @@ final class ActivityManagerConstants extends ContentObserver { pw.print(" "); pw.print(KEY_TIERED_CACHED_ADJ_DECAY_TIME); pw.print("="); pw.println(TIERED_CACHED_ADJ_DECAY_TIME); + pw.print(" "); pw.print(KEY_ENABLE_NEW_OOMADJ); + pw.print("="); pw.println(ENABLE_NEW_OOMADJ); + pw.print(" "); pw.print(KEY_DISABLE_APP_PROFILER_PSS_PROFILING); pw.print("="); pw.println(APP_PROFILER_PSS_PROFILING_DISABLED); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index a0fae26fcac1..a53c2fb44873 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2029,6 +2029,7 @@ public class ActivityManagerService extends IActivityManager.Stub app.makeActive(mSystemThread.getApplicationThread(), mProcessStats); app.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_SYSTEM); addPidLocked(app); + mOomAdjuster.onProcessBeginLocked(app); updateLruProcessLocked(app, false, null); updateOomAdjLocked(OOM_ADJ_REASON_SYSTEM_INIT); } @@ -2422,7 +2423,9 @@ public class ActivityManagerService extends IActivityManager.Stub mProcessList.init(this, activeUids, mPlatformCompat); mAppProfiler = new AppProfiler(this, BackgroundThread.getHandler().getLooper(), null); mPhantomProcessList = new PhantomProcessList(this); - mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids, handlerThread); + mOomAdjuster = mConstants.ENABLE_NEW_OOMADJ + ? new OomAdjusterModernImpl(this, mProcessList, activeUids, handlerThread) + : new OomAdjuster(this, mProcessList, activeUids, handlerThread); mIntentFirewall = null; mProcessStats = new ProcessStatsService(this, mContext.getCacheDir()); @@ -2483,7 +2486,9 @@ public class ActivityManagerService extends IActivityManager.Stub mAppProfiler = new AppProfiler(this, BackgroundThread.getHandler().getLooper(), new LowMemDetector(this)); mPhantomProcessList = new PhantomProcessList(this); - mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids); + mOomAdjuster = mConstants.ENABLE_NEW_OOMADJ + ? new OomAdjusterModernImpl(this, mProcessList, activeUids) + : new OomAdjuster(this, mProcessList, activeUids); // Broadcast policy parameters final BroadcastConstants foreConstants = new BroadcastConstants( @@ -3090,6 +3095,22 @@ public class ActivityManagerService extends IActivityManager.Stub } } + /** + * Enforces that the uid of the caller matches the uid of the package. + * + * @param packageName the name of the package to match uid against. + * @param callingUid the uid of the caller. + * @throws SecurityException if the calling uid doesn't match uid of the package. + */ + private void enforceCallingPackage(String packageName, int callingUid) { + final int userId = UserHandle.getUserId(callingUid); + final int packageUid = getPackageManagerInternal().getPackageUid(packageName, + /*flags=*/ 0, userId); + if (packageUid != callingUid) { + throw new SecurityException(packageName + " does not belong to uid " + callingUid); + } + } + @Override public void setPackageScreenCompatMode(String packageName, int mode) { mActivityTaskManager.setPackageScreenCompatMode(packageName, mode); @@ -4595,6 +4616,7 @@ public class ActivityManagerService extends IActivityManager.Stub EventLogTags.writeAmProcBound(app.userId, pid, app.processName); synchronized (mProcLock) { + mOomAdjuster.onProcessBeginLocked(app); mOomAdjuster.setAttachingProcessStatesLSP(app); clearProcessForegroundLocked(app); app.setDebugging(false); @@ -6980,6 +7002,7 @@ public class ActivityManagerService extends IActivityManager.Stub sdkSandboxClientAppPackage, new HostingRecord(HostingRecord.HOSTING_TYPE_ADDED_APPLICATION, customProcess != null ? customProcess : info.processName)); + mOomAdjuster.onProcessBeginLocked(app); updateLruProcessLocked(app, false, null); updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN); } @@ -13697,13 +13720,16 @@ public class ActivityManagerService extends IActivityManager.Stub // A backup agent has just come up @Override public void backupAgentCreated(String agentPackageName, IBinder agent, int userId) { + final int callingUid = Binder.getCallingUid(); + enforceCallingPackage(agentPackageName, callingUid); + // Resolve the target user id and enforce permissions. - userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), + userId = mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, /* allowAll */ false, ALLOW_FULL_ONLY, "backupAgentCreated", null); if (DEBUG_BACKUP) { Slog.v(TAG_BACKUP, "backupAgentCreated: " + agentPackageName + " = " + agent + " callingUserId = " + UserHandle.getCallingUserId() + " userId = " + userId - + " callingUid = " + Binder.getCallingUid() + " uid = " + Process.myUid()); + + " callingUid = " + callingUid + " uid = " + Process.myUid()); } synchronized(this) { diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index b7fc4844413f..04ebb2b14af6 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -84,6 +84,7 @@ import android.power.PowerStatsInternal; import android.provider.Settings; import android.telephony.DataConnectionRealTimeInfo; import android.telephony.ModemActivityInfo; +import android.telephony.NetworkRegistrationInfo; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; import android.util.IndentingPrintWriter; @@ -1587,7 +1588,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub @Override @EnforcePermission(UPDATE_DEVICE_STATS) public void notePhoneDataConnectionState(final int dataType, final boolean hasData, - final int serviceType, final int nrFrequency) { + final int serviceType, @NetworkRegistrationInfo.NRState final int nrState, + final int nrFrequency) { super.notePhoneDataConnectionState_enforcePermission(); synchronized (mLock) { @@ -1596,7 +1598,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub mHandler.post(() -> { synchronized (mStats) { mStats.notePhoneDataConnectionStateLocked(dataType, hasData, serviceType, - nrFrequency, elapsedRealtime, uptime); + nrState, nrFrequency, elapsedRealtime, uptime); } }); } diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java index 8c1fd516028e..2fff79b3fb26 100644 --- a/services/core/java/com/android/server/am/BroadcastConstants.java +++ b/services/core/java/com/android/server/am/BroadcastConstants.java @@ -373,7 +373,7 @@ public class BroadcastConstants { * Return the {@link SystemProperty} name for the given key in our * {@link DeviceConfig} namespace. */ - private @NonNull String propertyFor(@NonNull String key) { + private static @NonNull String propertyFor(@NonNull String key) { return "persist.device_config." + NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT + "." + key; } @@ -382,11 +382,11 @@ public class BroadcastConstants { * {@link DeviceConfig} namespace, but with a different prefix that can be * used to locally override the {@link DeviceConfig} value. */ - private @NonNull String propertyOverrideFor(@NonNull String key) { + private static @NonNull String propertyOverrideFor(@NonNull String key) { return "persist.sys." + NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT + "." + key; } - private boolean getDeviceConfigBoolean(@NonNull String key, boolean def) { + static boolean getDeviceConfigBoolean(@NonNull String key, boolean def) { return SystemProperties.getBoolean(propertyOverrideFor(key), SystemProperties.getBoolean(propertyFor(key), def)); } diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java index e26ee9c9d747..65fd54afbbfb 100644 --- a/services/core/java/com/android/server/am/ContentProviderHelper.java +++ b/services/core/java/com/android/server/am/ContentProviderHelper.java @@ -505,12 +505,11 @@ public class ContentProviderHelper { cpr.appInfo.packageName, userId, Event.APP_COMPONENT_USED); } - // Content provider is now in use, its package can't be stopped. try { checkTime(startTime, "getContentProviderImpl: before set stopped state"); - mService.mPackageManagerInt.setPackageStoppedState( - cpr.appInfo.packageName, false, userId); + mService.mPackageManagerInt.notifyComponentUsed( + cpr.appInfo.packageName, userId, callingPackage); checkTime(startTime, "getContentProviderImpl: after set stopped state"); } catch (IllegalArgumentException e) { Slog.w(TAG, "Failed trying to unstop package " diff --git a/services/core/java/com/android/server/am/DataConnectionStats.java b/services/core/java/com/android/server/am/DataConnectionStats.java index f0910dcb0da2..e25dd0599b6c 100644 --- a/services/core/java/com/android/server/am/DataConnectionStats.java +++ b/services/core/java/com/android/server/am/DataConnectionStats.java @@ -98,18 +98,13 @@ public class DataConnectionStats extends BroadcastReceiver { mServiceState.getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN); int networkType = regInfo == null ? TelephonyManager.NETWORK_TYPE_UNKNOWN : regInfo.getAccessNetworkTechnology(); - // If the device is in NSA NR connection the networkType will report as LTE. - // For cell dwell rate metrics, this should report NR instead. - if (mNrState == NetworkRegistrationInfo.NR_STATE_CONNECTED) { - networkType = TelephonyManager.NETWORK_TYPE_NR; - } if (DEBUG) { Log.d(TAG, String.format("Noting data connection for network type %s: %svisible", networkType, visible ? "" : "not ")); } try { mBatteryStats.notePhoneDataConnectionState(networkType, visible, - mServiceState.getState(), mServiceState.getNrFrequencyRange()); + mServiceState.getState(), mNrState, mServiceState.getNrFrequencyRange()); } catch (RemoteException e) { Log.w(TAG, "Error noting data connection state", e); } diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 459c6ff3504a..d3176ee46464 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -41,6 +41,7 @@ import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI; import static android.app.ActivityManager.PROCESS_STATE_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND; +import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP; @@ -124,6 +125,7 @@ import static com.android.server.am.ProcessList.UNKNOWN_ADJ; import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerInternal.OomAdjReason; @@ -369,20 +371,21 @@ public class OomAdjuster { */ private final Handler mProcessGroupHandler; - private final int[] mTmpSchedGroup = new int[1]; + protected final int[] mTmpSchedGroup = new int[1]; - private final ActivityManagerService mService; - private final ProcessList mProcessList; - private final ActivityManagerGlobalLock mProcLock; + final ActivityManagerService mService; + final ProcessList mProcessList; + final ActivityManagerGlobalLock mProcLock; private final int mNumSlots; - private final ArrayList<ProcessRecord> mTmpProcessList = new ArrayList<ProcessRecord>(); - private final ArrayList<UidRecord> mTmpBecameIdle = new ArrayList<UidRecord>(); - private final ActiveUids mTmpUidRecords; - private final ArrayDeque<ProcessRecord> mTmpQueue; - private final ArraySet<ProcessRecord> mTmpProcessSet = new ArraySet<>(); - private final ArraySet<ProcessRecord> mPendingProcessSet = new ArraySet<>(); - private final ArraySet<ProcessRecord> mProcessesInCycle = new ArraySet<>(); + protected final ArrayList<ProcessRecord> mTmpProcessList = new ArrayList<ProcessRecord>(); + protected final ArrayList<ProcessRecord> mTmpProcessList2 = new ArrayList<ProcessRecord>(); + protected final ArrayList<UidRecord> mTmpBecameIdle = new ArrayList<UidRecord>(); + protected final ActiveUids mTmpUidRecords; + protected final ArrayDeque<ProcessRecord> mTmpQueue; + protected final ArraySet<ProcessRecord> mTmpProcessSet = new ArraySet<>(); + protected final ArraySet<ProcessRecord> mPendingProcessSet = new ArraySet<>(); + protected final ArraySet<ProcessRecord> mProcessesInCycle = new ArraySet<>(); /** * Flag to mark if there is an ongoing oomAdjUpdate: potentially the oomAdjUpdate @@ -412,7 +415,7 @@ public class OomAdjuster { this(service, processList, activeUids, createAdjusterThread()); } - private static ServiceThread createAdjusterThread() { + static ServiceThread createAdjusterThread() { // The process group is usually critical to the response time of foreground app, so the // setter should apply it as soon as possible. final ServiceThread adjusterThread = @@ -532,7 +535,7 @@ public class OomAdjuster { mPendingProcessSet.remove(app); mProcessesInCycle.clear(); - computeOomAdjLSP(app, cachedAdj, topApp, false, now, false, true); + computeOomAdjLSP(app, cachedAdj, topApp, false, now, false, true, oomAdjReason, true); if (!mProcessesInCycle.isEmpty()) { // We can't use the score here if there is a cycle, abort. for (int i = mProcessesInCycle.size() - 1; i >= 0; i--) { @@ -550,7 +553,7 @@ public class OomAdjuster { && (uidRec.getSetProcState() != uidRec.getCurProcState() || uidRec.getSetCapability() != uidRec.getCurCapability() || uidRec.isSetAllowListed() != uidRec.isCurAllowListed())) { - ActiveUids uids = mTmpUidRecords; + final ActiveUids uids = mTmpUidRecords; uids.clear(); uids.put(uidRec.getUid(), uidRec); updateUidsLSP(uids, SystemClock.elapsedRealtime()); @@ -633,19 +636,20 @@ public class OomAdjuster { } @GuardedBy({"mService", "mProcLock"}) - private boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) { + protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) { final ProcessRecord topApp = mService.getTopApp(); Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); mService.mOomAdjProfiler.oomAdjStarted(); mAdjSeq++; - // Firstly, try to see if the importance of itself gets changed final ProcessStateRecord state = app.mState; final boolean wasCached = state.isCached(); final int oldAdj = state.getCurRawAdj(); final int cachedAdj = oldAdj >= CACHED_APP_MIN_ADJ ? oldAdj : UNKNOWN_ADJ; + + // Firstly, try to see if the importance of itself gets changed final boolean wasBackground = ActivityManager.isProcStateBackground( state.getSetProcState()); final int oldCap = state.getSetCapability(); @@ -693,8 +697,6 @@ public class OomAdjuster { mPendingProcessSet.clear(); if (!containsCycle) { - // Reset the flag - state.setReachable(false); // Remove this app from the return list because we've done the computation on it. processes.remove(app); } @@ -718,8 +720,13 @@ public class OomAdjuster { return true; } + /** + * Collect the reachable processes from the given {@code apps}, the result will be + * returned in the given {@code processes}, which will include the processes from + * the given {@code apps}. + */ @GuardedBy("mService") - private boolean collectReachableProcessesLocked(ArraySet<ProcessRecord> apps, + protected boolean collectReachableProcessesLocked(ArraySet<ProcessRecord> apps, ArrayList<ProcessRecord> processes, ActiveUids uids) { final ArrayDeque<ProcessRecord> queue = mTmpQueue; queue.clear(); @@ -824,11 +831,15 @@ public class OomAdjuster { if (size > 0) { // Reverse the process list, since the updateOomAdjInnerLSP scans from the end of it. for (int l = 0, r = size - 1; l < r; l++, r--) { - ProcessRecord t = processes.get(l); - processes.set(l, processes.get(r)); + final ProcessRecord t = processes.get(l); + final ProcessRecord u = processes.get(r); + t.mState.setReachable(false); + u.mState.setReachable(false); + processes.set(l, u); processes.set(r, t); } } + return containsCycle; } @@ -928,24 +939,18 @@ public class OomAdjuster { * Update OomAdj for all processes within the given list (could be partial), or the whole LRU * list if the given list is null; when it's partial update, each process's client proc won't * get evaluated recursively here. + * + * <p>Note: If the given {@code processes} is not null, the expectation to it is, the caller + * must have called {@link collectReachableProcessesLocked} on it. */ @GuardedBy({"mService", "mProcLock"}) - private void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp, + protected void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp, ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles, boolean startProfiling) { - if (startProfiling) { - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); - mService.mOomAdjProfiler.oomAdjStarted(); - } - final long now = SystemClock.uptimeMillis(); - final long nowElapsed = SystemClock.elapsedRealtime(); - final long oldTime = now - mConstants.mMaxEmptyTimeMillis; final boolean fullUpdate = processes == null; + final ArrayList<ProcessRecord> activeProcesses = fullUpdate + ? mProcessList.getLruProcessesLOSP() : processes; ActiveUids activeUids = uids; - ArrayList<ProcessRecord> activeProcesses = fullUpdate ? mProcessList.getLruProcessesLOSP() - : processes; - final int numProc = activeProcesses.size(); - if (activeUids == null) { final int numUids = mActiveUids.size(); activeUids = mTmpUidRecords; @@ -956,14 +961,14 @@ public class OomAdjuster { } } - // Reset state in all uid records. - for (int i = activeUids.size() - 1; i >= 0; i--) { - final UidRecord uidRec = activeUids.valueAt(i); - if (DEBUG_UID_OBSERVERS) { - Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec); - } - uidRec.reset(); + if (startProfiling) { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); + mService.mOomAdjProfiler.oomAdjStarted(); } + final long now = SystemClock.uptimeMillis(); + final long nowElapsed = SystemClock.elapsedRealtime(); + final long oldTime = now - mConstants.mMaxEmptyTimeMillis; + final int numProc = activeProcesses.size(); mAdjSeq++; if (fullUpdate) { @@ -971,6 +976,9 @@ public class OomAdjuster { mNewNumAServiceProcs = 0; } + // Reset state in all uid records. + resetUidRecordsLsp(activeUids); + boolean retryCycles = false; boolean computeClients = fullUpdate || potentialCycles; @@ -996,8 +1004,9 @@ public class OomAdjuster { if (!app.isKilledByAm() && app.getThread() != null) { state.setProcStateChanged(false); app.mOptRecord.setLastOomAdjChangeReason(oomAdjReason); + // It won't enter cycle if not computing clients. computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, fullUpdate, now, false, - computeClients); // It won't enter cycle if not computing clients. + computeClients, oomAdjReason, true); // if any app encountered a cycle, we need to perform an additional loop later retryCycles |= state.containsCycle(); // Keep the completedAdjSeq to up to date. @@ -1034,7 +1043,7 @@ public class OomAdjuster { final ProcessStateRecord state = app.mState; if (!app.isKilledByAm() && app.getThread() != null && state.containsCycle()) { if (computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, true, now, - true, true)) { + true, true, oomAdjReason, true)) { retryCycles = true; } } @@ -1045,10 +1054,33 @@ public class OomAdjuster { assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP()); + postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime); + + if (startProfiling) { + mService.mOomAdjProfiler.oomAdjEnded(); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } + } + + @GuardedBy({"mService", "mProcLock"}) + private void resetUidRecordsLsp(@NonNull ActiveUids activeUids) { + // Reset state in all uid records. + for (int i = activeUids.size() - 1; i >= 0; i--) { + final UidRecord uidRec = activeUids.valueAt(i); + if (DEBUG_UID_OBSERVERS) { + Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec); + } + uidRec.reset(); + } + } + + @GuardedBy({"mService", "mProcLock"}) + protected void postUpdateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, ActiveUids activeUids, + long now, long nowElapsed, long oldTime) { mNumNonCachedProcs = 0; mNumCachedHiddenProcs = 0; - boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids, + final boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids, oomAdjReason); mNumServiceProcs = mNewNumServiceProcs; @@ -1085,14 +1117,10 @@ public class OomAdjuster { Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms"); } } - if (startProfiling) { - mService.mOomAdjProfiler.oomAdjEnded(); - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - } } @GuardedBy({"mService", "mProcLock"}) - private void assignCachedAdjIfNecessary(ArrayList<ProcessRecord> lruList) { + protected void assignCachedAdjIfNecessary(ArrayList<ProcessRecord> lruList) { final int numLru = lruList.size(); if (mConstants.USE_TIERED_CACHED_ADJ) { final long now = SystemClock.uptimeMillis(); @@ -1413,7 +1441,7 @@ public class OomAdjuster { } @GuardedBy({"mService", "mProcLock"}) - private void updateAppUidRecIfNecessaryLSP(final ProcessRecord app) { + protected void updateAppUidRecIfNecessaryLSP(final ProcessRecord app) { if (!app.isKilledByAm() && app.getThread() != null) { if (app.isolated && app.mServices.numberOfRunningServices() <= 0 && app.getIsolatedEntryPoint() == null) { @@ -1442,7 +1470,7 @@ public class OomAdjuster { } @GuardedBy({"mService", "mProcLock"}) - private void updateUidsLSP(ActiveUids activeUids, final long nowElapsed) { + protected void updateUidsLSP(ActiveUids activeUids, final long nowElapsed) { // This compares previously set procstate to the current procstate in regards to whether // or not the app's network access will be blocked. So, this needs to be called before // we update the UidRecord's procstate by calling {@link UidRecord#setSetProcState}. @@ -1580,7 +1608,7 @@ public class OomAdjuster { return true; } - private final ComputeOomAdjWindowCallback mTmpComputeOomAdjWindowCallback = + protected final ComputeOomAdjWindowCallback mTmpComputeOomAdjWindowCallback = new ComputeOomAdjWindowCallback(); /** These methods are called inline during computeOomAdjLSP(), on the same thread */ @@ -1719,24 +1747,30 @@ public class OomAdjuster { } @GuardedBy({"mService", "mProcLock"}) - private boolean computeOomAdjLSP(ProcessRecord app, int cachedAdj, + protected boolean computeOomAdjLSP(ProcessRecord app, int cachedAdj, ProcessRecord topApp, boolean doingAll, long now, boolean cycleReEval, - boolean computeClients) { + boolean computeClients, int oomAdjReason, boolean couldRecurse) { final ProcessStateRecord state = app.mState; - if (mAdjSeq == state.getAdjSeq()) { - if (state.getAdjSeq() == state.getCompletedAdjSeq()) { - // This adjustment has already been computed successfully. - return false; - } else { - // The process is being computed, so there is a cycle. We cannot - // rely on this process's state. - state.setContainsCycle(true); - mProcessesInCycle.add(app); + if (couldRecurse) { + if (mAdjSeq == state.getAdjSeq()) { + if (state.getAdjSeq() == state.getCompletedAdjSeq()) { + // This adjustment has already been computed successfully. + return false; + } else { + // The process is being computed, so there is a cycle. We cannot + // rely on this process's state. + state.setContainsCycle(true); + mProcessesInCycle.add(app); - return false; + return false; + } } } + int prevAppAdj = getInitialAdj(app); + int prevProcState = getInitialProcState(app); + int prevCapability = getInitialCapability(app); + if (app.getThread() == null) { state.setAdjSeq(mAdjSeq); state.setCurrentSchedulingGroup(SCHED_GROUP_BACKGROUND); @@ -1745,6 +1779,8 @@ public class OomAdjuster { state.setCurRawAdj(CACHED_APP_MAX_ADJ); state.setCompletedAdjSeq(state.getAdjSeq()); state.setCurCapability(PROCESS_CAPABILITY_NONE); + onProcessStateChanged(app, prevProcState); + onProcessOomAdjChanged(app, prevAppAdj); return false; } @@ -1753,7 +1789,7 @@ public class OomAdjuster { state.setAdjTarget(null); state.setEmpty(false); state.setCached(false); - if (!cycleReEval) { + if (!couldRecurse || !cycleReEval) { // Don't reset this flag when doing cycles re-evaluation. state.setNoKillOnBgRestrictedAndIdle(false); // If this UID is currently allowlisted, it should not be frozen. @@ -1764,9 +1800,6 @@ public class OomAdjuster { final int appUid = app.info.uid; final int logUid = mService.mCurOomAdjUid; - int prevAppAdj = state.getCurAdj(); - int prevProcState = state.getCurProcState(); - int prevCapability = state.getCurCapability(); final ProcessServiceRecord psr = app.mServices; if (state.getMaxAdj() <= FOREGROUND_APP_ADJ) { @@ -1812,6 +1845,8 @@ public class OomAdjuster { state.setCurRawProcState(state.getCurProcState()); state.setCurAdj(state.getMaxAdj()); state.setCompletedAdjSeq(state.getAdjSeq()); + onProcessStateChanged(app, prevProcState); + onProcessOomAdjChanged(app, prevAppAdj); // if curAdj is less than prevAppAdj, then this process was promoted return state.getCurAdj() < prevAppAdj || state.getCurProcState() < prevProcState; } @@ -1825,7 +1860,7 @@ public class OomAdjuster { int adj; int schedGroup; int procState; - int capability = cycleReEval ? app.mState.getCurCapability() : 0; + int capability = cycleReEval ? getInitialCapability(app) : 0; boolean foregroundActivities = false; boolean hasVisibleActivities = false; @@ -1904,7 +1939,7 @@ public class OomAdjuster { // value that the caller wants us to. adj = cachedAdj; procState = PROCESS_STATE_CACHED_EMPTY; - if (!state.containsCycle()) { + if (!couldRecurse || !state.containsCycle()) { state.setCached(true); state.setEmpty(true); state.setAdjType("cch-empty"); @@ -2169,8 +2204,10 @@ public class OomAdjuster { } } - boolean boundByNonBgRestricted = state.isCurBoundByNonBgRestrictedApp(); - boolean scheduleLikeTopApp = false; + state.setCurBoundByNonBgRestrictedApp(getInitialIsCurBoundByNonBgRestrictedApp(app)); + + state.setScheduleLikeTopApp(false); + for (int is = psr.numberOfRunningServices() - 1; is >= 0 && (adj > FOREGROUND_APP_ADJ || schedGroup == SCHED_GROUP_BACKGROUND @@ -2243,6 +2280,18 @@ public class OomAdjuster { } } + if (!couldRecurse) { + // We're entering recursive functions below, if we're told it's not a recursive + // loop, abort here. + continue; + } + + + state.setCurRawAdj(adj); + state.setCurRawProcState(procState); + state.setCurrentSchedulingGroup(schedGroup); + state.setCurCapability(capability); + ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = s.getConnections(); for (int conni = serviceConnections.size() - 1; conni >= 0 && (adj > FOREGROUND_APP_ADJ @@ -2263,335 +2312,13 @@ public class OomAdjuster { continue; } - boolean trackedProcState = false; - - ProcessRecord client = cr.binding.client; - if (app.isSdkSandbox && cr.binding.attributedClient != null) { - // For SDK sandboxes, use the attributed client (eg the app that - // requested the sandbox) - client = cr.binding.attributedClient; - } - final ProcessStateRecord cstate = client.mState; - if (computeClients) { - computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now, - cycleReEval, true); - } else { - cstate.setCurRawAdj(cstate.getCurAdj()); - cstate.setCurRawProcState(cstate.getCurProcState()); - } - - int clientAdj = cstate.getCurRawAdj(); - int clientProcState = cstate.getCurRawProcState(); - - final boolean clientIsSystem = clientProcState < PROCESS_STATE_TOP; - - boundByNonBgRestricted |= cstate.isCurBoundByNonBgRestrictedApp() - || clientProcState <= PROCESS_STATE_BOUND_TOP - || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE - && !cstate.isBackgroundRestricted()); - - if (client.mOptRecord.shouldNotFreeze()) { - // Propagate the shouldNotFreeze flag down the bindings. - app.mOptRecord.setShouldNotFreeze(true); - } - - // We always propagate PROCESS_CAPABILITY_BFSL over bindings here, - // but, right before actually setting it to the process, - // we check the final procstate, and remove it if the procsate is below BFGS. - capability |= getBfslCapabilityFromClient(client); - - if (cr.notHasFlag(Context.BIND_WAIVE_PRIORITY)) { - if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) { - capability |= cstate.getCurCapability(); - } - - // If an app has network capability by default - // (by having procstate <= BFGS), then the apps it binds to will get - // elevated to a high enough procstate anyway to get network unless they - // request otherwise, so don't propagate the network capability by default - // in this case unless they explicitly request it. - if ((cstate.getCurCapability() - & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0) { - if (clientProcState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE) { - // This is used to grant network access to Expedited Jobs. - if (cr.hasFlag(Context.BIND_BYPASS_POWER_NETWORK_RESTRICTIONS)) { - capability |= PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK; - } - } else { - capability |= PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK; - } - } - if ((cstate.getCurCapability() - & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0) { - if (clientProcState <= PROCESS_STATE_IMPORTANT_FOREGROUND) { - // This is used to grant network access to User Initiated Jobs. - if (cr.hasFlag(Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS)) { - capability |= PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK; - } - } - } - - if (shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) { - continue; - } - - if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { - // If the other app is cached for any reason, for purposes here - // we are going to consider it empty. The specific cached state - // doesn't propagate except under certain conditions. - clientProcState = PROCESS_STATE_CACHED_EMPTY; - } - String adjType = null; - if (cr.hasFlag(Context.BIND_ALLOW_OOM_MANAGEMENT)) { - // Similar to BIND_WAIVE_PRIORITY, keep it unfrozen. - if (clientAdj < CACHED_APP_MIN_ADJ) { - app.mOptRecord.setShouldNotFreeze(true); - } - // Not doing bind OOM management, so treat - // this guy more like a started service. - if (state.hasShownUi() && !state.getCachedIsHomeProcess()) { - // If this process has shown some UI, let it immediately - // go to the LRU list because it may be pretty heavy with - // UI stuff. We'll tag it with a label just to help - // debug and understand what is going on. - if (adj > clientAdj) { - adjType = "cch-bound-ui-services"; - } - state.setCached(false); - clientAdj = adj; - clientProcState = procState; - } else { - if (now >= (s.lastActivity - + mConstants.MAX_SERVICE_INACTIVITY)) { - // This service has not seen activity within - // recent memory, so allow it to drop to the - // LRU list if there is no other reason to keep - // it around. We'll also tag it with a label just - // to help debug and undertand what is going on. - if (adj > clientAdj) { - adjType = "cch-bound-services"; - } - clientAdj = adj; - } - } - } - if (adj > clientAdj) { - // If this process has recently shown UI, and - // the process that is binding to it is less - // important than being visible, then we don't - // care about the binding as much as we care - // about letting this process get into the LRU - // list to be killed and restarted if needed for - // memory. - if (state.hasShownUi() && !state.getCachedIsHomeProcess() - && clientAdj > PERCEPTIBLE_APP_ADJ) { - if (adj >= CACHED_APP_MIN_ADJ) { - adjType = "cch-bound-ui-services"; - } - } else { - int newAdj; - int lbAdj = VISIBLE_APP_ADJ; // lower bound of adj. - if (cr.hasFlag(Context.BIND_ABOVE_CLIENT - | Context.BIND_IMPORTANT)) { - if (clientAdj >= PERSISTENT_SERVICE_ADJ) { - newAdj = clientAdj; - } else { - // make this service persistent - newAdj = PERSISTENT_SERVICE_ADJ; - schedGroup = SCHED_GROUP_DEFAULT; - procState = ActivityManager.PROCESS_STATE_PERSISTENT; - cr.trackProcState(procState, mAdjSeq); - trackedProcState = true; - } - } else if (cr.hasFlag(Context.BIND_NOT_PERCEPTIBLE) - && clientAdj <= PERCEPTIBLE_APP_ADJ - && adj >= (lbAdj = PERCEPTIBLE_LOW_APP_ADJ)) { - newAdj = PERCEPTIBLE_LOW_APP_ADJ; - } else if (cr.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE) - && cr.notHasFlag(Context.BIND_NOT_FOREGROUND) - && clientAdj < PERCEPTIBLE_APP_ADJ - && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) { - // This is for user-initiated jobs. - // We use APP_ADJ + 1 here, so we can tell them apart from FGS. - newAdj = PERCEPTIBLE_APP_ADJ + 1; - } else if (cr.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE) - && cr.hasFlag(Context.BIND_NOT_FOREGROUND) - && clientAdj < PERCEPTIBLE_APP_ADJ - && adj >= (lbAdj = (PERCEPTIBLE_MEDIUM_APP_ADJ + 2))) { - // This is for expedited jobs. - // We use MEDIUM_APP_ADJ + 2 here, so we can tell apart - // EJ and short-FGS. - newAdj = PERCEPTIBLE_MEDIUM_APP_ADJ + 2; - } else if (cr.hasFlag(Context.BIND_NOT_VISIBLE) - && clientAdj < PERCEPTIBLE_APP_ADJ - && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) { - newAdj = PERCEPTIBLE_APP_ADJ; - } else if (clientAdj >= PERCEPTIBLE_APP_ADJ) { - newAdj = clientAdj; - } else if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE) - && clientAdj <= VISIBLE_APP_ADJ - && adj > VISIBLE_APP_ADJ) { - newAdj = VISIBLE_APP_ADJ; - } else { - if (adj > VISIBLE_APP_ADJ) { - // TODO: Is this too limiting for apps bound from TOP? - newAdj = Math.max(clientAdj, lbAdj); - } else { - newAdj = adj; - } - } - if (!cstate.isCached()) { - state.setCached(false); - } - if (adj > newAdj) { - adj = newAdj; - state.setCurRawAdj(adj); - adjType = "service"; - } - } - } - if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND - | Context.BIND_IMPORTANT_BACKGROUND)) { - // This will treat important bound services identically to - // the top app, which may behave differently than generic - // foreground work. - final int curSchedGroup = cstate.getCurrentSchedulingGroup(); - if (curSchedGroup > schedGroup) { - if (cr.hasFlag(Context.BIND_IMPORTANT)) { - schedGroup = curSchedGroup; - } else { - schedGroup = SCHED_GROUP_DEFAULT; - } - } - if (clientProcState < PROCESS_STATE_TOP) { - // Special handling for above-top states (persistent - // processes). These should not bring the current process - // into the top state, since they are not on top. Instead - // give them the best bound state after that. - if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE)) { - clientProcState = PROCESS_STATE_FOREGROUND_SERVICE; - } else if (cr.hasFlag(Context.BIND_FOREGROUND_SERVICE)) { - clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE; - } else if (mService.mWakefulness.get() - == PowerManagerInternal.WAKEFULNESS_AWAKE - && cr.hasFlag(Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE)) - { - clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE; - } else { - clientProcState = - PROCESS_STATE_IMPORTANT_FOREGROUND; - } - } else if (clientProcState == PROCESS_STATE_TOP) { - // Go at most to BOUND_TOP, unless requested to elevate - // to client's state. - clientProcState = PROCESS_STATE_BOUND_TOP; - final boolean enabled = cstate.getCachedCompatChange( - CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY); - if (enabled) { - if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) { - // TOP process passes all capabilities to the service. - capability |= cstate.getCurCapability(); - } else { - // TOP process passes no capability to the service. - } - } else { - // TOP process passes all capabilities to the service. - capability |= cstate.getCurCapability(); - } - } - } else if (cr.notHasFlag(Context.BIND_IMPORTANT_BACKGROUND)) { - if (clientProcState < - PROCESS_STATE_TRANSIENT_BACKGROUND) { - clientProcState = - PROCESS_STATE_TRANSIENT_BACKGROUND; - } - } else { - if (clientProcState < - PROCESS_STATE_IMPORTANT_BACKGROUND) { - clientProcState = - PROCESS_STATE_IMPORTANT_BACKGROUND; - } - } - - if (schedGroup < SCHED_GROUP_TOP_APP - && cr.hasFlag(Context.BIND_SCHEDULE_LIKE_TOP_APP) - && clientIsSystem) { - schedGroup = SCHED_GROUP_TOP_APP; - scheduleLikeTopApp = true; - } + computeServiceHostOomAdjLSP(cr, app, cr.binding.client, now, topApp, doingAll, + cycleReEval, computeClients, oomAdjReason, cachedAdj, true); - if (!trackedProcState) { - cr.trackProcState(clientProcState, mAdjSeq); - } - - if (procState > clientProcState) { - procState = clientProcState; - state.setCurRawProcState(procState); - if (adjType == null) { - adjType = "service"; - } - } - if (procState < PROCESS_STATE_IMPORTANT_BACKGROUND - && cr.hasFlag(Context.BIND_SHOWING_UI)) { - app.setPendingUiClean(true); - } - if (adjType != null) { - state.setAdjType(adjType); - state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo - .REASON_SERVICE_IN_USE); - state.setAdjSource(client); - state.setAdjSourceProcState(clientProcState); - state.setAdjTarget(s.instanceName); - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType - + ": " + app + ", due to " + client - + " adj=" + adj + " procState=" - + ProcessList.makeProcStateString(procState)); - } - } - } else { // BIND_WAIVE_PRIORITY == true - // BIND_WAIVE_PRIORITY bindings are special when it comes to the - // freezer. Processes bound via WPRI are expected to be running, - // but they are not promoted in the LRU list to keep them out of - // cached. As a result, they can freeze based on oom_adj alone. - // Normally, bindToDeath would fire when a cached app would die - // in the background, but nothing will fire when a running process - // pings a frozen process. Accordingly, any cached app that is - // bound by an unfrozen app via a WPRI binding has to remain - // unfrozen. - if (clientAdj < CACHED_APP_MIN_ADJ) { - app.mOptRecord.setShouldNotFreeze(true); - } - } - if (cr.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)) { - psr.setTreatLikeActivity(true); - } - final ActivityServiceConnectionsHolder a = cr.activity; - if (cr.hasFlag(Context.BIND_ADJUST_WITH_ACTIVITY)) { - if (a != null && adj > FOREGROUND_APP_ADJ - && a.isActivityVisible()) { - adj = FOREGROUND_APP_ADJ; - state.setCurRawAdj(adj); - if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND)) { - if (cr.hasFlag(Context.BIND_IMPORTANT)) { - schedGroup = SCHED_GROUP_TOP_APP_BOUND; - } else { - schedGroup = SCHED_GROUP_DEFAULT; - } - } - state.setCached(false); - state.setAdjType("service"); - state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo - .REASON_SERVICE_IN_USE); - state.setAdjSource(a); - state.setAdjSourceProcState(procState); - state.setAdjTarget(s.instanceName); - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, - "Raise to service w/activity: " + app); - } - } - } + adj = state.getCurRawAdj(); + procState = state.getCurRawProcState(); + schedGroup = state.getCurrentSchedulingGroup(); + capability = state.getCurCapability(); } } } @@ -2603,97 +2330,27 @@ public class OomAdjuster { || procState > PROCESS_STATE_TOP); provi--) { ContentProviderRecord cpr = ppr.getProviderAt(provi); - for (int i = cpr.connections.size() - 1; - i >= 0 && (adj > FOREGROUND_APP_ADJ - || schedGroup == SCHED_GROUP_BACKGROUND - || procState > PROCESS_STATE_TOP); - i--) { - ContentProviderConnection conn = cpr.connections.get(i); - ProcessRecord client = conn.client; - final ProcessStateRecord cstate = client.mState; - if (client == app) { - // Being our own client is not interesting. - continue; - } - if (computeClients) { - computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now, cycleReEval, true); - } else { - cstate.setCurRawAdj(cstate.getCurAdj()); - cstate.setCurRawProcState(cstate.getCurProcState()); - } - - if (shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) { - continue; - } - - int clientAdj = cstate.getCurRawAdj(); - int clientProcState = cstate.getCurRawProcState(); - - // We always propagate PROCESS_CAPABILITY_BFSL to providers here, - // but, right before actually setting it to the process, - // we check the final procstate, and remove it if the procsate is below BFGS. - capability |= getBfslCapabilityFromClient(client); - - if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { - // If the other app is cached for any reason, for purposes here - // we are going to consider it empty. - clientProcState = PROCESS_STATE_CACHED_EMPTY; - } - if (client.mOptRecord.shouldNotFreeze()) { - // Propagate the shouldNotFreeze flag down the bindings. - app.mOptRecord.setShouldNotFreeze(true); - } - - boundByNonBgRestricted |= cstate.isCurBoundByNonBgRestrictedApp() - || clientProcState <= PROCESS_STATE_BOUND_TOP - || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE - && !cstate.isBackgroundRestricted()); - - String adjType = null; - if (adj > clientAdj) { - if (state.hasShownUi() && !state.getCachedIsHomeProcess() - && clientAdj > PERCEPTIBLE_APP_ADJ) { - adjType = "cch-ui-provider"; - } else { - adj = Math.max(clientAdj, FOREGROUND_APP_ADJ); - state.setCurRawAdj(adj); - adjType = "provider"; - } - state.setCached(state.isCached() & cstate.isCached()); - } - - if (clientProcState <= PROCESS_STATE_FOREGROUND_SERVICE) { - if (adjType == null) { - adjType = "provider"; - } - if (clientProcState == PROCESS_STATE_TOP) { - clientProcState = PROCESS_STATE_BOUND_TOP; - } else { - clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE; - } - } + if (couldRecurse) { + // We're entering recursive functions below. + state.setCurRawAdj(adj); + state.setCurRawProcState(procState); + state.setCurrentSchedulingGroup(schedGroup); + state.setCurCapability(capability); + + for (int i = cpr.connections.size() - 1; + i >= 0 && (adj > FOREGROUND_APP_ADJ + || schedGroup == SCHED_GROUP_BACKGROUND + || procState > PROCESS_STATE_TOP); + i--) { + ContentProviderConnection conn = cpr.connections.get(i); + ProcessRecord client = conn.client; + computeProviderHostOomAdjLSP(conn, app, client, now, topApp, doingAll, + cycleReEval, computeClients, oomAdjReason, cachedAdj, true); - conn.trackProcState(clientProcState, mAdjSeq); - if (procState > clientProcState) { - procState = clientProcState; - state.setCurRawProcState(procState); - } - if (cstate.getCurrentSchedulingGroup() > schedGroup) { - schedGroup = SCHED_GROUP_DEFAULT; - } - if (adjType != null) { - state.setAdjType(adjType); - state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo - .REASON_PROVIDER_IN_USE); - state.setAdjSource(client); - state.setAdjSourceProcState(clientProcState); - state.setAdjTarget(cpr.name); - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType - + ": " + app + ", due to " + client - + " adj=" + adj + " procState=" - + ProcessList.makeProcStateString(procState)); - } + adj = state.getCurRawAdj(); + procState = state.getCurRawProcState(); + schedGroup = state.getCurrentSchedulingGroup(); + capability = state.getCurCapability(); } } // If the provider has external (non-framework) process @@ -2799,7 +2456,7 @@ public class OomAdjuster { // restrictions on screen off if (procState >= PROCESS_STATE_BOUND_FOREGROUND_SERVICE && mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE - && !scheduleLikeTopApp) { + && !state.shouldScheduleLikeTopApp()) { if (schedGroup > SCHED_GROUP_RESTRICTED) { schedGroup = SCHED_GROUP_RESTRICTED; } @@ -2817,7 +2474,6 @@ public class OomAdjuster { capability &= ~PROCESS_CAPABILITY_BFSL; } - if (app.isPendingFinishAttach()) { // If the app is still starting up. We reset the computations to the // hardcoded values in setAttachingProcessStatesLSP. This ensures that the app keeps @@ -2834,22 +2490,581 @@ public class OomAdjuster { // it when computing the final cached adj later. Note that we don't need to // worry about this for max adj above, since max adj will always be used to // keep it out of the cached vaues. - state.setCurAdj(adj); state.setCurCapability(capability); - state.setCurrentSchedulingGroup(schedGroup); - state.setCurProcState(procState); - state.setCurRawProcState(procState); state.updateLastInvisibleTime(hasVisibleActivities); state.setHasForegroundActivities(foregroundActivities); state.setCompletedAdjSeq(mAdjSeq); - state.setCurBoundByNonBgRestrictedApp(boundByNonBgRestricted); + + schedGroup = setIntermediateAdjLSP(app, adj, prevAppAdj, schedGroup); + setIntermediateProcStateLSP(app, procState, prevProcState); + setIntermediateSchedGroupLSP(state, schedGroup); // if curAdj or curProcState improved, then this process was promoted return state.getCurAdj() < prevAppAdj || state.getCurProcState() < prevProcState || state.getCurCapability() != prevCapability; } - private int getDefaultCapability(ProcessRecord app, int procState) { + /** + * @return The proposed change to the schedGroup. + */ + @GuardedBy({"mService", "mProcLock"}) + protected int setIntermediateAdjLSP(ProcessRecord app, int adj, int prevRawAppAdj, + int schedGroup) { + final ProcessStateRecord state = app.mState; + state.setCurRawAdj(adj); + + adj = app.mServices.modifyRawOomAdj(adj); + if (adj > state.getMaxAdj()) { + adj = state.getMaxAdj(); + if (adj <= PERCEPTIBLE_LOW_APP_ADJ) { + schedGroup = SCHED_GROUP_DEFAULT; + } + } + + state.setCurAdj(adj); + + return schedGroup; + } + + @GuardedBy({"mService", "mProcLock"}) + protected void setIntermediateProcStateLSP(ProcessRecord app, int procState, + int prevProcState) { + final ProcessStateRecord state = app.mState; + state.setCurProcState(procState); + state.setCurRawProcState(procState); + } + + @GuardedBy({"mService", "mProcLock"}) + protected void setIntermediateSchedGroupLSP(ProcessStateRecord state, int schedGroup) { + // Put bound foreground services in a special sched group for additional + // restrictions on screen off + if (state.getCurProcState() >= PROCESS_STATE_BOUND_FOREGROUND_SERVICE + && mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE + && !state.shouldScheduleLikeTopApp()) { + if (schedGroup > SCHED_GROUP_RESTRICTED) { + schedGroup = SCHED_GROUP_RESTRICTED; + } + } + + state.setCurrentSchedulingGroup(schedGroup); + } + + @GuardedBy({"mService", "mProcLock"}) + protected void computeServiceHostOomAdjLSP(ConnectionRecord cr, ProcessRecord app, + ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll, + boolean cycleReEval, boolean computeClients, int oomAdjReason, int cachedAdj, + boolean couldRecurse) { + if (app.isPendingFinishAttach()) { + // We've set the attaching process state in the computeInitialOomAdjLSP. Skip it here. + return; + } + + final ProcessStateRecord state = app.mState; + ProcessStateRecord cstate = client.mState; + + if (couldRecurse) { + if (app.isSdkSandbox && cr.binding.attributedClient != null) { + // For SDK sandboxes, use the attributed client (eg the app that + // requested the sandbox) + client = cr.binding.attributedClient; + cstate = client.mState; + } + if (computeClients) { + computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now, cycleReEval, true, + oomAdjReason, true); + } else { + cstate.setCurRawAdj(cstate.getCurAdj()); + cstate.setCurRawProcState(cstate.getCurProcState()); + } + } + + int clientAdj = cstate.getCurRawAdj(); + int clientProcState = cstate.getCurRawProcState(); + + final boolean clientIsSystem = clientProcState < PROCESS_STATE_TOP; + + int adj = state.getCurRawAdj(); + int procState = state.getCurRawProcState(); + int schedGroup = state.getCurrentSchedulingGroup(); + int capability = state.getCurCapability(); + + final int prevRawAdj = adj; + final int prevProcState = procState; + final int prevSchedGroup = schedGroup; + + final int appUid = app.info.uid; + final int logUid = mService.mCurOomAdjUid; + + state.setCurBoundByNonBgRestrictedApp(state.isCurBoundByNonBgRestrictedApp() + || cstate.isCurBoundByNonBgRestrictedApp() + || clientProcState <= PROCESS_STATE_BOUND_TOP + || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE + && !cstate.isBackgroundRestricted())); + + if (client.mOptRecord.shouldNotFreeze()) { + // Propagate the shouldNotFreeze flag down the bindings. + app.mOptRecord.setShouldNotFreeze(true); + } + + boolean trackedProcState = false; + + // We always propagate PROCESS_CAPABILITY_BFSL over bindings here, + // but, right before actually setting it to the process, + // we check the final procstate, and remove it if the procsate is below BFGS. + capability |= getBfslCapabilityFromClient(client); + + if (cr.notHasFlag(Context.BIND_WAIVE_PRIORITY)) { + if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) { + capability |= cstate.getCurCapability(); + } + + // If an app has network capability by default + // (by having procstate <= BFGS), then the apps it binds to will get + // elevated to a high enough procstate anyway to get network unless they + // request otherwise, so don't propagate the network capability by default + // in this case unless they explicitly request it. + if ((cstate.getCurCapability() + & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0) { + if (clientProcState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE) { + // This is used to grant network access to Expedited Jobs. + if (cr.hasFlag(Context.BIND_BYPASS_POWER_NETWORK_RESTRICTIONS)) { + capability |= PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK; + } + } else { + capability |= PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK; + } + } + if ((cstate.getCurCapability() + & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0) { + if (clientProcState <= PROCESS_STATE_IMPORTANT_FOREGROUND) { + // This is used to grant network access to User Initiated Jobs. + if (cr.hasFlag(Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS)) { + capability |= PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK; + } + } + } + + if (couldRecurse && shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) { + return; + } + + if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { + // If the other app is cached for any reason, for purposes here + // we are going to consider it empty. The specific cached state + // doesn't propagate except under certain conditions. + clientProcState = PROCESS_STATE_CACHED_EMPTY; + } + String adjType = null; + if (cr.hasFlag(Context.BIND_ALLOW_OOM_MANAGEMENT)) { + // Similar to BIND_WAIVE_PRIORITY, keep it unfrozen. + if (clientAdj < CACHED_APP_MIN_ADJ) { + app.mOptRecord.setShouldNotFreeze(true); + } + // Not doing bind OOM management, so treat + // this guy more like a started service. + if (state.hasShownUi() && !state.getCachedIsHomeProcess()) { + // If this process has shown some UI, let it immediately + // go to the LRU list because it may be pretty heavy with + // UI stuff. We'll tag it with a label just to help + // debug and understand what is going on. + if (adj > clientAdj) { + adjType = "cch-bound-ui-services"; + } + state.setCached(false); + clientAdj = adj; + clientProcState = procState; + } else { + if (now >= (cr.binding.service.lastActivity + + mConstants.MAX_SERVICE_INACTIVITY)) { + // This service has not seen activity within + // recent memory, so allow it to drop to the + // LRU list if there is no other reason to keep + // it around. We'll also tag it with a label just + // to help debug and undertand what is going on. + if (adj > clientAdj) { + adjType = "cch-bound-services"; + } + clientAdj = adj; + } + } + } + if (adj > clientAdj) { + // If this process has recently shown UI, and + // the process that is binding to it is less + // important than being visible, then we don't + // care about the binding as much as we care + // about letting this process get into the LRU + // list to be killed and restarted if needed for + // memory. + if (state.hasShownUi() && !state.getCachedIsHomeProcess() + && clientAdj > PERCEPTIBLE_APP_ADJ) { + if (adj >= CACHED_APP_MIN_ADJ) { + adjType = "cch-bound-ui-services"; + } + } else { + int newAdj; + int lbAdj = VISIBLE_APP_ADJ; // lower bound of adj. + if (cr.hasFlag(Context.BIND_ABOVE_CLIENT + | Context.BIND_IMPORTANT)) { + if (clientAdj >= PERSISTENT_SERVICE_ADJ) { + newAdj = clientAdj; + } else { + // make this service persistent + newAdj = PERSISTENT_SERVICE_ADJ; + schedGroup = SCHED_GROUP_DEFAULT; + procState = ActivityManager.PROCESS_STATE_PERSISTENT; + cr.trackProcState(procState, mAdjSeq); + trackedProcState = true; + } + } else if (cr.hasFlag(Context.BIND_NOT_PERCEPTIBLE) + && clientAdj <= PERCEPTIBLE_APP_ADJ + && adj >= (lbAdj = PERCEPTIBLE_LOW_APP_ADJ)) { + newAdj = PERCEPTIBLE_LOW_APP_ADJ; + } else if (cr.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE) + && cr.notHasFlag(Context.BIND_NOT_FOREGROUND) + && clientAdj < PERCEPTIBLE_APP_ADJ + && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) { + // This is for user-initiated jobs. + // We use APP_ADJ + 1 here, so we can tell them apart from FGS. + newAdj = PERCEPTIBLE_APP_ADJ + 1; + } else if (cr.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE) + && cr.hasFlag(Context.BIND_NOT_FOREGROUND) + && clientAdj < PERCEPTIBLE_APP_ADJ + && adj >= (lbAdj = (PERCEPTIBLE_MEDIUM_APP_ADJ + 2))) { + // This is for expedited jobs. + // We use MEDIUM_APP_ADJ + 2 here, so we can tell apart + // EJ and short-FGS. + newAdj = PERCEPTIBLE_MEDIUM_APP_ADJ + 2; + } else if (cr.hasFlag(Context.BIND_NOT_VISIBLE) + && clientAdj < PERCEPTIBLE_APP_ADJ + && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) { + newAdj = PERCEPTIBLE_APP_ADJ; + } else if (clientAdj >= PERCEPTIBLE_APP_ADJ) { + newAdj = clientAdj; + } else if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE) + && clientAdj <= VISIBLE_APP_ADJ + && adj > VISIBLE_APP_ADJ) { + newAdj = VISIBLE_APP_ADJ; + } else { + if (adj > VISIBLE_APP_ADJ) { + // TODO: Is this too limiting for apps bound from TOP? + newAdj = Math.max(clientAdj, lbAdj); + } else { + newAdj = adj; + } + } + if (!cstate.isCached()) { + state.setCached(false); + } + if (adj > newAdj) { + adj = newAdj; + state.setCurRawAdj(adj); + adjType = "service"; + } + } + } + if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND + | Context.BIND_IMPORTANT_BACKGROUND)) { + // This will treat important bound services identically to + // the top app, which may behave differently than generic + // foreground work. + final int curSchedGroup = cstate.getCurrentSchedulingGroup(); + if (curSchedGroup > schedGroup) { + if (cr.hasFlag(Context.BIND_IMPORTANT)) { + schedGroup = curSchedGroup; + } else { + schedGroup = SCHED_GROUP_DEFAULT; + } + } + if (clientProcState < PROCESS_STATE_TOP) { + // Special handling for above-top states (persistent + // processes). These should not bring the current process + // into the top state, since they are not on top. Instead + // give them the best bound state after that. + if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE)) { + clientProcState = PROCESS_STATE_FOREGROUND_SERVICE; + } else if (cr.hasFlag(Context.BIND_FOREGROUND_SERVICE)) { + clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE; + } else if (mService.mWakefulness.get() + == PowerManagerInternal.WAKEFULNESS_AWAKE + && cr.hasFlag(Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE)) { + clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE; + } else { + clientProcState = + PROCESS_STATE_IMPORTANT_FOREGROUND; + } + } else if (clientProcState == PROCESS_STATE_TOP) { + // Go at most to BOUND_TOP, unless requested to elevate + // to client's state. + clientProcState = PROCESS_STATE_BOUND_TOP; + final boolean enabled = cstate.getCachedCompatChange( + CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY); + if (enabled) { + if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) { + // TOP process passes all capabilities to the service. + capability |= cstate.getCurCapability(); + } else { + // TOP process passes no capability to the service. + } + } else { + // TOP process passes all capabilities to the service. + capability |= cstate.getCurCapability(); + } + } + } else if (cr.notHasFlag(Context.BIND_IMPORTANT_BACKGROUND)) { + if (clientProcState < PROCESS_STATE_TRANSIENT_BACKGROUND) { + clientProcState = + PROCESS_STATE_TRANSIENT_BACKGROUND; + } + } else { + if (clientProcState < PROCESS_STATE_IMPORTANT_BACKGROUND) { + clientProcState = + PROCESS_STATE_IMPORTANT_BACKGROUND; + } + } + + if (schedGroup < SCHED_GROUP_TOP_APP + && cr.hasFlag(Context.BIND_SCHEDULE_LIKE_TOP_APP) + && clientIsSystem) { + schedGroup = SCHED_GROUP_TOP_APP; + state.setScheduleLikeTopApp(true); + } + + if (!trackedProcState) { + cr.trackProcState(clientProcState, mAdjSeq); + } + + if (procState > clientProcState) { + procState = clientProcState; + state.setCurRawProcState(procState); + if (adjType == null) { + adjType = "service"; + } + } + if (procState < PROCESS_STATE_IMPORTANT_BACKGROUND + && cr.hasFlag(Context.BIND_SHOWING_UI)) { + app.setPendingUiClean(true); + } + if (adjType != null) { + state.setAdjType(adjType); + state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo + .REASON_SERVICE_IN_USE); + state.setAdjSource(client); + state.setAdjSourceProcState(clientProcState); + state.setAdjTarget(cr.binding.service.instanceName); + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType + + ": " + app + ", due to " + client + + " adj=" + adj + " procState=" + + ProcessList.makeProcStateString(procState)); + } + } + } else { // BIND_WAIVE_PRIORITY == true + // BIND_WAIVE_PRIORITY bindings are special when it comes to the + // freezer. Processes bound via WPRI are expected to be running, + // but they are not promoted in the LRU list to keep them out of + // cached. As a result, they can freeze based on oom_adj alone. + // Normally, bindToDeath would fire when a cached app would die + // in the background, but nothing will fire when a running process + // pings a frozen process. Accordingly, any cached app that is + // bound by an unfrozen app via a WPRI binding has to remain + // unfrozen. + if (clientAdj < CACHED_APP_MIN_ADJ) { + app.mOptRecord.setShouldNotFreeze(true); + } + } + if (cr.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)) { + app.mServices.setTreatLikeActivity(true); + if (clientProcState <= PROCESS_STATE_CACHED_ACTIVITY + && procState > PROCESS_STATE_CACHED_ACTIVITY) { + // This is a cached process, but somebody wants us to treat it like it has + // an activity, okay! + procState = PROCESS_STATE_CACHED_ACTIVITY; + state.setAdjType("cch-as-act"); + } + } + final ActivityServiceConnectionsHolder a = cr.activity; + if (cr.hasFlag(Context.BIND_ADJUST_WITH_ACTIVITY)) { + if (a != null && adj > FOREGROUND_APP_ADJ + && a.isActivityVisible()) { + adj = FOREGROUND_APP_ADJ; + state.setCurRawAdj(adj); + if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND)) { + if (cr.hasFlag(Context.BIND_IMPORTANT)) { + schedGroup = SCHED_GROUP_TOP_APP_BOUND; + } else { + schedGroup = SCHED_GROUP_DEFAULT; + } + } + state.setCached(false); + state.setAdjType("service"); + state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo + .REASON_SERVICE_IN_USE); + state.setAdjSource(a); + state.setAdjSourceProcState(procState); + state.setAdjTarget(cr.binding.service.instanceName); + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, + "Raise to service w/activity: " + app); + } + } + } + + capability |= getDefaultCapability(app, procState); + + // Procstates below BFGS should never have this capability. + if (procState > PROCESS_STATE_BOUND_FOREGROUND_SERVICE) { + capability &= ~PROCESS_CAPABILITY_BFSL; + } + + if (adj < prevRawAdj) { + schedGroup = setIntermediateAdjLSP(app, adj, prevRawAdj, schedGroup); + } + if (procState < prevProcState) { + setIntermediateProcStateLSP(app, procState, prevProcState); + } + if (schedGroup > prevSchedGroup) { + setIntermediateSchedGroupLSP(state, schedGroup); + } + state.setCurCapability(capability); + + state.setEmpty(false); + } + + protected void computeProviderHostOomAdjLSP(ContentProviderConnection conn, ProcessRecord app, + ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll, + boolean cycleReEval, boolean computeClients, int oomAdjReason, int cachedAdj, + boolean couldRecurse) { + if (app.isPendingFinishAttach()) { + // We've set the attaching process state in the computeInitialOomAdjLSP. Skip it here. + return; + } + + final ProcessStateRecord state = app.mState; + final ProcessStateRecord cstate = client.mState; + + if (client == app) { + // Being our own client is not interesting. + return; + } + if (couldRecurse) { + if (computeClients) { + computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now, cycleReEval, true, + oomAdjReason, true); + } else if (couldRecurse) { + cstate.setCurRawAdj(cstate.getCurAdj()); + cstate.setCurRawProcState(cstate.getCurProcState()); + } + + if (shouldSkipDueToCycle(app, cstate, state.getCurRawProcState(), state.getCurRawAdj(), + cycleReEval)) { + return; + } + } + + int clientAdj = cstate.getCurRawAdj(); + int clientProcState = cstate.getCurRawProcState(); + + int adj = state.getCurRawAdj(); + int procState = state.getCurRawProcState(); + int schedGroup = state.getCurrentSchedulingGroup(); + int capability = state.getCurCapability(); + + final int prevRawAdj = adj; + final int prevProcState = procState; + final int prevSchedGroup = schedGroup; + + final int appUid = app.info.uid; + final int logUid = mService.mCurOomAdjUid; + + // We always propagate PROCESS_CAPABILITY_BFSL to providers here, + // but, right before actually setting it to the process, + // we check the final procstate, and remove it if the procsate is below BFGS. + capability |= getBfslCapabilityFromClient(client); + + if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { + // If the other app is cached for any reason, for purposes here + // we are going to consider it empty. + clientProcState = PROCESS_STATE_CACHED_EMPTY; + } + if (client.mOptRecord.shouldNotFreeze()) { + // Propagate the shouldNotFreeze flag down the bindings. + app.mOptRecord.setShouldNotFreeze(true); + } + + state.setCurBoundByNonBgRestrictedApp(state.isCurBoundByNonBgRestrictedApp() + || cstate.isCurBoundByNonBgRestrictedApp() + || clientProcState <= PROCESS_STATE_BOUND_TOP + || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE + && !cstate.isBackgroundRestricted())); + + String adjType = null; + if (adj > clientAdj) { + if (state.hasShownUi() && !state.getCachedIsHomeProcess() + && clientAdj > PERCEPTIBLE_APP_ADJ) { + adjType = "cch-ui-provider"; + } else { + adj = Math.max(clientAdj, FOREGROUND_APP_ADJ); + state.setCurRawAdj(adj); + adjType = "provider"; + } + state.setCached(state.isCached() & cstate.isCached()); + } + + if (clientProcState <= PROCESS_STATE_FOREGROUND_SERVICE) { + if (adjType == null) { + adjType = "provider"; + } + if (clientProcState == PROCESS_STATE_TOP) { + clientProcState = PROCESS_STATE_BOUND_TOP; + } else { + clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE; + } + } + + conn.trackProcState(clientProcState, mAdjSeq); + if (procState > clientProcState) { + procState = clientProcState; + state.setCurRawProcState(procState); + } + if (cstate.getCurrentSchedulingGroup() > schedGroup) { + schedGroup = SCHED_GROUP_DEFAULT; + } + if (adjType != null) { + state.setAdjType(adjType); + state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo + .REASON_PROVIDER_IN_USE); + state.setAdjSource(client); + state.setAdjSourceProcState(clientProcState); + state.setAdjTarget(conn.provider.name); + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType + + ": " + app + ", due to " + client + + " adj=" + adj + " procState=" + + ProcessList.makeProcStateString(procState)); + } + } + + // Procstates below BFGS should never have this capability. + if (procState > PROCESS_STATE_BOUND_FOREGROUND_SERVICE) { + capability &= ~PROCESS_CAPABILITY_BFSL; + } + + if (adj < prevRawAdj) { + schedGroup = setIntermediateAdjLSP(app, adj, prevRawAdj, schedGroup); + } + if (procState < prevProcState) { + setIntermediateProcStateLSP(app, procState, prevProcState); + } + if (schedGroup > prevSchedGroup) { + setIntermediateSchedGroupLSP(state, schedGroup); + } + state.setCurCapability(capability); + + state.setEmpty(false); + } + + protected int getDefaultCapability(ProcessRecord app, int procState) { final int networkCapabilities = NetworkPolicyManager.getDefaultProcessNetworkCapabilities(procState); final int baseCapabilities; @@ -2882,7 +3097,7 @@ public class OomAdjuster { /** * @return the BFSL capability from a client (of a service binding or provider). */ - int getBfslCapabilityFromClient(ProcessRecord client) { + protected int getBfslCapabilityFromClient(ProcessRecord client) { // Procstates above FGS should always have this flag. We shouldn't need this logic, // but let's do it just in case. if (client.mState.getCurProcState() < PROCESS_STATE_FOREGROUND_SERVICE) { @@ -2967,7 +3182,7 @@ public class OomAdjuster { /** Inform the oomadj observer of changes to oomadj. Used by tests. */ @GuardedBy("mService") - private void reportOomAdjMessageLocked(String tag, String msg) { + protected void reportOomAdjMessageLocked(String tag, String msg) { Slog.d(tag, msg); synchronized (mService.mOomAdjObserverLock) { if (mService.mCurOomAdjObserver != null) { @@ -2983,7 +3198,7 @@ public class OomAdjuster { /** Applies the computed oomadj, procstate and sched group values and freezes them in set* */ @GuardedBy({"mService", "mProcLock"}) - private boolean applyOomAdjLSP(ProcessRecord app, boolean doingAll, long now, + protected boolean applyOomAdjLSP(ProcessRecord app, boolean doingAll, long now, long nowElapsed, @OomAdjReason int oomAdjReson) { boolean success = true; final ProcessStateRecord state = app.mState; @@ -3272,6 +3487,8 @@ public class OomAdjuster { int initialCapability = PROCESS_CAPABILITY_NONE; boolean initialCached = true; final ProcessStateRecord state = app.mState; + final int prevProcState = PROCESS_STATE_UNKNOWN; + final int prevAdj = UNKNOWN_ADJ; // If the process has been marked as foreground, it is starting as the top app (with // Zygote#START_AS_TOP_APP_ARG), so boost the thread priority of its default UI thread. if (state.hasForegroundActivities()) { @@ -3306,6 +3523,9 @@ public class OomAdjuster { state.setCurRawAdj(ProcessList.FOREGROUND_APP_ADJ); state.setForcingToImportant(null); state.setHasShownUi(false); + + onProcessStateChanged(app, prevProcState); + onProcessOomAdjChanged(app, prevAdj); } // ONLY used for unit testing in OomAdjusterTests.java @@ -3553,4 +3773,56 @@ public class OomAdjuster { } processes.clear(); } + + @GuardedBy("mService") + void onProcessBeginLocked(@NonNull ProcessRecord app) { + // Empty, the OomAdjusterModernImpl will have an implementation. + } + + @GuardedBy("mService") + void onProcessEndLocked(@NonNull ProcessRecord app) { + // Empty, the OomAdjusterModernImpl will have an implementation. + } + + /** + * Called when the process state is changed outside of the OomAdjuster. + */ + @GuardedBy("mService") + void onProcessStateChanged(@NonNull ProcessRecord app, int prevProcState) { + // Empty, the OomAdjusterModernImpl will have an implementation. + } + + /** + * Called when the oom adj is changed outside of the OomAdjuster. + */ + @GuardedBy("mService") + void onProcessOomAdjChanged(@NonNull ProcessRecord app, int prevAdj) { + // Empty, the OomAdjusterModernImpl will have an implementation. + } + + @VisibleForTesting + void resetInternal() { + // Empty, the OomAdjusterModernImpl will have an implementation. + } + + @GuardedBy("mService") + protected int getInitialAdj(@NonNull ProcessRecord app) { + return app.mState.getCurAdj(); + } + + @GuardedBy("mService") + protected int getInitialProcState(@NonNull ProcessRecord app) { + return app.mState.getCurProcState(); + } + + @GuardedBy("mService") + protected int getInitialCapability(@NonNull ProcessRecord app) { + return app.mState.getCurCapability(); + } + + @GuardedBy("mService") + protected boolean getInitialIsCurBoundByNonBgRestrictedApp(@NonNull ProcessRecord app) { + // The caller will set the initial value in this implementation. + return app.mState.isCurBoundByNonBgRestrictedApp(); + } } diff --git a/services/core/java/com/android/server/am/OomAdjuster.md b/services/core/java/com/android/server/am/OomAdjuster.md index 16091d1c162d..da5e12ef21fa 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.md +++ b/services/core/java/com/android/server/am/OomAdjuster.md @@ -130,3 +130,28 @@ The Oom Adjuster maintains a global sequence ID `mAdjSeq` to track the current O * Iterate the processes from least important to most important ones. * A maximum retries of 10 is enforced, while in practice, the maximum retries could reach only 2 to 3. +## The Modern Implementation + +As aforementioned, the OomAdjuster makes the computation in a recursive way, while this is inefficient in dealing with the cycles. The overall code complexity should be around **O((1 + num(retries)) * num(procs) * num(binding connections))**. In addition, depending on the ordering of the input, the algorithm may produce different results and sometimes it's wrong. + +The new "Modern Implementation" is based on the rationale that, apps can't promote the service/provider it connects to, to a higher bucket than itself. We are introducing a bucket based, breadth first search algorithm, as illustrated below: + +``` +for all processes in the process list + compute the state of each process, but, excluding its clients + put each process to the corresponding bucket according to the state value +done + +for each bucket, starting from the top most to the bottom most + for each process in the bucket + for each process it binds to + if the state of the bindee process could be elevated because of the binding; then + move the bindee process to the higher bucket + fi + done + done +done +``` + +The overall code complexity should be around **O(num(procs) * num(binding connections))**, which saves the retry time from the existing algorithm. + diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java new file mode 100644 index 000000000000..b852ef56fceb --- /dev/null +++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java @@ -0,0 +1,1125 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static android.app.ActivityManager.PROCESS_STATE_BACKUP; +import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; +import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP; +import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; +import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT; +import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY; +import static android.app.ActivityManager.PROCESS_STATE_CACHED_RECENT; +import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; +import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT; +import static android.app.ActivityManager.PROCESS_STATE_HOME; +import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND; +import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; +import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY; +import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT; +import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI; +import static android.app.ActivityManager.PROCESS_STATE_RECEIVER; +import static android.app.ActivityManager.PROCESS_STATE_SERVICE; +import static android.app.ActivityManager.PROCESS_STATE_TOP; +import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING; +import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND; +import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN; + +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS; +import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS; +import static com.android.server.am.ProcessList.BACKUP_APP_ADJ; +import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ; +import static com.android.server.am.ProcessList.FOREGROUND_APP_ADJ; +import static com.android.server.am.ProcessList.HEAVY_WEIGHT_APP_ADJ; +import static com.android.server.am.ProcessList.HOME_APP_ADJ; +import static com.android.server.am.ProcessList.NATIVE_ADJ; +import static com.android.server.am.ProcessList.PERCEPTIBLE_APP_ADJ; +import static com.android.server.am.ProcessList.PERCEPTIBLE_LOW_APP_ADJ; +import static com.android.server.am.ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ; +import static com.android.server.am.ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ; +import static com.android.server.am.ProcessList.PERSISTENT_PROC_ADJ; +import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ; +import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ; +import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND; +import static com.android.server.am.ProcessList.SERVICE_ADJ; +import static com.android.server.am.ProcessList.SERVICE_B_ADJ; +import static com.android.server.am.ProcessList.SYSTEM_ADJ; +import static com.android.server.am.ProcessList.UNKNOWN_ADJ; +import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.ActivityManagerInternal.OomAdjReason; +import android.content.pm.ServiceInfo; +import android.os.IBinder; +import android.os.SystemClock; +import android.os.Trace; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.ServiceThread; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.function.Consumer; + +/** + * A modern implementation of the oom adjuster. + */ +public class OomAdjusterModernImpl extends OomAdjuster { + static final String TAG = "OomAdjusterModernImpl"; + + // The ADJ_SLOT_INVALID is NOT an actual slot. + static final int ADJ_SLOT_INVALID = -1; + static final int ADJ_SLOT_NATIVE = 0; + static final int ADJ_SLOT_SYSTEM = 1; + static final int ADJ_SLOT_PERSISTENT_PROC = 2; + static final int ADJ_SLOT_PERSISTENT_SERVICE = 3; + static final int ADJ_SLOT_FOREGROUND_APP = 4; + static final int ADJ_SLOT_PERCEPTIBLE_RECENT_FOREGROUND_APP = 5; + static final int ADJ_SLOT_VISIBLE_APP = 6; + static final int ADJ_SLOT_PERCEPTIBLE_APP = 7; + static final int ADJ_SLOT_PERCEPTIBLE_MEDIUM_APP = 8; + static final int ADJ_SLOT_PERCEPTIBLE_LOW_APP = 9; + static final int ADJ_SLOT_BACKUP_APP = 10; + static final int ADJ_SLOT_HEAVY_WEIGHT_APP = 11; + static final int ADJ_SLOT_SERVICE = 12; + static final int ADJ_SLOT_HOME_APP = 13; + static final int ADJ_SLOT_PREVIOUS_APP = 14; + static final int ADJ_SLOT_SERVICE_B = 15; + static final int ADJ_SLOT_CACHED_APP = 16; + static final int ADJ_SLOT_UNKNOWN = 17; + + @IntDef(prefix = { "ADJ_SLOT_" }, value = { + ADJ_SLOT_INVALID, + ADJ_SLOT_NATIVE, + ADJ_SLOT_SYSTEM, + ADJ_SLOT_PERSISTENT_PROC, + ADJ_SLOT_PERSISTENT_SERVICE, + ADJ_SLOT_FOREGROUND_APP, + ADJ_SLOT_PERCEPTIBLE_RECENT_FOREGROUND_APP, + ADJ_SLOT_VISIBLE_APP, + ADJ_SLOT_PERCEPTIBLE_APP, + ADJ_SLOT_PERCEPTIBLE_MEDIUM_APP, + ADJ_SLOT_PERCEPTIBLE_LOW_APP, + ADJ_SLOT_BACKUP_APP, + ADJ_SLOT_HEAVY_WEIGHT_APP, + ADJ_SLOT_SERVICE, + ADJ_SLOT_HOME_APP, + ADJ_SLOT_PREVIOUS_APP, + ADJ_SLOT_SERVICE_B, + ADJ_SLOT_CACHED_APP, + ADJ_SLOT_UNKNOWN, + }) + @Retention(RetentionPolicy.SOURCE) + @interface AdjSlot{} + + static final int[] ADJ_SLOT_VALUES = new int[] { + NATIVE_ADJ, + SYSTEM_ADJ, + PERSISTENT_PROC_ADJ, + PERSISTENT_SERVICE_ADJ, + FOREGROUND_APP_ADJ, + PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ, + VISIBLE_APP_ADJ, + PERCEPTIBLE_APP_ADJ, + PERCEPTIBLE_MEDIUM_APP_ADJ, + PERCEPTIBLE_LOW_APP_ADJ, + BACKUP_APP_ADJ, + HEAVY_WEIGHT_APP_ADJ, + SERVICE_ADJ, + HOME_APP_ADJ, + PREVIOUS_APP_ADJ, + SERVICE_B_ADJ, + CACHED_APP_MIN_ADJ, + UNKNOWN_ADJ, + }; + + /** + * Note: Always use the raw adj to call this API. + */ + static @AdjSlot int adjToSlot(int adj) { + if (adj >= ADJ_SLOT_VALUES[0] && adj <= ADJ_SLOT_VALUES[ADJ_SLOT_VALUES.length - 1]) { + // Conduct a binary search, in most of the cases it'll get a hit. + final int index = Arrays.binarySearch(ADJ_SLOT_VALUES, adj); + if (index >= 0) { + return index; + } + // If not found, the returned index above should be (-(insertion point) - 1), + // let's return the first slot that's less than the adj value. + return -(index + 1) - 1; + } + return ADJ_SLOT_VALUES.length - 1; + } + + static final int[] PROC_STATE_SLOTS = new int[] { + PROCESS_STATE_PERSISTENT, // 0 + PROCESS_STATE_PERSISTENT_UI, + PROCESS_STATE_TOP, + PROCESS_STATE_BOUND_TOP, + PROCESS_STATE_FOREGROUND_SERVICE, + PROCESS_STATE_BOUND_FOREGROUND_SERVICE, + PROCESS_STATE_IMPORTANT_FOREGROUND, + PROCESS_STATE_IMPORTANT_BACKGROUND, + PROCESS_STATE_TRANSIENT_BACKGROUND, + PROCESS_STATE_BACKUP, + PROCESS_STATE_SERVICE, + PROCESS_STATE_RECEIVER, + PROCESS_STATE_TOP_SLEEPING, + PROCESS_STATE_HEAVY_WEIGHT, + PROCESS_STATE_HOME, + PROCESS_STATE_LAST_ACTIVITY, + PROCESS_STATE_CACHED_ACTIVITY, + PROCESS_STATE_CACHED_ACTIVITY_CLIENT, + PROCESS_STATE_CACHED_RECENT, + PROCESS_STATE_CACHED_EMPTY, + PROCESS_STATE_UNKNOWN, // -1 + }; + + static int processStateToSlot(@ActivityManager.ProcessState int state) { + if (state >= PROCESS_STATE_PERSISTENT && state <= PROCESS_STATE_CACHED_EMPTY) { + return state; + } + return PROC_STATE_SLOTS.length - 1; + } + + /** + * A container node in the {@link LinkedProcessRecordList}, + * holding the references to {@link ProcessRecord}. + */ + static class ProcessRecordNode { + static final int NODE_TYPE_PROC_STATE = 0; + static final int NODE_TYPE_ADJ = 1; + + @IntDef(prefix = { "NODE_TYPE_" }, value = { + NODE_TYPE_PROC_STATE, + NODE_TYPE_ADJ, + }) + @Retention(RetentionPolicy.SOURCE) + @interface NodeType {} + + static final int NUM_NODE_TYPE = NODE_TYPE_ADJ + 1; + + @Nullable ProcessRecordNode mPrev; + @Nullable ProcessRecordNode mNext; + final @Nullable ProcessRecord mApp; + + ProcessRecordNode(@Nullable ProcessRecord app) { + mApp = app; + } + + void unlink() { + if (mPrev != null) { + mPrev.mNext = mNext; + } + if (mNext != null) { + mNext.mPrev = mPrev; + } + mPrev = mNext = null; + } + + boolean isLinked() { + return mPrev != null && mNext != null; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("ProcessRecordNode{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(' '); + sb.append(mApp); + sb.append(' '); + sb.append(mApp != null ? mApp.mState.getCurProcState() : PROCESS_STATE_UNKNOWN); + sb.append(' '); + sb.append(mApp != null ? mApp.mState.getCurAdj() : UNKNOWN_ADJ); + sb.append(' '); + sb.append(Integer.toHexString(System.identityHashCode(mPrev))); + sb.append(' '); + sb.append(Integer.toHexString(System.identityHashCode(mNext))); + sb.append('}'); + return sb.toString(); + } + } + + private class ProcessRecordNodes { + private final @ProcessRecordNode.NodeType int mType; + + private final LinkedProcessRecordList[] mProcessRecordNodes; + // The last node besides the tail. + private final ProcessRecordNode[] mLastNode; + + ProcessRecordNodes(@ProcessRecordNode.NodeType int type, int size) { + mType = type; + mProcessRecordNodes = new LinkedProcessRecordList[size]; + for (int i = 0; i < size; i++) { + mProcessRecordNodes[i] = new LinkedProcessRecordList(type); + } + mLastNode = new ProcessRecordNode[size]; + } + + int size() { + return mProcessRecordNodes.length; + } + + @VisibleForTesting + void reset() { + for (int i = 0; i < mProcessRecordNodes.length; i++) { + mProcessRecordNodes[i].reset(); + mLastNode[i] = null; + } + } + + void resetLastNodes() { + for (int i = 0; i < mProcessRecordNodes.length; i++) { + mLastNode[i] = mProcessRecordNodes[i].getLastNodeBeforeTail(); + } + } + + void setLastNodeToHead(int slot) { + mLastNode[slot] = mProcessRecordNodes[slot].HEAD; + } + + void forEachNewNode(int slot, @NonNull Consumer<OomAdjusterArgs> callback) { + ProcessRecordNode node = mLastNode[slot].mNext; + final ProcessRecordNode tail = mProcessRecordNodes[slot].TAIL; + while (node != tail) { + mTmpOomAdjusterArgs.mApp = node.mApp; + // Save the next before calling callback, since that may change the node.mNext. + final ProcessRecordNode next = node.mNext; + callback.accept(mTmpOomAdjusterArgs); + // There are couple of cases: + // a) The current node is moved to another slot + // - for this case, we'd need to keep using the "next" node. + // b) There are one or more new nodes being appended to this slot + // - for this case, we'd need to make sure we scan the new node too. + // Based on the assumption that case a) is only possible with + // the computeInitialOomAdjLSP(), where the movings are for single node only, + // we may safely assume that, if the "next" used to be the "tail" here, and it's + // now a new tail somewhere else, that's case a); otherwise, it's case b); + node = next == tail && node.mNext != null && node.mNext.mNext != null + ? node.mNext : next; + } + } + + int getNumberOfSlots() { + return mProcessRecordNodes.length; + } + + void moveAppTo(@NonNull ProcessRecord app, int prevSlot, int newSlot) { + final ProcessRecordNode node = app.mLinkedNodes[mType]; + if (prevSlot != ADJ_SLOT_INVALID) { + if (mLastNode[prevSlot] == node) { + mLastNode[prevSlot] = node.mPrev; + } + node.unlink(); + } + mProcessRecordNodes[newSlot].append(node); + } + + void moveAllNodesTo(int fromSlot, int toSlot) { + final LinkedProcessRecordList fromList = mProcessRecordNodes[fromSlot]; + final LinkedProcessRecordList toList = mProcessRecordNodes[toSlot]; + if (fromSlot != toSlot && fromList.HEAD.mNext != fromList.TAIL) { + fromList.moveTo(toList); + mLastNode[fromSlot] = fromList.getLastNodeBeforeTail(); + } + } + + void moveAppToTail(ProcessRecord app) { + final ProcessRecordNode node = app.mLinkedNodes[mType]; + int slot; + switch (mType) { + case ProcessRecordNode.NODE_TYPE_PROC_STATE: + slot = processStateToSlot(app.mState.getCurProcState()); + if (mLastNode[slot] == node) { + mLastNode[slot] = node.mPrev; + } + mProcessRecordNodes[slot].moveNodeToTail(node); + break; + case ProcessRecordNode.NODE_TYPE_ADJ: + slot = adjToSlot(app.mState.getCurRawAdj()); + if (mLastNode[slot] == node) { + mLastNode[slot] = node.mPrev; + } + mProcessRecordNodes[slot].moveNodeToTail(node); + break; + default: + return; + } + + } + + void reset(int slot) { + mProcessRecordNodes[slot].reset(); + } + + void unlink(@NonNull ProcessRecord app) { + final ProcessRecordNode node = app.mLinkedNodes[mType]; + final int slot = getCurrentSlot(app); + if (slot != ADJ_SLOT_INVALID) { + if (mLastNode[slot] == node) { + mLastNode[slot] = node.mPrev; + } + } + node.unlink(); + } + + void append(@NonNull ProcessRecord app) { + append(app, getCurrentSlot(app)); + } + + void append(@NonNull ProcessRecord app, int targetSlot) { + final ProcessRecordNode node = app.mLinkedNodes[mType]; + mProcessRecordNodes[targetSlot].append(node); + } + + private int getCurrentSlot(@NonNull ProcessRecord app) { + switch (mType) { + case ProcessRecordNode.NODE_TYPE_PROC_STATE: + return processStateToSlot(app.mState.getCurProcState()); + case ProcessRecordNode.NODE_TYPE_ADJ: + return adjToSlot(app.mState.getCurRawAdj()); + } + return ADJ_SLOT_INVALID; + } + + String toString(int slot, int logUid) { + return "lastNode=" + mLastNode[slot] + " " + mProcessRecordNodes[slot].toString(logUid); + } + + /** + * A simple version of {@link java.util.LinkedList}, as here we don't allocate new node + * while adding an object to it. + */ + private static class LinkedProcessRecordList { + // Sentinel head/tail, to make bookkeeping work easier. + final ProcessRecordNode HEAD = new ProcessRecordNode(null); + final ProcessRecordNode TAIL = new ProcessRecordNode(null); + final @ProcessRecordNode.NodeType int mNodeType; + + LinkedProcessRecordList(@ProcessRecordNode.NodeType int nodeType) { + HEAD.mNext = TAIL; + TAIL.mPrev = HEAD; + mNodeType = nodeType; + } + + void append(@NonNull ProcessRecordNode node) { + node.mNext = TAIL; + node.mPrev = TAIL.mPrev; + TAIL.mPrev.mNext = node; + TAIL.mPrev = node; + } + + void moveTo(@NonNull LinkedProcessRecordList toList) { + if (HEAD.mNext != TAIL) { + toList.TAIL.mPrev.mNext = HEAD.mNext; + HEAD.mNext.mPrev = toList.TAIL.mPrev; + toList.TAIL.mPrev = TAIL.mPrev; + TAIL.mPrev.mNext = toList.TAIL; + HEAD.mNext = TAIL; + TAIL.mPrev = HEAD; + } + } + + void moveNodeToTail(@NonNull ProcessRecordNode node) { + node.unlink(); + append(node); + } + + @NonNull ProcessRecordNode getLastNodeBeforeTail() { + return TAIL.mPrev; + } + + @VisibleForTesting + void reset() { + HEAD.mNext = TAIL; + TAIL.mPrev = HEAD; + } + + String toString(int logUid) { + final StringBuilder sb = new StringBuilder(); + sb.append("LinkedProcessRecordList{"); + sb.append(HEAD); + sb.append(' '); + sb.append(TAIL); + sb.append('['); + ProcessRecordNode node = HEAD.mNext; + while (node != TAIL) { + if (node.mApp != null && node.mApp.uid == logUid) { + sb.append(node); + sb.append(','); + } + node = node.mNext; + } + sb.append(']'); + sb.append('}'); + return sb.toString(); + } + } + } + + /** + * A data class for holding the parameters in computing oom adj. + */ + private class OomAdjusterArgs { + ProcessRecord mApp; + ProcessRecord mTopApp; + long mNow; + int mCachedAdj; + @OomAdjReason int mOomAdjReason; + @NonNull ActiveUids mUids; + boolean mFullUpdate; + + void update(ProcessRecord topApp, long now, int cachedAdj, + @OomAdjReason int oomAdjReason, @NonNull ActiveUids uids, boolean fullUpdate) { + mTopApp = topApp; + mNow = now; + mCachedAdj = cachedAdj; + mOomAdjReason = oomAdjReason; + mUids = uids; + mFullUpdate = fullUpdate; + } + } + + OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList, + ActiveUids activeUids) { + this(service, processList, activeUids, createAdjusterThread()); + } + + OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList, + ActiveUids activeUids, ServiceThread adjusterThread) { + super(service, processList, activeUids, adjusterThread); + } + + private final ProcessRecordNodes mProcessRecordProcStateNodes = new ProcessRecordNodes( + ProcessRecordNode.NODE_TYPE_PROC_STATE, PROC_STATE_SLOTS.length); + private final ProcessRecordNodes mProcessRecordAdjNodes = new ProcessRecordNodes( + ProcessRecordNode.NODE_TYPE_ADJ, ADJ_SLOT_VALUES.length); + private final OomAdjusterArgs mTmpOomAdjusterArgs = new OomAdjusterArgs(); + + void linkProcessRecordToList(@NonNull ProcessRecord app) { + mProcessRecordProcStateNodes.append(app); + mProcessRecordAdjNodes.append(app); + } + + void unlinkProcessRecordFromList(@NonNull ProcessRecord app) { + mProcessRecordProcStateNodes.unlink(app); + mProcessRecordAdjNodes.unlink(app); + } + + @Override + @VisibleForTesting + void resetInternal() { + mProcessRecordProcStateNodes.reset(); + mProcessRecordAdjNodes.reset(); + } + + @GuardedBy("mService") + @Override + void onProcessBeginLocked(@NonNull ProcessRecord app) { + // Check one type should be good enough. + if (app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE] == null) { + for (int i = 0; i < app.mLinkedNodes.length; i++) { + app.mLinkedNodes[i] = new ProcessRecordNode(app); + } + } + if (!app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE].isLinked()) { + linkProcessRecordToList(app); + } + } + + @GuardedBy("mService") + @Override + void onProcessEndLocked(@NonNull ProcessRecord app) { + if (app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE] != null + && app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE].isLinked()) { + unlinkProcessRecordFromList(app); + } + } + + @GuardedBy("mService") + @Override + void onProcessStateChanged(@NonNull ProcessRecord app, int prevProcState) { + updateProcStateSlotIfNecessary(app, prevProcState); + } + + @GuardedBy("mService") + void onProcessOomAdjChanged(@NonNull ProcessRecord app, int prevAdj) { + updateAdjSlotIfNecessary(app, prevAdj); + } + + @GuardedBy("mService") + @Override + protected int getInitialAdj(@NonNull ProcessRecord app) { + return UNKNOWN_ADJ; + } + + @GuardedBy("mService") + @Override + protected int getInitialProcState(@NonNull ProcessRecord app) { + return PROCESS_STATE_UNKNOWN; + } + + @GuardedBy("mService") + @Override + protected int getInitialCapability(@NonNull ProcessRecord app) { + return 0; + } + + @GuardedBy("mService") + @Override + protected boolean getInitialIsCurBoundByNonBgRestrictedApp(@NonNull ProcessRecord app) { + return false; + } + + private void updateAdjSlotIfNecessary(ProcessRecord app, int prevRawAdj) { + if (app.mState.getCurRawAdj() != prevRawAdj) { + final int slot = adjToSlot(app.mState.getCurRawAdj()); + final int prevSlot = adjToSlot(prevRawAdj); + if (slot != prevSlot && slot != ADJ_SLOT_INVALID) { + mProcessRecordAdjNodes.moveAppTo(app, prevSlot, slot); + } + } + } + + private void updateProcStateSlotIfNecessary(ProcessRecord app, int prevProcState) { + if (app.mState.getCurProcState() != prevProcState) { + final int slot = processStateToSlot(app.mState.getCurProcState()); + final int prevSlot = processStateToSlot(prevProcState); + if (slot != prevSlot) { + mProcessRecordProcStateNodes.moveAppTo(app, prevSlot, slot); + } + } + } + + @Override + protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) { + final ProcessRecord topApp = mService.getTopApp(); + + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); + mService.mOomAdjProfiler.oomAdjStarted(); + mAdjSeq++; + + final ProcessStateRecord state = app.mState; + final int oldAdj = state.getCurRawAdj(); + final int cachedAdj = oldAdj >= CACHED_APP_MIN_ADJ + ? oldAdj : UNKNOWN_ADJ; + + final ActiveUids uids = mTmpUidRecords; + final ArraySet<ProcessRecord> targetProcesses = mTmpProcessSet; + final ArrayList<ProcessRecord> reachableProcesses = mTmpProcessList; + final long now = SystemClock.uptimeMillis(); + final long nowElapsed = SystemClock.elapsedRealtime(); + + uids.clear(); + targetProcesses.clear(); + targetProcesses.add(app); + reachableProcesses.clear(); + + // Find out all reachable processes from this app. + collectReachableProcessesLocked(targetProcesses, reachableProcesses, uids); + + // Copy all of the reachable processes into the target process set. + targetProcesses.addAll(reachableProcesses); + reachableProcesses.clear(); + + final boolean result = performNewUpdateOomAdjLSP(oomAdjReason, + topApp, targetProcesses, uids, false, now, cachedAdj); + + reachableProcesses.addAll(targetProcesses); + assignCachedAdjIfNecessary(reachableProcesses); + for (int i = uids.size() - 1; i >= 0; i--) { + final UidRecord uidRec = uids.valueAt(i); + uidRec.forEachProcess(this::updateAppUidRecIfNecessaryLSP); + } + updateUidsLSP(uids, nowElapsed); + for (int i = 0, size = targetProcesses.size(); i < size; i++) { + applyOomAdjLSP(targetProcesses.valueAt(i), false, now, nowElapsed, oomAdjReason); + } + targetProcesses.clear(); + reachableProcesses.clear(); + + mService.mOomAdjProfiler.oomAdjEnded(); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + return result; + } + + @GuardedBy({"mService", "mProcLock"}) + @Override + protected void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp, + ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles, + boolean startProfiling) { + final boolean fullUpdate = processes == null; + final ArrayList<ProcessRecord> activeProcesses = fullUpdate + ? mProcessList.getLruProcessesLOSP() : processes; + ActiveUids activeUids = uids; + if (activeUids == null) { + final int numUids = mActiveUids.size(); + activeUids = mTmpUidRecords; + activeUids.clear(); + for (int i = 0; i < numUids; i++) { + UidRecord uidRec = mActiveUids.valueAt(i); + activeUids.put(uidRec.getUid(), uidRec); + } + } + + if (startProfiling) { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); + mService.mOomAdjProfiler.oomAdjStarted(); + } + final long now = SystemClock.uptimeMillis(); + final long nowElapsed = SystemClock.elapsedRealtime(); + final long oldTime = now - mConstants.mMaxEmptyTimeMillis; + final int numProc = activeProcesses.size(); + + mAdjSeq++; + if (fullUpdate) { + mNewNumServiceProcs = 0; + mNewNumAServiceProcs = 0; + } + + final ArraySet<ProcessRecord> targetProcesses = mTmpProcessSet; + targetProcesses.clear(); + if (!fullUpdate) { + targetProcesses.addAll(activeProcesses); + } + + performNewUpdateOomAdjLSP(oomAdjReason, topApp, targetProcesses, activeUids, + fullUpdate, now, UNKNOWN_ADJ); + + if (fullUpdate) { + assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP()); + postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime); + } else { + activeProcesses.clear(); + activeProcesses.addAll(targetProcesses); + assignCachedAdjIfNecessary(activeProcesses); + + for (int i = activeUids.size() - 1; i >= 0; i--) { + final UidRecord uidRec = activeUids.valueAt(i); + uidRec.forEachProcess(this::updateAppUidRecIfNecessaryLSP); + } + updateUidsLSP(activeUids, nowElapsed); + + for (int i = 0, size = targetProcesses.size(); i < size; i++) { + applyOomAdjLSP(targetProcesses.valueAt(i), false, now, nowElapsed, oomAdjReason); + } + + activeProcesses.clear(); + } + targetProcesses.clear(); + + if (startProfiling) { + mService.mOomAdjProfiler.oomAdjEnded(); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } + return; + } + + /** + * Perform the oom adj update on the given {@code targetProcesses}. + * + * <p>Note: The expectation to the given {@code targetProcesses} is, the caller + * must have called {@link collectReachableProcessesLocked} on it. + */ + private boolean performNewUpdateOomAdjLSP(@OomAdjReason int oomAdjReason, + ProcessRecord topApp, ArraySet<ProcessRecord> targetProcesses, ActiveUids uids, + boolean fullUpdate, long now, int cachedAdj) { + + final ArrayList<ProcessRecord> clientProcesses = mTmpProcessList2; + clientProcesses.clear(); + + // We'll need to collect the upstream processes of the target apps here, because those + // processes would potentially impact the procstate/adj via bindings. + if (!fullUpdate) { + final boolean containsCycle = collectReversedReachableProcessesLocked(targetProcesses, + clientProcesses); + + // If any of its upstream processes are in a cycle, + // move them into the candidate targets. + if (containsCycle) { + // Add all client apps to the target process list. + for (int i = 0, size = clientProcesses.size(); i < size; i++) { + final ProcessRecord client = clientProcesses.get(i); + final UidRecord uidRec = client.getUidRecord(); + targetProcesses.add(client); + if (uidRec != null) { + uids.put(uidRec.getUid(), uidRec); + } + } + clientProcesses.clear(); + } + for (int i = 0, size = targetProcesses.size(); i < size; i++) { + final ProcessRecord app = targetProcesses.valueAt(i); + app.mState.resetCachedInfo(); + final UidRecord uidRec = app.getUidRecord(); + if (uidRec != null) { + if (DEBUG_UID_OBSERVERS) { + Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec); + } + uidRec.reset(); + } + } + } else { + final ArrayList<ProcessRecord> lru = mProcessList.getLruProcessesLOSP(); + for (int i = 0, size = lru.size(); i < size; i++) { + final ProcessRecord app = lru.get(i); + app.mState.resetCachedInfo(); + final UidRecord uidRec = app.getUidRecord(); + if (uidRec != null) { + if (DEBUG_UID_OBSERVERS) { + Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec); + } + uidRec.reset(); + } + } + } + + updateNewOomAdjInnerLSP(oomAdjReason, topApp, targetProcesses, clientProcesses, uids, + cachedAdj, now, fullUpdate); + + clientProcesses.clear(); + + return true; + } + + /** + * Collect the reversed reachable processes from the given {@code apps}, the result will be + * returned in the given {@code processes}, which will <em>NOT</em> include the processes from + * the given {@code apps}. + */ + @GuardedBy("mService") + private boolean collectReversedReachableProcessesLocked(ArraySet<ProcessRecord> apps, + ArrayList<ProcessRecord> clientProcesses) { + final ArrayDeque<ProcessRecord> queue = mTmpQueue; + queue.clear(); + clientProcesses.clear(); + for (int i = 0, size = apps.size(); i < size; i++) { + final ProcessRecord app = apps.valueAt(i); + app.mState.setReachable(true); + app.mState.setReversedReachable(true); + queue.offer(app); + } + + // Track if any of them reachables could include a cycle + boolean containsCycle = false; + + // Scan upstreams of the process record + for (ProcessRecord pr = queue.poll(); pr != null; pr = queue.poll()) { + if (!pr.mState.isReachable()) { + // If not in the given initial set of apps, add it. + clientProcesses.add(pr); + } + final ProcessServiceRecord psr = pr.mServices; + for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) { + final ServiceRecord s = psr.getRunningServiceAt(i); + final ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = + s.getConnections(); + for (int j = serviceConnections.size() - 1; j >= 0; j--) { + final ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(j); + for (int k = clist.size() - 1; k >= 0; k--) { + final ConnectionRecord cr = clist.get(k); + final ProcessRecord client = cr.binding.client; + containsCycle |= client.mState.isReversedReachable(); + if (client.mState.isReversedReachable()) { + continue; + } + queue.offer(client); + client.mState.setReversedReachable(true); + } + } + } + final ProcessProviderRecord ppr = pr.mProviders; + for (int i = ppr.numberOfProviders() - 1; i >= 0; i--) { + final ContentProviderRecord cpr = ppr.getProviderAt(i); + for (int j = cpr.connections.size() - 1; j >= 0; j--) { + final ContentProviderConnection conn = cpr.connections.get(j); + final ProcessRecord client = conn.client; + containsCycle |= client.mState.isReversedReachable(); + if (client.mState.isReversedReachable()) { + continue; + } + queue.offer(client); + client.mState.setReversedReachable(true); + } + } + // If this process is a sandbox itself, also add the app on whose behalf + // its running + if (pr.isSdkSandbox) { + for (int is = psr.numberOfRunningServices() - 1; is >= 0; is--) { + ServiceRecord s = psr.getRunningServiceAt(is); + ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = + s.getConnections(); + for (int conni = serviceConnections.size() - 1; conni >= 0; conni--) { + ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(conni); + for (int i = clist.size() - 1; i >= 0; i--) { + ConnectionRecord cr = clist.get(i); + ProcessRecord attributedApp = cr.binding.attributedClient; + if (attributedApp == null || attributedApp == pr) { + continue; + } + containsCycle |= attributedApp.mState.isReversedReachable(); + if (attributedApp.mState.isReversedReachable()) { + continue; + } + queue.offer(attributedApp); + attributedApp.mState.setReversedReachable(true); + } + } + } + } + } + + // Reset the temporary bits. + for (int i = clientProcesses.size() - 1; i >= 0; i--) { + clientProcesses.get(i).mState.setReversedReachable(false); + } + for (int i = 0, size = apps.size(); i < size; i++) { + final ProcessRecord app = apps.valueAt(i); + app.mState.setReachable(false); + app.mState.setReversedReachable(false); + } + return containsCycle; + } + + @GuardedBy({"mService", "mProcLock"}) + private void updateNewOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp, + ArraySet<ProcessRecord> targetProcesses, ArrayList<ProcessRecord> clientProcesses, + ActiveUids uids, int cachedAdj, long now, boolean fullUpdate) { + mTmpOomAdjusterArgs.update(topApp, now, cachedAdj, oomAdjReason, uids, fullUpdate); + + mProcessRecordProcStateNodes.resetLastNodes(); + mProcessRecordAdjNodes.resetLastNodes(); + + final int procStateTarget = mProcessRecordProcStateNodes.size() - 1; + final int adjTarget = mProcessRecordAdjNodes.size() - 1; + + final int appUid = !fullUpdate && targetProcesses.size() > 0 + ? targetProcesses.valueAt(0).uid : -1; + final int logUid = mService.mCurOomAdjUid; + + mAdjSeq++; + // All apps to be updated will be moved to the lowest slot. + if (fullUpdate) { + // Move all the process record node to the lowest slot, we'll do recomputation on all of + // them. Use the processes from the lru list, because the scanning order matters here. + final ArrayList<ProcessRecord> lruList = mProcessList.getLruProcessesLOSP(); + for (int i = procStateTarget; i >= 0; i--) { + mProcessRecordProcStateNodes.reset(i); + // Force the last node to the head since we'll recompute all of them. + mProcessRecordProcStateNodes.setLastNodeToHead(i); + } + // enqueue the targets in the reverse order of the lru list. + for (int i = lruList.size() - 1; i >= 0; i--) { + mProcessRecordProcStateNodes.append(lruList.get(i), procStateTarget); + } + // Do the same to the adj nodes. + for (int i = adjTarget; i >= 0; i--) { + mProcessRecordAdjNodes.reset(i); + // Force the last node to the head since we'll recompute all of them. + mProcessRecordAdjNodes.setLastNodeToHead(i); + } + for (int i = lruList.size() - 1; i >= 0; i--) { + mProcessRecordAdjNodes.append(lruList.get(i), adjTarget); + } + } else { + // Move the target processes to the lowest slot. + for (int i = 0, size = targetProcesses.size(); i < size; i++) { + final ProcessRecord app = targetProcesses.valueAt(i); + final int procStateSlot = processStateToSlot(app.mState.getCurProcState()); + final int adjSlot = adjToSlot(app.mState.getCurRawAdj()); + mProcessRecordProcStateNodes.moveAppTo(app, procStateSlot, procStateTarget); + mProcessRecordAdjNodes.moveAppTo(app, adjSlot, adjTarget); + } + // Move the "lastNode" to head to make sure we scan all nodes in this slot. + mProcessRecordProcStateNodes.setLastNodeToHead(procStateTarget); + mProcessRecordAdjNodes.setLastNodeToHead(adjTarget); + } + + // All apps to be updated have been moved to the lowest slot. + // Do an initial pass of the computation. + mProcessRecordProcStateNodes.forEachNewNode(mProcessRecordProcStateNodes.size() - 1, + this::computeInitialOomAdjLSP); + + if (!fullUpdate) { + // We didn't update the client processes with the computeInitialOomAdjLSP + // because they don't need to do so. But they'll be playing vital roles in + // computing the bindings. So include them into the scan list below. + for (int i = 0, size = clientProcesses.size(); i < size; i++) { + mProcessRecordProcStateNodes.moveAppToTail(clientProcesses.get(i)); + } + // We don't update the adj list since we're resetting it below. + } + + // Now nodes are set into their slots, without facting in the bindings. + // The nodes between the `lastNode` pointer and the TAIL should be the new nodes. + // + // The whole rationale here is that, the bindings from client to host app, won't elevate + // the host app's procstate/adj higher than the client app's state (BIND_ABOVE_CLIENT + // is a special case here, but client app's raw adj is still no less than the host app's). + // Therefore, starting from the top to the bottom, for each slot, scan all of the new nodes, + // check its bindings, elevate its host app's slot if necessary. + // + // We'd have to do this in two passes: 1) scan procstate node list; 2) scan adj node list. + // Because the procstate and adj are not always in sync - there are cases where + // the processes with lower proc state could be getting a higher oom adj score. + // And because of this, the procstate and adj node lists are basically two priority heaps. + // + // As the 2nd pass with the adj node lists potentially includes a significant amount of + // duplicated scans as the 1st pass has done, we'll reset the last node pointers for + // the adj node list before the 1st pass; so during the 1st pass, if any app's adj slot + // gets bumped, we'll only scan those in 2nd pass. + + mProcessRecordAdjNodes.resetLastNodes(); + + // 1st pass, scan each slot in the procstate node list. + for (int i = 0, end = mProcessRecordProcStateNodes.size() - 1; i < end; i++) { + mProcessRecordProcStateNodes.forEachNewNode(i, this::computeHostOomAdjLSP); + } + + // 2nd pass, scan each slot in the adj node list. + for (int i = 0, end = mProcessRecordAdjNodes.size() - 1; i < end; i++) { + mProcessRecordAdjNodes.forEachNewNode(i, this::computeHostOomAdjLSP); + } + } + + @GuardedBy({"mService", "mProcLock"}) + private void computeInitialOomAdjLSP(OomAdjusterArgs args) { + final ProcessRecord app = args.mApp; + final int cachedAdj = args.mCachedAdj; + final ProcessRecord topApp = args.mTopApp; + final long now = args.mNow; + final int oomAdjReason = args.mOomAdjReason; + final ActiveUids uids = args.mUids; + final boolean fullUpdate = args.mFullUpdate; + + if (DEBUG_OOM_ADJ) { + Slog.i(TAG, "OOM ADJ initial args app=" + app + + " cachedAdj=" + cachedAdj + + " topApp=" + topApp + + " now=" + now + + " oomAdjReason=" + oomAdjReasonToString(oomAdjReason) + + " fullUpdate=" + fullUpdate); + } + + if (uids != null) { + final UidRecord uidRec = app.getUidRecord(); + + if (uidRec != null) { + uids.put(uidRec.getUid(), uidRec); + } + } + + computeOomAdjLSP(app, cachedAdj, topApp, fullUpdate, now, false, false, oomAdjReason, + false); + } + + /** + * @return The proposed change to the schedGroup. + */ + @GuardedBy({"mService", "mProcLock"}) + @Override + protected int setIntermediateAdjLSP(ProcessRecord app, int adj, int prevRawAppAdj, + int schedGroup) { + schedGroup = super.setIntermediateAdjLSP(app, adj, prevRawAppAdj, schedGroup); + + updateAdjSlotIfNecessary(app, prevRawAppAdj); + + return schedGroup; + } + + @GuardedBy({"mService", "mProcLock"}) + @Override + protected void setIntermediateProcStateLSP(ProcessRecord app, int procState, + int prevProcState) { + super.setIntermediateProcStateLSP(app, procState, prevProcState); + + updateProcStateSlotIfNecessary(app, prevProcState); + } + + @GuardedBy({"mService", "mProcLock"}) + private void computeHostOomAdjLSP(OomAdjusterArgs args) { + final ProcessRecord app = args.mApp; + final int cachedAdj = args.mCachedAdj; + final ProcessRecord topApp = args.mTopApp; + final long now = args.mNow; + final @OomAdjReason int oomAdjReason = args.mOomAdjReason; + final boolean fullUpdate = args.mFullUpdate; + final ActiveUids uids = args.mUids; + + final ProcessServiceRecord psr = app.mServices; + for (int i = psr.numberOfConnections() - 1; i >= 0; i--) { + ConnectionRecord cr = psr.getConnectionAt(i); + ProcessRecord service = cr.hasFlag(ServiceInfo.FLAG_ISOLATED_PROCESS) + ? cr.binding.service.isolationHostProc : cr.binding.service.app; + if (service == null || service == app + || (service.mState.getMaxAdj() >= SYSTEM_ADJ + && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ) + || (service.mState.getCurAdj() <= FOREGROUND_APP_ADJ + && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND + && service.mState.getCurProcState() <= PROCESS_STATE_TOP)) { + continue; + } + + + computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false, + oomAdjReason, cachedAdj, false); + } + + for (int i = psr.numberOfSdkSandboxConnections() - 1; i >= 0; i--) { + final ConnectionRecord cr = psr.getSdkSandboxConnectionAt(i); + final ProcessRecord service = cr.binding.service.app; + if (service == null || service == app + || (service.mState.getMaxAdj() >= SYSTEM_ADJ + && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ) + || (service.mState.getCurAdj() <= FOREGROUND_APP_ADJ + && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND + && service.mState.getCurProcState() <= PROCESS_STATE_TOP)) { + continue; + } + + computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false, + oomAdjReason, cachedAdj, false); + } + + final ProcessProviderRecord ppr = app.mProviders; + for (int i = ppr.numberOfProviderConnections() - 1; i >= 0; i--) { + ContentProviderConnection cpc = ppr.getProviderConnectionAt(i); + ProcessRecord provider = cpc.provider.proc; + if (provider == null || provider == app + || (provider.mState.getMaxAdj() >= ProcessList.SYSTEM_ADJ + && provider.mState.getMaxAdj() < FOREGROUND_APP_ADJ) + || (provider.mState.getCurAdj() <= FOREGROUND_APP_ADJ + && provider.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND + && provider.mState.getCurProcState() <= PROCESS_STATE_TOP)) { + continue; + } + + computeProviderHostOomAdjLSP(cpc, provider, app, now, topApp, fullUpdate, false, false, + oomAdjReason, cachedAdj, false); + } + } +} diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index f532122c10d9..e2edd8a37091 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -22,6 +22,7 @@ import static com.android.internal.util.Preconditions.checkArgument; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.am.ActivityManagerService.MY_PID; +import static com.android.server.am.OomAdjusterModernImpl.ProcessRecordNode.NUM_NODE_TYPE; import static java.util.Objects.requireNonNull; @@ -63,6 +64,7 @@ import com.android.internal.app.procstats.ProcessState; import com.android.internal.app.procstats.ProcessStats; import com.android.internal.os.Zygote; import com.android.server.FgThread; +import com.android.server.am.OomAdjusterModernImpl.ProcessRecordNode; import com.android.server.wm.WindowProcessController; import com.android.server.wm.WindowProcessListener; @@ -434,6 +436,8 @@ class ProcessRecord implements WindowProcessListener { */ volatile boolean mSkipProcessGroupCreation; + final ProcessRecordNode[] mLinkedNodes = new ProcessRecordNode[NUM_NODE_TYPE]; + void setStartParams(int startUid, HostingRecord hostingRecord, String seInfo, long startUptime, long startElapsedTime) { this.mStartUid = startUid; @@ -770,6 +774,11 @@ class ProcessRecord implements WindowProcessListener { } @GuardedBy("mService") + boolean isThreadReady() { + return mThread != null && !mPendingFinishAttach; + } + + @GuardedBy("mService") long getStartSeq() { return mStartSeq; } @@ -1114,6 +1123,7 @@ class ProcessRecord implements WindowProcessListener { mState.onCleanupApplicationRecordLSP(); mServices.onCleanupApplicationRecordLocked(); mReceivers.onCleanupApplicationRecordLocked(); + mService.mOomAdjuster.onProcessEndLocked(this); return mProviders.onCleanupApplicationRecordLocked(allowRestart); } diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java index 7ff6d116baaf..a165e8897aa4 100644 --- a/services/core/java/com/android/server/am/ProcessServiceRecord.java +++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java @@ -19,6 +19,7 @@ package com.android.server.am; import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BOUND_SERVICE; import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE; +import android.annotation.Nullable; import android.app.ActivityManager; import android.content.Context; import android.content.pm.ServiceInfo; @@ -134,6 +135,11 @@ final class ProcessServiceRecord { private final ArraySet<ConnectionRecord> mConnections = new ArraySet<>(); /** + * All ConnectionRecord this process holds indirectly to SDK sandbox processes. + */ + private @Nullable ArraySet<ConnectionRecord> mSdkSandboxConnections; + + /** * A set of UIDs of all bound clients. */ private ArraySet<Integer> mBoundClientUids = new ArraySet<>(); @@ -490,13 +496,18 @@ final class ProcessServiceRecord { void addConnection(ConnectionRecord connection) { mConnections.add(connection); + addSdkSandboxConnectionIfNecessary(connection); } void removeConnection(ConnectionRecord connection) { mConnections.remove(connection); + removeSdkSandboxConnectionIfNecessary(connection); } void removeAllConnections() { + for (int i = 0, size = mConnections.size(); i < size; i++) { + removeSdkSandboxConnectionIfNecessary(mConnections.valueAt(i)); + } mConnections.clear(); } @@ -508,6 +519,39 @@ final class ProcessServiceRecord { return mConnections.size(); } + private void addSdkSandboxConnectionIfNecessary(ConnectionRecord connection) { + final ProcessRecord attributedClient = connection.binding.attributedClient; + if (attributedClient != null && connection.binding.service.isSdkSandbox) { + if (attributedClient.mServices.mSdkSandboxConnections == null) { + attributedClient.mServices.mSdkSandboxConnections = new ArraySet<>(); + } + attributedClient.mServices.mSdkSandboxConnections.add(connection); + } + } + + private void removeSdkSandboxConnectionIfNecessary(ConnectionRecord connection) { + final ProcessRecord attributedClient = connection.binding.attributedClient; + if (attributedClient != null && connection.binding.service.isSdkSandbox) { + if (attributedClient.mServices.mSdkSandboxConnections == null) { + attributedClient.mServices.mSdkSandboxConnections.remove(connection); + } + } + } + + void removeAllSdkSandboxConnections() { + if (mSdkSandboxConnections != null) { + mSdkSandboxConnections.clear(); + } + } + + ConnectionRecord getSdkSandboxConnectionAt(int index) { + return mSdkSandboxConnections != null ? mSdkSandboxConnections.valueAt(index) : null; + } + + int numberOfSdkSandboxConnections() { + return mSdkSandboxConnections != null ? mSdkSandboxConnections.size() : 0; + } + void addBoundClientUid(int clientUid, String clientPackageName, long bindFlags) { mBoundClientUids.add(clientUid); mApp.getWindowProcessController() diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java index db341d253818..a9c388c232ed 100644 --- a/services/core/java/com/android/server/am/ProcessStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessStateRecord.java @@ -378,6 +378,12 @@ final class ProcessStateRecord { private boolean mReachable; /** + * Whether or not this process is reversed reachable from given process. + */ + @GuardedBy("mService") + private boolean mReversedReachable; + + /** * The most recent time when the last visible activity within this process became invisible. * * <p> It'll be set to 0 if there is never a visible activity, or Long.MAX_VALUE if there is @@ -454,6 +460,9 @@ final class ProcessStateRecord { @GuardedBy("mService") private int mCachedSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND; + @GuardedBy("mService") + private boolean mScheduleLikeTopApp = false; + ProcessStateRecord(ProcessRecord app) { mApp = app; mService = app.mService; @@ -614,9 +623,11 @@ final class ProcessStateRecord { void forceProcessStateUpTo(int newState) { if (mRepProcState > newState) { synchronized (mProcLock) { + final int prevProcState = mRepProcState; setReportedProcState(newState); setCurProcState(newState); setCurRawProcState(newState); + mService.mOomAdjuster.onProcessStateChanged(mApp, prevProcState); } } } @@ -985,6 +996,16 @@ final class ProcessStateRecord { } @GuardedBy("mService") + boolean isReversedReachable() { + return mReversedReachable; + } + + @GuardedBy("mService") + void setReversedReachable(boolean reversedReachable) { + mReversedReachable = reversedReachable; + } + + @GuardedBy("mService") void resetCachedInfo() { mCachedHasActivities = VALUE_INVALID; mCachedIsHeavyWeight = VALUE_INVALID; @@ -1134,6 +1155,16 @@ final class ProcessStateRecord { return mCachedSchedGroup; } + @GuardedBy("mService") + boolean shouldScheduleLikeTopApp() { + return mScheduleLikeTopApp; + } + + @GuardedBy("mService") + void setScheduleLikeTopApp(boolean scheduleLikeTopApp) { + mScheduleLikeTopApp = scheduleLikeTopApp; + } + @GuardedBy(anyOf = {"mService", "mProcLock"}) public String makeAdjReason() { if (mAdjSource != null || mAdjTarget != null) { diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING index 0af9b2b4c26a..575db01931e6 100644 --- a/services/core/java/com/android/server/am/TEST_MAPPING +++ b/services/core/java/com/android/server/am/TEST_MAPPING @@ -88,11 +88,14 @@ "file_patterns": ["Battery[^/]*\\.java", "MeasuredEnergy[^/]*\\.java"], "name": "FrameworksServicesTests", "options": [ - { "include-filter": "com.android.server.am.BatteryStatsServiceTest" }, - { "include-filter": "com.android.server.power.stats.BatteryStatsTests" } + { "include-filter": "com.android.server.am.BatteryStatsServiceTest" } ] }, { + "file_patterns": ["Battery[^/]*\\.java", "MeasuredEnergy[^/]*\\.java"], + "name": "PowerStatsTests" + }, + { "file_patterns": ["Broadcast.*"], "name": "FrameworksMockingServicesTests", "options": [ @@ -111,27 +114,14 @@ ] }, { - "name": "CtsUsageStatsTestCases", + "name": "CtsBRSTestCases", "file_patterns": [ "ActivityManagerService\\.java", "BroadcastQueue\\.java" ], "options": [ - { - "include-filter": "android.app.usage.cts.BroadcastResponseStatsTest" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" - }, - { - "exclude-annotation": "androidx.test.filters.MediumTest" - }, - { - "exclude-annotation": "androidx.test.filters.LargeTest" - } + { "exclude-annotation": "androidx.test.filters.FlakyTest" }, + { "exclude-annotation": "org.junit.Ignore" } ] } ], diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java index 683b3eb7a92e..247094f2f796 100644 --- a/services/core/java/com/android/server/audio/AdiDeviceState.java +++ b/services/core/java/com/android/server/audio/AdiDeviceState.java @@ -16,6 +16,7 @@ package com.android.server.audio; +import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN; import static android.media.AudioSystem.DEVICE_NONE; import static android.media.AudioSystem.isBluetoothDevice; @@ -23,8 +24,10 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; +import android.media.AudioManager; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import java.util.Objects; @@ -41,8 +44,16 @@ import java.util.Objects; private final int mDeviceType; private final int mInternalDeviceType; + @NonNull private final String mDeviceAddress; + + /** Unique device id from internal device type and address. */ + private final Pair<Integer, String> mDeviceId; + + @AudioManager.AudioDeviceCategory + private int mAudioDeviceCategory = AUDIO_DEVICE_CATEGORY_UNKNOWN; + private boolean mSAEnabled; private boolean mHasHeadTracker = false; private boolean mHeadTrackerEnabled; @@ -68,6 +79,12 @@ import java.util.Objects; } mDeviceAddress = isBluetoothDevice(mInternalDeviceType) ? Objects.requireNonNull( address) : ""; + + mDeviceId = new Pair<>(mInternalDeviceType, mDeviceAddress); + } + + public Pair<Integer, String> getDeviceId() { + return mDeviceId; } @AudioDeviceInfo.AudioDeviceType @@ -109,6 +126,15 @@ import java.util.Objects; return mHasHeadTracker; } + @AudioDeviceInfo.AudioDeviceType + public int getAudioDeviceCategory() { + return mAudioDeviceCategory; + } + + public void setAudioDeviceCategory(@AudioDeviceInfo.AudioDeviceType int audioDeviceCategory) { + mAudioDeviceCategory = audioDeviceCategory; + } + @Override public boolean equals(Object obj) { if (this == obj) { @@ -127,20 +153,23 @@ import java.util.Objects; && mDeviceAddress.equals(sads.mDeviceAddress) // NonNull && mSAEnabled == sads.mSAEnabled && mHasHeadTracker == sads.mHasHeadTracker - && mHeadTrackerEnabled == sads.mHeadTrackerEnabled; + && mHeadTrackerEnabled == sads.mHeadTrackerEnabled + && mAudioDeviceCategory == sads.mAudioDeviceCategory; } @Override public int hashCode() { return Objects.hash(mDeviceType, mInternalDeviceType, mDeviceAddress, mSAEnabled, - mHasHeadTracker, mHeadTrackerEnabled); + mHasHeadTracker, mHeadTrackerEnabled, mAudioDeviceCategory); } @Override public String toString() { return "type: " + mDeviceType + "internal type: " + mInternalDeviceType - + " addr: " + mDeviceAddress + " enabled: " + mSAEnabled - + " HT: " + mHasHeadTracker + " HTenabled: " + mHeadTrackerEnabled; + + " addr: " + mDeviceAddress + " bt audio type: " + + AudioManager.audioDeviceCategoryToString(mAudioDeviceCategory) + + " enabled: " + mSAEnabled + " HT: " + mHasHeadTracker + + " HTenabled: " + mHeadTrackerEnabled; } public String toPersistableString() { @@ -150,6 +179,7 @@ import java.util.Objects; .append(SETTING_FIELD_SEPARATOR).append(mHasHeadTracker ? "1" : "0") .append(SETTING_FIELD_SEPARATOR).append(mHeadTrackerEnabled ? "1" : "0") .append(SETTING_FIELD_SEPARATOR).append(mInternalDeviceType) + .append(SETTING_FIELD_SEPARATOR).append(mAudioDeviceCategory) .toString()); } @@ -174,21 +204,27 @@ import java.util.Objects; String[] fields = TextUtils.split(persistedString, SETTING_FIELD_SEPARATOR); // we may have 5 fields for the legacy AdiDeviceState and 6 containing the internal // device type - if (fields.length != 5 && fields.length != 6) { - // expecting all fields, fewer may mean corruption, ignore those settings + if (fields.length < 5 || fields.length > 7) { + // different number of fields may mean corruption, ignore those settings + // newly added fields are optional (mInternalDeviceType, mBtAudioDeviceCategory) return null; } try { final int deviceType = Integer.parseInt(fields[0]); int internalDeviceType = -1; - if (fields.length == 6) { + if (fields.length >= 6) { internalDeviceType = Integer.parseInt(fields[5]); } + int audioDeviceCategory = AUDIO_DEVICE_CATEGORY_UNKNOWN; + if (fields.length == 7) { + audioDeviceCategory = Integer.parseInt(fields[6]); + } final AdiDeviceState deviceState = new AdiDeviceState(deviceType, internalDeviceType, fields[1]); deviceState.setHasHeadTracker(Integer.parseInt(fields[2]) == 1); deviceState.setHasHeadTracker(Integer.parseInt(fields[3]) == 1); deviceState.setHeadTrackerEnabled(Integer.parseInt(fields[4]) == 1); + deviceState.setAudioDeviceCategory(audioDeviceCategory); return deviceState; } catch (NumberFormatException e) { Log.e(TAG, "unable to parse setting for AdiDeviceState: " + persistedString, e); @@ -200,5 +236,4 @@ import java.util.Objects; return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT, mDeviceType, mDeviceAddress); } - } diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 946f01688e66..af8aa9167217 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -63,6 +63,7 @@ import com.android.server.utils.EventLogger; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -2493,13 +2494,13 @@ public class AudioDeviceBroker { void onPersistAudioDeviceSettings() { final String deviceSettings = mDeviceInventory.getDeviceSettings(); - Log.v(TAG, "saving audio device settings: " + deviceSettings); + Log.v(TAG, "saving AdiDeviceState: " + deviceSettings); final SettingsAdapter settings = mAudioService.getSettings(); boolean res = settings.putSecureStringForUser(mAudioService.getContentResolver(), Settings.Secure.AUDIO_DEVICE_INVENTORY, deviceSettings, UserHandle.USER_CURRENT); if (!res) { - Log.e(TAG, "error saving audio device settings: " + deviceSettings); + Log.e(TAG, "error saving AdiDeviceState: " + deviceSettings); } } @@ -2509,7 +2510,7 @@ public class AudioDeviceBroker { String settings = settingsAdapter.getSecureStringForUser(contentResolver, Settings.Secure.AUDIO_DEVICE_INVENTORY, UserHandle.USER_CURRENT); if (settings == null) { - Log.i(TAG, "reading spatial audio device settings from legacy key" + Log.i(TAG, "reading AdiDeviceState from legacy key" + Settings.Secure.SPATIAL_AUDIO_ENABLED); // legacy string format for key SPATIAL_AUDIO_ENABLED has the same order of fields like // the strings for key AUDIO_DEVICE_INVENTORY. This will ensure to construct valid @@ -2517,21 +2518,21 @@ public class AudioDeviceBroker { settings = settingsAdapter.getSecureStringForUser(contentResolver, Settings.Secure.SPATIAL_AUDIO_ENABLED, UserHandle.USER_CURRENT); if (settings == null) { - Log.i(TAG, "no spatial audio device settings stored with legacy key"); + Log.i(TAG, "no AdiDeviceState stored with legacy key"); } else if (!settings.equals("")) { // Delete old key value and update the new key if (!settingsAdapter.putSecureStringForUser(contentResolver, Settings.Secure.SPATIAL_AUDIO_ENABLED, /*value=*/"", UserHandle.USER_CURRENT)) { - Log.w(TAG, "cannot erase the legacy audio device settings with key " + Log.w(TAG, "cannot erase the legacy AdiDeviceState with key " + Settings.Secure.SPATIAL_AUDIO_ENABLED); } if (!settingsAdapter.putSecureStringForUser(contentResolver, Settings.Secure.AUDIO_DEVICE_INVENTORY, settings, UserHandle.USER_CURRENT)) { - Log.e(TAG, "error updating the new audio device settings with key " + Log.e(TAG, "error updating the new AdiDeviceState with key " + Settings.Secure.AUDIO_DEVICE_INVENTORY); } } @@ -2551,19 +2552,29 @@ public class AudioDeviceBroker { return mDeviceInventory.getDeviceSettings(); } - List<AdiDeviceState> getImmutableDeviceInventory() { + Collection<AdiDeviceState> getImmutableDeviceInventory() { return mDeviceInventory.getImmutableDeviceInventory(); } - void addDeviceStateToInventory(AdiDeviceState deviceState) { - mDeviceInventory.addDeviceStateToInventory(deviceState); + void addOrUpdateDeviceSAStateInInventory(AdiDeviceState deviceState) { + mDeviceInventory.addOrUpdateDeviceSAStateInInventory(deviceState); } + void addOrUpdateBtAudioDeviceCategoryInInventory(AdiDeviceState deviceState) { + mDeviceInventory.addOrUpdateAudioDeviceCategoryInInventory(deviceState); + } + + @Nullable AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada, int canonicalType) { return mDeviceInventory.findDeviceStateForAudioDeviceAttributes(ada, canonicalType); } + @Nullable + AdiDeviceState findBtDeviceStateForAddress(String address, boolean isBle) { + return mDeviceInventory.findBtDeviceStateForAddress(address, isBle); + } + //------------------------------------------------ // for testing purposes only void clearDeviceInventory() { diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index f5b7ecf5daf4..5a92cb464950 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -15,6 +15,8 @@ */ package com.android.server.audio; +import static android.media.AudioSystem.DEVICE_OUT_ALL_A2DP_SET; +import static android.media.AudioSystem.DEVICE_OUT_ALL_BLE_SET; import static android.media.AudioSystem.isBluetoothDevice; import android.annotation.NonNull; @@ -61,11 +63,13 @@ import com.google.android.collect.Sets; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.UUID; @@ -90,35 +94,95 @@ public class AudioDeviceInventory { private static final String mMetricsId = "audio.device."; private final Object mDeviceInventoryLock = new Object(); - @GuardedBy("mDeviceCatalogLock") - private final ArrayList<AdiDeviceState> mDeviceInventory = new ArrayList<>(0); - List<AdiDeviceState> getImmutableDeviceInventory() { + + @GuardedBy("mDeviceInventoryLock") + private final HashMap<Pair<Integer, String>, AdiDeviceState> mDeviceInventory = new HashMap<>(); + + Collection<AdiDeviceState> getImmutableDeviceInventory() { + synchronized (mDeviceInventoryLock) { + return mDeviceInventory.values(); + } + } + + /** + * Adds a new AdiDeviceState or updates the spatial audio related properties of the matching + * AdiDeviceState in the {@link AudioDeviceInventory#mDeviceInventory} list. + * @param deviceState the device to update + */ + void addOrUpdateDeviceSAStateInInventory(AdiDeviceState deviceState) { + synchronized (mDeviceInventoryLock) { + mDeviceInventory.merge(deviceState.getDeviceId(), deviceState, (oldState, newState) -> { + oldState.setHasHeadTracker(newState.hasHeadTracker()); + oldState.setHeadTrackerEnabled(newState.isHeadTrackerEnabled()); + oldState.setSAEnabled(newState.isSAEnabled()); + return oldState; + }); + } + } + + /** + * Adds a new AdiDeviceState or updates the audio device cateogory of the matching + * AdiDeviceState in the {@link AudioDeviceInventory#mDeviceInventory} list. + * @param deviceState the device to update + */ + void addOrUpdateAudioDeviceCategoryInInventory(AdiDeviceState deviceState) { synchronized (mDeviceInventoryLock) { - return List.copyOf(mDeviceInventory); + mDeviceInventory.merge(deviceState.getDeviceId(), deviceState, (oldState, newState) -> { + oldState.setAudioDeviceCategory(newState.getAudioDeviceCategory()); + return oldState; + }); } } - void addDeviceStateToInventory(AdiDeviceState deviceState) { + /** + * Finds the BT device that matches the passed {@code address}. Currently, this method only + * returns a valid device for A2DP and BLE devices. + * + * @param address MAC address of BT device + * @param isBle true if the device is BLE, false for A2DP + * @return the found {@link AdiDeviceState} or {@code null} otherwise. + */ + @Nullable + AdiDeviceState findBtDeviceStateForAddress(String address, boolean isBle) { synchronized (mDeviceInventoryLock) { - mDeviceInventory.add(deviceState); + final Set<Integer> deviceSet = isBle ? DEVICE_OUT_ALL_BLE_SET : DEVICE_OUT_ALL_A2DP_SET; + for (Integer internalType : deviceSet) { + AdiDeviceState deviceState = mDeviceInventory.get( + new Pair<>(internalType, address)); + if (deviceState != null) { + return deviceState; + } + } } + return null; } + /** + * Finds the device state that matches the passed {@link AudioDeviceAttributes} and device + * type. Note: currently this method only returns a valid device for A2DP and BLE devices. + * + * @param ada attributes of device to match + * @param canonicalDeviceType external device type to match + * @return the found {@link AdiDeviceState} matching a cached A2DP or BLE device or + * {@code null} otherwise. + */ + @Nullable AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada, int canonicalDeviceType) { final boolean isWireless = isBluetoothDevice(ada.getInternalType()); synchronized (mDeviceInventoryLock) { - for (AdiDeviceState deviceSetting : mDeviceInventory) { - if (deviceSetting.getDeviceType() == canonicalDeviceType + for (AdiDeviceState deviceState : mDeviceInventory.values()) { + if (deviceState.getDeviceType() == canonicalDeviceType && (!isWireless || ada.getAddress().equals( - deviceSetting.getDeviceAddress()))) { - return deviceSetting; + deviceState.getDeviceAddress()))) { + return deviceState; } } } return null; } + /** Clears all cached {@link AdiDeviceState}'s. */ void clearDeviceInventory() { synchronized (mDeviceInventoryLock) { mDeviceInventory.clear(); @@ -384,7 +448,7 @@ public class AudioDeviceInventory { + " role:" + key.second + " devices:" + devices); }); pw.println("\ndevices:\n"); synchronized (mDeviceInventoryLock) { - for (AdiDeviceState device : mDeviceInventory) { + for (AdiDeviceState device : mDeviceInventory.values()) { pw.println("\t" + device + "\n"); } } @@ -1232,11 +1296,11 @@ public class AudioDeviceInventory { AudioDeviceInfo[] connectedDevices = AudioManager.getDevicesStatic( AudioManager.GET_DEVICES_ALL); - Iterator<Map.Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>>> itRole = + Iterator<Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>>> itRole = rolesMap.entrySet().iterator(); while (itRole.hasNext()) { - Map.Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>> entry = + Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>> entry = itRole.next(); Pair<Integer, Integer> keyRole = entry.getKey(); Iterator<AudioDeviceAttributes> itDev = rolesMap.get(keyRole).iterator(); @@ -2423,19 +2487,20 @@ public class AudioDeviceInventory { int deviceCatalogSize = 0; synchronized (mDeviceInventoryLock) { deviceCatalogSize = mDeviceInventory.size(); - } - final StringBuilder settingsBuilder = new StringBuilder( - deviceCatalogSize * AdiDeviceState.getPeristedMaxSize()); - synchronized (mDeviceInventoryLock) { - for (int i = 0; i < mDeviceInventory.size(); i++) { - settingsBuilder.append(mDeviceInventory.get(i).toPersistableString()); - if (i != mDeviceInventory.size() - 1) { - settingsBuilder.append(SETTING_DEVICE_SEPARATOR_CHAR); - } + final StringBuilder settingsBuilder = new StringBuilder( + deviceCatalogSize * AdiDeviceState.getPeristedMaxSize()); + + Iterator<AdiDeviceState> iterator = mDeviceInventory.values().iterator(); + if (iterator.hasNext()) { + settingsBuilder.append(iterator.next().toPersistableString()); + } + while (iterator.hasNext()) { + settingsBuilder.append(SETTING_DEVICE_SEPARATOR_CHAR); + settingsBuilder.append(iterator.next().toPersistableString()); } + return settingsBuilder.toString(); } - return settingsBuilder.toString(); } /*package*/ void setDeviceSettings(String settings) { @@ -2448,7 +2513,8 @@ public class AudioDeviceInventory { // Note if the device is not compatible with spatialization mode or the device // type is not canonical, it will be ignored in {@link SpatializerHelper}. if (devState != null) { - addDeviceStateToInventory(devState); + addOrUpdateDeviceSAStateInInventory(devState); + addOrUpdateAudioDeviceCategoryInInventory(devState); } } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 3353b9ec538f..76c4cfe929bb 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -18,6 +18,14 @@ package com.android.server.audio; import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED; import static android.app.BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT; +import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET; +import static android.media.AudioDeviceInfo.TYPE_BLE_SPEAKER; +import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP; +import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES; +import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN; +import static android.media.AudioManager.DEVICE_OUT_BLE_HEADSET; +import static android.media.AudioManager.DEVICE_OUT_BLE_SPEAKER; +import static android.media.AudioManager.DEVICE_OUT_BLUETOOTH_A2DP; import static android.media.AudioManager.RINGER_MODE_NORMAL; import static android.media.AudioManager.RINGER_MODE_SILENT; import static android.media.AudioManager.RINGER_MODE_VIBRATE; @@ -92,6 +100,7 @@ import android.media.AudioFocusRequest; import android.media.AudioFormat; import android.media.AudioHalVersionInfo; import android.media.AudioManager; +import android.media.AudioManager.AudioDeviceCategory; import android.media.AudioManagerInternal; import android.media.AudioMixerAttributes; import android.media.AudioPlaybackConfiguration; @@ -405,6 +414,7 @@ public class AudioService extends IAudioService.Stub private static final int MSG_DISABLE_AUDIO_FOR_UID = 100; private static final int MSG_INIT_STREAMS_VOLUMES = 101; private static final int MSG_INIT_SPATIALIZER = 102; + private static final int MSG_INIT_ADI_DEVICE_STATES = 103; // end of messages handled under wakelock @@ -1286,6 +1296,8 @@ public class AudioService extends IAudioService.Stub // done with service initialization, continue additional work in our Handler thread queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_STREAMS_VOLUMES, 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */); + queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_ADI_DEVICE_STATES, + 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */); queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_SPATIALIZER, 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */); @@ -7377,7 +7389,7 @@ public class AudioService extends IAudioService.Stub if (pkgName == null) { pkgName = ""; } - if (device.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP) { + if (device.getType() == TYPE_BLUETOOTH_A2DP) { avrcpSupportsAbsoluteVolume(device.getAddress(), deviceVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE); return; @@ -9253,6 +9265,11 @@ public class AudioService extends IAudioService.Stub mAudioEventWakeLock.release(); break; + case MSG_INIT_ADI_DEVICE_STATES: + onInitAdiDeviceStates(); + mAudioEventWakeLock.release(); + break; + case MSG_INIT_SPATIALIZER: onInitSpatializer(); mAudioEventWakeLock.release(); @@ -10322,8 +10339,13 @@ public class AudioService extends IAudioService.Stub /*arg1*/ 0, /*arg2*/ 0, TAG, /*delay*/ 0); } - void onInitSpatializer() { + void onInitAdiDeviceStates() { mDeviceBroker.onReadAudioDeviceSettings(); + mSoundDoseHelper.initCachedAudioDeviceCategories( + mDeviceBroker.getImmutableDeviceInventory()); + } + + void onInitSpatializer() { mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect); mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect); } @@ -10718,6 +10740,51 @@ public class AudioService extends IAudioService.Stub return mSoundDoseHelper.isCsdEnabled(); } + @Override + @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) + public void setBluetoothAudioDeviceCategory(@NonNull String address, boolean isBle, + @AudioDeviceCategory int btAudioDeviceCategory) { + super.setBluetoothAudioDeviceCategory_enforcePermission(); + + final String addr = Objects.requireNonNull(address); + + AdiDeviceState deviceState = mDeviceBroker.findBtDeviceStateForAddress(addr, isBle); + + int internalType = !isBle ? DEVICE_OUT_BLUETOOTH_A2DP + : ((btAudioDeviceCategory == AUDIO_DEVICE_CATEGORY_HEADPHONES) + ? DEVICE_OUT_BLE_HEADSET : DEVICE_OUT_BLE_SPEAKER); + int deviceType = !isBle ? TYPE_BLUETOOTH_A2DP + : ((btAudioDeviceCategory == AUDIO_DEVICE_CATEGORY_HEADPHONES) ? TYPE_BLE_HEADSET + : TYPE_BLE_SPEAKER); + + if (deviceState == null) { + deviceState = new AdiDeviceState(deviceType, internalType, addr); + } + + deviceState.setAudioDeviceCategory(btAudioDeviceCategory); + + mDeviceBroker.addOrUpdateBtAudioDeviceCategoryInInventory(deviceState); + mDeviceBroker.persistAudioDeviceSettings(); + + mSoundDoseHelper.setAudioDeviceCategory(addr, internalType, + btAudioDeviceCategory == AUDIO_DEVICE_CATEGORY_HEADPHONES); + } + + @Override + @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) + @AudioDeviceCategory + public int getBluetoothAudioDeviceCategory(@NonNull String address, boolean isBle) { + super.getBluetoothAudioDeviceCategory_enforcePermission(); + + final AdiDeviceState deviceState = mDeviceBroker.findBtDeviceStateForAddress( + Objects.requireNonNull(address), isBle); + if (deviceState == null) { + return AUDIO_DEVICE_CATEGORY_UNKNOWN; + } + + return deviceState.getAudioDeviceCategory(); + } + //========================================================================================== // Hdmi CEC: // - System audio mode: diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java index 01af3a838597..851c5c3cb73c 100644 --- a/services/core/java/com/android/server/audio/SoundDoseHelper.java +++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java @@ -16,6 +16,9 @@ package com.android.server.audio; +import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES; +import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN; + import static com.android.server.audio.AudioService.MAX_STREAM_VOLUME; import static com.android.server.audio.AudioService.MIN_STREAM_VOLUME; import static com.android.server.audio.AudioService.MSG_SET_DEVICE_VOLUME; @@ -57,6 +60,7 @@ import com.android.server.utils.EventLogger; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; @@ -189,6 +193,9 @@ public class SoundDoseHelper { private final AtomicBoolean mEnableCsd = new AtomicBoolean(false); + private ArrayList<ISoundDose.AudioDeviceCategory> mCachedAudioDeviceCategories = + new ArrayList<>(); + private final Object mCsdStateLock = new Object(); private final AtomicReference<ISoundDose> mSoundDose = new AtomicReference<>(); @@ -487,6 +494,43 @@ public class SoundDoseHelper { return false; } + void setAudioDeviceCategory(String address, int internalAudioType, boolean isHeadphone) { + if (!mEnableCsd.get()) { + return; + } + + final ISoundDose soundDose = mSoundDose.get(); + if (soundDose == null) { + Log.w(TAG, "Sound dose interface not initialized"); + return; + } + + try { + final ISoundDose.AudioDeviceCategory audioDeviceCategory = + new ISoundDose.AudioDeviceCategory(); + audioDeviceCategory.address = address; + audioDeviceCategory.internalAudioType = internalAudioType; + audioDeviceCategory.csdCompatible = isHeadphone; + soundDose.setAudioDeviceCategory(audioDeviceCategory); + } catch (RemoteException e) { + Log.e(TAG, "Exception while forcing the internal MEL computation", e); + } + } + + void initCachedAudioDeviceCategories(Collection<AdiDeviceState> deviceStates) { + for (final AdiDeviceState state : deviceStates) { + if (state.getAudioDeviceCategory() != AUDIO_DEVICE_CATEGORY_UNKNOWN) { + final ISoundDose.AudioDeviceCategory audioDeviceCategory = + new ISoundDose.AudioDeviceCategory(); + audioDeviceCategory.address = state.getDeviceAddress(); + audioDeviceCategory.internalAudioType = state.getInternalDeviceType(); + audioDeviceCategory.csdCompatible = + state.getAudioDeviceCategory() == AUDIO_DEVICE_CATEGORY_HEADPHONES; + mCachedAudioDeviceCategories.add(audioDeviceCategory); + } + } + } + /*package*/ int safeMediaVolumeIndex(int device) { final int vol = mSafeMediaVolumeDevices.get(device); if (vol == SAFE_MEDIA_VOLUME_UNINITIALIZED) { @@ -810,6 +854,16 @@ public class SoundDoseHelper { Log.v(TAG, "Initializing sound dose"); + try { + if (mCachedAudioDeviceCategories.size() > 0) { + soundDose.initCachedAudioDeviceCategories(mCachedAudioDeviceCategories.toArray( + new ISoundDose.AudioDeviceCategory[0])); + mCachedAudioDeviceCategories.clear(); + } + } catch (RemoteException e) { + Log.e(TAG, "Exception while forcing the internal MEL computation", e); + } + synchronized (mCsdStateLock) { if (mGlobalTimeOffsetInSecs == GLOBAL_TIME_OFFSET_UNINITIALIZED) { mGlobalTimeOffsetInSecs = System.currentTimeMillis() / 1000L; diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index 969dd60a8012..496bdf48b5bf 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -560,7 +560,7 @@ public class SpatializerHelper { updatedDevice = new AdiDeviceState(canonicalDeviceType, ada.getInternalType(), ada.getAddress()); initSAState(updatedDevice); - mDeviceBroker.addDeviceStateToInventory(updatedDevice); + mDeviceBroker.addOrUpdateDeviceSAStateInInventory(updatedDevice); } if (updatedDevice != null) { onRoutingUpdated(); @@ -693,7 +693,7 @@ public class SpatializerHelper { new AdiDeviceState(canonicalDeviceType, ada.getInternalType(), ada.getAddress()); initSAState(deviceState); - mDeviceBroker.addDeviceStateToInventory(deviceState); + mDeviceBroker.addOrUpdateDeviceSAStateInInventory(deviceState); mDeviceBroker.persistAudioDeviceSettings(); logDeviceState(deviceState, "addWirelessDeviceIfNew"); // may be updated later. } diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 279aaf9d3253..1898b8015462 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -1308,10 +1308,13 @@ public class BiometricService extends SystemService { .getString(R.string.biometric_dialog_default_subtitle)); } else if (hasEligibleFingerprintSensor) { promptInfo.setSubtitle(getContext() - .getString(R.string.biometric_dialog_fingerprint_subtitle)); + .getString(R.string.fingerprint_dialog_default_subtitle)); } else if (hasEligibleFaceSensor) { promptInfo.setSubtitle(getContext() - .getString(R.string.biometric_dialog_face_subtitle)); + .getString(R.string.face_dialog_default_subtitle)); + } else { + promptInfo.setSubtitle(getContext() + .getString(R.string.screen_lock_dialog_default_subtitle)); } } diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java index fc3d7c8114b0..745222873698 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java @@ -216,6 +216,10 @@ public final class BiometricContextProvider implements BiometricContext { public void subscribe(@NonNull OperationContextExt context, @NonNull Consumer<OperationContext> consumer) { mSubscribers.put(context, consumer); + // TODO(b/294161627) Combine the getContext/subscribe APIs to avoid race + if (context.getDisplayState() != getDisplayState()) { + consumer.accept(context.update(this, context.isCrypto()).toAidlContext()); + } } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index 3d0ea9d8bef6..54d1faa39be0 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -260,6 +260,14 @@ class FingerprintAuthenticationClient final AidlSession session = getFreshDaemon(); final OperationContextExt opContext = getOperationContext(); + final ICancellationSignal cancel; + if (session.hasContextMethods()) { + cancel = session.getSession().authenticateWithContext( + mOperationId, opContext.toAidlContext(getOptions())); + } else { + cancel = session.getSession().authenticate(mOperationId); + } + getBiometricContext().subscribe(opContext, ctx -> { if (session.hasContextMethods()) { try { @@ -281,12 +289,7 @@ class FingerprintAuthenticationClient mALSProbeCallback.getProbe().enable(); } - if (session.hasContextMethods()) { - return session.getSession().authenticateWithContext( - mOperationId, opContext.toAidlContext(getOptions())); - } else { - return session.getSession().authenticate(mOperationId); - } + return cancel; } @Override diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index e5965eff7070..c131226a60dd 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -222,8 +222,14 @@ import javax.xml.datatype.DatatypeConfigurationException; * <minimum>120</minimum> * <maximum>120</maximum> * </refreshRate> - * <thermalStatusLimit>light</thermalStatusLimit> * <allowInLowPowerMode>false</allowInLowPowerMode> + * <minimumHdrPercentOfScreen>0.6</minimumHdrPercentOfScreen> + * <sdrHdrRatioMap> + * <point> + * <sdrNits>2.000</sdrNits> + * <hdrRatio>4.000</hdrRatio> + * </point> + * </sdrHdrRatioMap> * </highBrightnessMode> * * <luxThrottling> @@ -276,6 +282,10 @@ import javax.xml.datatype.DatatypeConfigurationException; * <lightSensor> * <type>android.sensor.light</type> * <name>1234 Ambient Light Sensor</name> + * <refreshRate> + * <minimum>60</minimum> + * <maximum>120</maximum> + * </refreshRate> * </lightSensor> * <screenOffBrightnessSensor> * <type>com.google.sensor.binned_brightness</type> @@ -1568,37 +1578,40 @@ public class DisplayDeviceConfig { public String toString() { return "DisplayDeviceConfig{" + "mLoadedFrom=" + mLoadedFrom - + ", mBacklight=" + Arrays.toString(mBacklight) + + "\n" + + "mBacklight=" + Arrays.toString(mBacklight) + ", mNits=" + Arrays.toString(mNits) + ", mRawBacklight=" + Arrays.toString(mRawBacklight) + ", mRawNits=" + Arrays.toString(mRawNits) + ", mInterpolationType=" + mInterpolationType - + ", mBrightness=" + Arrays.toString(mBrightness) - + ", mBrightnessToBacklightSpline=" + mBrightnessToBacklightSpline + + "mBrightness=" + Arrays.toString(mBrightness) + + "\n" + + "mBrightnessToBacklightSpline=" + mBrightnessToBacklightSpline + ", mBacklightToBrightnessSpline=" + mBacklightToBrightnessSpline + ", mNitsToBacklightSpline=" + mNitsToBacklightSpline + ", mBacklightMinimum=" + mBacklightMinimum + ", mBacklightMaximum=" + mBacklightMaximum + ", mBrightnessDefault=" + mBrightnessDefault + ", mQuirks=" + mQuirks - + ", isHbmEnabled=" + mIsHighBrightnessModeEnabled - + ", mLuxThrottlingData=" + mLuxThrottlingData + + ", mIsHighBrightnessModeEnabled=" + mIsHighBrightnessModeEnabled + + "\n" + + "mLuxThrottlingData=" + mLuxThrottlingData + ", mHbmData=" + mHbmData + ", mSdrToHdrRatioSpline=" + mSdrToHdrRatioSpline + ", mThermalBrightnessThrottlingDataMapByThrottlingId=" + mThermalBrightnessThrottlingDataMapByThrottlingId + "\n" - + ", mBrightnessRampFastDecrease=" + mBrightnessRampFastDecrease + + "mBrightnessRampFastDecrease=" + mBrightnessRampFastDecrease + ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease + ", mBrightnessRampSlowDecrease=" + mBrightnessRampSlowDecrease + ", mBrightnessRampSlowIncrease=" + mBrightnessRampSlowIncrease + ", mBrightnessRampDecreaseMaxMillis=" + mBrightnessRampDecreaseMaxMillis + ", mBrightnessRampIncreaseMaxMillis=" + mBrightnessRampIncreaseMaxMillis + "\n" - + ", mAmbientHorizonLong=" + mAmbientHorizonLong + + "mAmbientHorizonLong=" + mAmbientHorizonLong + ", mAmbientHorizonShort=" + mAmbientHorizonShort + "\n" - + ", mScreenDarkeningMinThreshold=" + mScreenDarkeningMinThreshold + + "mScreenDarkeningMinThreshold=" + mScreenDarkeningMinThreshold + ", mScreenDarkeningMinThresholdIdle=" + mScreenDarkeningMinThresholdIdle + ", mScreenBrighteningMinThreshold=" + mScreenBrighteningMinThreshold + ", mScreenBrighteningMinThresholdIdle=" + mScreenBrighteningMinThresholdIdle @@ -1608,7 +1621,7 @@ public class DisplayDeviceConfig { + ", mAmbientLuxBrighteningMinThresholdIdle=" + mAmbientLuxBrighteningMinThresholdIdle + "\n" - + ", mScreenBrighteningLevels=" + Arrays.toString( + + "mScreenBrighteningLevels=" + Arrays.toString( mScreenBrighteningLevels) + ", mScreenBrighteningPercentages=" + Arrays.toString( mScreenBrighteningPercentages) @@ -1625,7 +1638,7 @@ public class DisplayDeviceConfig { + ", mAmbientDarkeningPercentages=" + Arrays.toString( mAmbientDarkeningPercentages) + "\n" - + ", mAmbientBrighteningLevelsIdle=" + Arrays.toString( + + "mAmbientBrighteningLevelsIdle=" + Arrays.toString( mAmbientBrighteningLevelsIdle) + ", mAmbientBrighteningPercentagesIdle=" + Arrays.toString( mAmbientBrighteningPercentagesIdle) @@ -1642,7 +1655,7 @@ public class DisplayDeviceConfig { + ", mScreenDarkeningPercentagesIdle=" + Arrays.toString( mScreenDarkeningPercentagesIdle) + "\n" - + ", mAmbientLightSensor=" + mAmbientLightSensor + + "mAmbientLightSensor=" + mAmbientLightSensor + ", mScreenOffBrightnessSensor=" + mScreenOffBrightnessSensor + ", mProximitySensor=" + mProximitySensor + ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray()) @@ -1656,7 +1669,7 @@ public class DisplayDeviceConfig { + ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable + ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable + "\n" - + ", mDefaultLowBlockingZoneRefreshRate= " + mDefaultLowBlockingZoneRefreshRate + + "mDefaultLowBlockingZoneRefreshRate= " + mDefaultLowBlockingZoneRefreshRate + ", mDefaultHighBlockingZoneRefreshRate= " + mDefaultHighBlockingZoneRefreshRate + ", mDefaultPeakRefreshRate= " + mDefaultPeakRefreshRate + ", mDefaultRefreshRate= " + mDefaultRefreshRate @@ -1665,7 +1678,7 @@ public class DisplayDeviceConfig { + ", mDefaultRefreshRateInHbmSunlight= " + mDefaultRefreshRateInHbmSunlight + ", mRefreshRateThrottlingMap= " + mRefreshRateThrottlingMap + "\n" - + ", mLowDisplayBrightnessThresholds= " + + "mLowDisplayBrightnessThresholds= " + Arrays.toString(mLowDisplayBrightnessThresholds) + ", mLowAmbientBrightnessThresholds= " + Arrays.toString(mLowAmbientBrightnessThresholds) @@ -1674,10 +1687,10 @@ public class DisplayDeviceConfig { + ", mHighAmbientBrightnessThresholds= " + Arrays.toString(mHighAmbientBrightnessThresholds) + "\n" - + ", mScreenOffBrightnessSensorValueToLux=" + Arrays.toString( + + "mScreenOffBrightnessSensorValueToLux=" + Arrays.toString( mScreenOffBrightnessSensorValueToLux) + "\n" - + ", mUsiVersion= " + mHostUsiVersion + + "mUsiVersion= " + mHostUsiVersion + "}"; } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 19dffeba868e..58f4d0859c1a 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -39,6 +39,7 @@ import static android.hardware.display.DisplayViewport.VIEWPORT_EXTERNAL; import static android.hardware.display.DisplayViewport.VIEWPORT_INTERNAL; import static android.hardware.display.DisplayViewport.VIEWPORT_VIRTUAL; import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_UNSUPPORTED; +import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; import static android.os.Process.FIRST_APPLICATION_UID; import static android.os.Process.ROOT_UID; @@ -593,7 +594,7 @@ public final class DisplayManagerService extends SystemService { DisplayManagerGlobal.invalidateLocalDisplayInfoCaches(); publishBinderService(Context.DISPLAY_SERVICE, new BinderService(), - true /*allowIsolated*/); + true /*allowIsolated*/, DUMP_FLAG_PRIORITY_CRITICAL); publishLocalService(DisplayManagerInternal.class, new LocalService()); } diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index b96187f7af77..5213d3179de7 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -613,7 +613,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal setUpAutoBrightness(resources, handler); - mColorFadeEnabled = mInjector.isColorFadeEnabled(resources); + mColorFadeEnabled = mInjector.isColorFadeEnabled() + && !resources.getBoolean( + com.android.internal.R.bool.config_displayColorFadeDisabled); mColorFadeFadesConfig = resources.getBoolean( R.bool.config_animateScreenLights); @@ -2449,6 +2451,10 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal dumpRbcEvents(pw); + if (mScreenOffBrightnessSensorController != null) { + mScreenOffBrightnessSensorController.dump(pw); + } + if (mBrightnessRangeController != null) { mBrightnessRangeController.dump(pw); } @@ -3005,10 +3011,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal sensorManager, resources); } - boolean isColorFadeEnabled(Resources resources) { - return !ActivityManager.isLowRamDeviceStatic() - && !resources.getBoolean( - com.android.internal.R.bool.config_displayColorFadeDisabled); + boolean isColorFadeEnabled() { + return !ActivityManager.isLowRamDeviceStatic(); } } diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 4edc8bc3eceb..9c271ff54d6b 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -441,6 +441,9 @@ final class LogicalDisplay { if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED) != 0) { mBaseDisplayInfo.flags |= Display.FLAG_ALWAYS_UNLOCKED; } + if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0) { + mBaseDisplayInfo.flags |= Display.FLAG_ROTATES_WITH_CONTENT; + } if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_TOUCH_FEEDBACK_DISABLED) != 0) { mBaseDisplayInfo.flags |= Display.FLAG_TOUCH_FEEDBACK_DISABLED; } diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java index 5bdf26307d11..a5162c09f838 100644 --- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java +++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java @@ -169,7 +169,9 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { @Override @MainThread public void onInputDeviceAdded(int deviceId) { - onInputDeviceChanged(deviceId); + // Logging keyboard configuration data to statsd whenever input device is added. Currently + // only logging for New Settings UI where we are using IME to decide the layout information. + onInputDeviceChangedInternal(deviceId, true /* shouldLogConfiguration */); } @Override @@ -182,6 +184,10 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { @Override @MainThread public void onInputDeviceChanged(int deviceId) { + onInputDeviceChangedInternal(deviceId, false /* shouldLogConfiguration */); + } + + private void onInputDeviceChangedInternal(int deviceId, boolean shouldLogConfiguration) { final InputDevice inputDevice = getInputDevice(deviceId); if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) { return; @@ -243,18 +249,15 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { synchronized (mDataStore) { try { final String key = keyboardIdentifier.toString(); - boolean isFirstConfiguration = !mDataStore.hasInputDeviceEntry(key); if (mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) { // Need to show the notification only if layout selection changed // from the previous configuration needToShowNotification = true; + } - // Logging keyboard configuration data to statsd only if the - // configuration changed from the previous configuration. Currently - // only logging for New Settings UI where we are using IME to decide - // the layout information. + if (shouldLogConfiguration) { logKeyboardConfigurationEvent(inputDevice, imeInfoList, layoutInfoList, - isFirstConfiguration); + !mDataStore.hasInputDeviceEntry(key)); } } finally { mDataStore.saveIfNeeded(); diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java index 9ad4628596fc..2e0274b79d1f 100644 --- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java +++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java @@ -23,6 +23,7 @@ import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_EXP import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_FORCED; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; +import static android.view.MotionEvent.TOOL_TYPE_UNKNOWN; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED; @@ -44,6 +45,7 @@ import android.util.PrintWriterPrinter; import android.util.Printer; import android.util.Slog; import android.util.proto.ProtoOutputStream; +import android.view.MotionEvent; import android.view.WindowManager; import android.view.inputmethod.ImeTracker; import android.view.inputmethod.InputMethod; @@ -351,7 +353,8 @@ public final class ImeVisibilityStateComputer { void setWindowState(IBinder windowToken, @NonNull ImeTargetWindowState newState) { final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken); - if (state != null && newState.hasEditorFocused()) { + if (state != null && newState.hasEditorFocused() + && newState.mToolType != MotionEvent.TOOL_TYPE_STYLUS) { // Inherit the last requested IME visible state when the target window is still // focused with an editor. newState.setRequestedImeVisible(state.mRequestedImeVisible); @@ -652,14 +655,23 @@ public final class ImeVisibilityStateComputer { * A class that represents the current state of the IME target window. */ static class ImeTargetWindowState { + ImeTargetWindowState(@SoftInputModeFlags int softInputModeState, int windowFlags, boolean imeFocusChanged, boolean hasFocusedEditor, boolean isStartInputByGainFocus) { + this(softInputModeState, windowFlags, imeFocusChanged, hasFocusedEditor, + isStartInputByGainFocus, TOOL_TYPE_UNKNOWN); + } + + ImeTargetWindowState(@SoftInputModeFlags int softInputModeState, int windowFlags, + boolean imeFocusChanged, boolean hasFocusedEditor, + boolean isStartInputByGainFocus, @MotionEvent.ToolType int toolType) { mSoftInputModeState = softInputModeState; mWindowFlags = windowFlags; mImeFocusChanged = imeFocusChanged; mHasFocusedEditor = hasFocusedEditor; mIsStartInputByGainFocus = isStartInputByGainFocus; + mToolType = toolType; } /** @@ -670,6 +682,11 @@ public final class ImeVisibilityStateComputer { private final int mWindowFlags; /** + * {@link MotionEvent#getToolType(int)} that was used to click editor. + */ + private final int mToolType; + + /** * {@code true} means the IME focus changed from the previous window, {@code false} * otherwise. */ @@ -718,6 +735,10 @@ public final class ImeVisibilityStateComputer { return mWindowFlags; } + int getToolType() { + return mToolType; + } + private void setImeDisplayId(int imeDisplayId) { mImeDisplayId = imeDisplayId; } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java index 1d222e58c8fd..20c7029b77cb 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java @@ -40,6 +40,7 @@ import android.util.Slog; import android.view.WindowManager; import android.view.inputmethod.InputMethod; import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -295,7 +296,12 @@ final class InputMethodBindingController { } if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken); final InputMethodInfo info = mMethodMap.get(mSelectedMethodId); + boolean supportsStylusHwChanged = + mSupportsStylusHw != info.supportsStylusHandwriting(); mSupportsStylusHw = info.supportsStylusHandwriting(); + if (supportsStylusHwChanged) { + InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches(); + } mService.initializeImeLocked(mCurMethod, mCurToken); mService.scheduleNotifyImeUidToAudioService(mCurMethodUid); mService.reRequestCurrentClientSessionLocked(); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 4ce7e2448487..2fc48294ae70 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -2315,8 +2315,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mCurClient = null; ImeTracker.forLogging().onFailed(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME); mCurStatsToken = null; - InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches(); - mMenuController.hideInputMethodMenuLocked(); } } @@ -3794,11 +3792,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0; final boolean startInputByWinGainedFocus = (startInputFlags & StartInputFlags.WINDOW_GAINED_FOCUS) != 0; + final int toolType = editorInfo != null + ? editorInfo.getInitialToolType() : MotionEvent.TOOL_TYPE_UNKNOWN; // Init the focused window state (e.g. whether the editor has focused or IME focus has // changed from another window). - final ImeTargetWindowState windowState = new ImeTargetWindowState(softInputMode, - windowFlags, !sameWindowFocused, isTextEditor, startInputByWinGainedFocus); + final ImeTargetWindowState windowState = new ImeTargetWindowState( + softInputMode, windowFlags, !sameWindowFocused, isTextEditor, + startInputByWinGainedFocus, toolType); mVisibilityStateComputer.setWindowState(windowToken, windowState); if (sameWindowFocused && isTextEditor) { diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index 595b2e4113b2..74b7f0866b54 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -1750,13 +1750,6 @@ public class LocationManagerService extends ILocationManager.Stub implements } @Override - public void sendNiResponse(int notifId, int userResponse) { - if (mGnssManagerService != null) { - mGnssManagerService.sendNiResponse(notifId, userResponse); - } - } - - @Override public @Nullable LocationTime getGnssTimeMillis() { LocationProviderManager gpsManager = getLocationProviderManager(GPS_PROVIDER); if (gpsManager == null) { diff --git a/services/core/java/com/android/server/location/TEST_MAPPING b/services/core/java/com/android/server/location/TEST_MAPPING index 214d2f3f5646..f5deb2ba3e07 100644 --- a/services/core/java/com/android/server/location/TEST_MAPPING +++ b/services/core/java/com/android/server/location/TEST_MAPPING @@ -1,7 +1,13 @@ { "presubmit": [ { - "name": "CtsLocationFineTestCases" + "name": "CtsLocationFineTestCases", + "options": [ + { + // TODO: Wait for test to deflake - b/293934372 + "exclude-filter":"android.location.cts.fine.ScanningSettingsTest" + } + ] }, { "name": "CtsLocationCoarseTestCases" @@ -16,4 +22,4 @@ }] } ] -}
\ No newline at end of file +} diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java index ed5c1306733e..e97a12a83b1f 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -62,7 +62,6 @@ import android.content.pm.PackageManager; import android.database.ContentObserver; import android.location.GnssCapabilities; import android.location.GnssStatus; -import android.location.INetInitiatedListener; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; @@ -109,7 +108,6 @@ import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IBatteryStats; import com.android.internal.location.GpsNetInitiatedHandler; -import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.HexDump; import com.android.server.FgThread; @@ -396,7 +394,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements mC2KServerPort = mGnssConfiguration.getC2KPort(TCP_MIN_PORT); mNIHandler.setEmergencyExtensionSeconds(mGnssConfiguration.getEsExtensionSec()); mSuplEsEnabled = mGnssConfiguration.getSuplEs(0) == 1; - mNIHandler.setSuplEsEnabled(mSuplEsEnabled); if (mGnssVisibilityControl != null) { mGnssVisibilityControl.onConfigurationUpdated(mGnssConfiguration); } @@ -465,7 +462,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } }; mNIHandler = new GpsNetInitiatedHandler(context, - mNetInitiatedListener, emergencyCallCallback, mSuplEsEnabled); // Trigger PSDS data download when the network comes up after booting. @@ -1435,96 +1431,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements updateRequirements(); } - //============================================================= - // NI Client support - //============================================================= - private final INetInitiatedListener mNetInitiatedListener = new INetInitiatedListener.Stub() { - // Sends a response for an NI request to HAL. - @Override - public boolean sendNiResponse(int notificationId, int userResponse) { - // TODO Add Permission check - - if (DEBUG) { - Log.d(TAG, "sendNiResponse, notifId: " + notificationId - + ", response: " + userResponse); - } - mGnssNative.sendNiResponse(notificationId, userResponse); - - FrameworkStatsLog.write(FrameworkStatsLog.GNSS_NI_EVENT_REPORTED, - FrameworkStatsLog.GNSS_NI_EVENT_REPORTED__EVENT_TYPE__NI_RESPONSE, - notificationId, - /* niType= */ 0, - /* needNotify= */ false, - /* needVerify= */ false, - /* privacyOverride= */ false, - /* timeout= */ 0, - /* defaultResponse= */ 0, - /* requestorId= */ null, - /* text= */ null, - /* requestorIdEncoding= */ 0, - /* textEncoding= */ 0, - mSuplEsEnabled, - isGpsEnabled(), - userResponse); - - return true; - } - }; - - public INetInitiatedListener getNetInitiatedListener() { - return mNetInitiatedListener; - } - - /** Reports a NI notification. */ - private void reportNiNotification(int notificationId, int niType, int notifyFlags, int timeout, - int defaultResponse, String requestorId, String text, int requestorIdEncoding, - int textEncoding) { - Log.i(TAG, "reportNiNotification: entered"); - Log.i(TAG, "notificationId: " + notificationId - + ", niType: " + niType - + ", notifyFlags: " + notifyFlags - + ", timeout: " + timeout - + ", defaultResponse: " + defaultResponse); - - Log.i(TAG, "requestorId: " + requestorId - + ", text: " + text - + ", requestorIdEncoding: " + requestorIdEncoding - + ", textEncoding: " + textEncoding); - - GpsNiNotification notification = new GpsNiNotification(); - - notification.notificationId = notificationId; - notification.niType = niType; - notification.needNotify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_NOTIFY) != 0; - notification.needVerify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_VERIFY) != 0; - notification.privacyOverride = - (notifyFlags & GpsNetInitiatedHandler.GPS_NI_PRIVACY_OVERRIDE) != 0; - notification.timeout = timeout; - notification.defaultResponse = defaultResponse; - notification.requestorId = requestorId; - notification.text = text; - notification.requestorIdEncoding = requestorIdEncoding; - notification.textEncoding = textEncoding; - - mNIHandler.handleNiNotification(notification); - FrameworkStatsLog.write(FrameworkStatsLog.GNSS_NI_EVENT_REPORTED, - FrameworkStatsLog.GNSS_NI_EVENT_REPORTED__EVENT_TYPE__NI_REQUEST, - notification.notificationId, - notification.niType, - notification.needNotify, - notification.needVerify, - notification.privacyOverride, - notification.timeout, - notification.defaultResponse, - notification.requestorId, - notification.text, - notification.requestorIdEncoding, - notification.textEncoding, - mSuplEsEnabled, - isGpsEnabled(), - /* userResponse= */ 0); - } - private void demandUtcTimeInjection() { if (DEBUG) Log.d(TAG, "demandUtcTimeInjection"); postWithWakeLockHeld(mNetworkTimeHelper::demandUtcTimeInjection); @@ -1829,14 +1735,6 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } @Override - public void onReportNiNotification(int notificationId, int niType, int notifyFlags, - int timeout, int defaultResponse, String requestorId, String text, - int requestorIdEncoding, int textEncoding) { - reportNiNotification(notificationId, niType, notifyFlags, timeout, - defaultResponse, requestorId, text, requestorIdEncoding, textEncoding); - } - - @Override public void onRequestSetID(@GnssNative.AGpsCallbacks.AgpsSetIdFlags int flags) { TelephonyManager phone = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java index c962bc4c20d8..133704d11b08 100644 --- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java +++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java @@ -38,7 +38,6 @@ import android.location.LocationManager; import android.location.util.identity.CallerIdentity; import android.os.BatteryStats; import android.os.Binder; -import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.util.IndentingPrintWriter; @@ -275,17 +274,6 @@ public class GnssManagerService { } /** - * Send Ni Response, indicating a location request initiated by a network carrier. - */ - public void sendNiResponse(int notifId, int userResponse) { - try { - mGnssLocationProvider.getNetInitiatedListener().sendNiResponse(notifId, userResponse); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** * Dump info for debugging. */ public void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) { diff --git a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java index 7618419ab0e4..bdd488581817 100644 --- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java +++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java @@ -284,9 +284,6 @@ public class GnssNative { /** Callbacks for notifications. */ public interface NotificationCallbacks { - void onReportNiNotification(int notificationId, int niType, int notifyFlags, - int timeout, int defaultResponse, String requestorId, String text, - int requestorIdEncoding, int textEncoding); void onReportNfwNotification(String proxyAppPackageName, byte protocolStack, String otherProtocolStackName, byte requestor, String requestorId, byte responseType, boolean inEmergencyMode, boolean isCachedLocation); @@ -933,14 +930,6 @@ public class GnssNative { } /** - * Send a network initiated respnse. - */ - public void sendNiResponse(int notificationId, int userResponse) { - Preconditions.checkState(mRegistered); - mGnssHal.sendNiResponse(notificationId, userResponse); - } - - /** * Request an eventual update of GNSS power statistics. */ public void requestPowerStats() { @@ -1244,16 +1233,6 @@ public class GnssNative { } @NativeEntryPoint - void reportNiNotification(int notificationId, int niType, int notifyFlags, - int timeout, int defaultResponse, String requestorId, String text, - int requestorIdEncoding, int textEncoding) { - Binder.withCleanCallingIdentity( - () -> mNotificationCallbacks.onReportNiNotification(notificationId, niType, - notifyFlags, timeout, defaultResponse, requestorId, text, - requestorIdEncoding, textEncoding)); - } - - @NativeEntryPoint void requestSetID(int flags) { Binder.withCleanCallingIdentity(() -> mAGpsCallbacks.onRequestSetID(flags)); } @@ -1488,10 +1467,6 @@ public class GnssNative { return native_is_gnss_visibility_control_supported(); } - protected void sendNiResponse(int notificationId, int userResponse) { - native_send_ni_response(notificationId, userResponse); - } - protected void requestPowerStats() { native_request_power_stats(); } @@ -1648,8 +1623,6 @@ public class GnssNative { private static native boolean native_is_gnss_visibility_control_supported(); - private static native void native_send_ni_response(int notificationId, int userResponse); - // power stats APIs private static native void native_request_power_stats(); diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java index 06db6b8a38f8..ef56a1e26854 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java @@ -138,7 +138,7 @@ public class ApplicationKeyStorage { } catch (android.security.KeyStoreException e) { if (e.getNumericErrorCode() == android.security.KeyStoreException.ERROR_KEY_DOES_NOT_EXIST) { - Log.e(TAG, "Failed to get grant for KeyStore key - key not found", e); + Log.w(TAG, "Failed to get grant for KeyStore key - key not found"); throw new ServiceSpecificException(ERROR_KEY_NOT_FOUND, e.getMessage()); } Log.e(TAG, "Failed to get grant for KeyStore key.", e); diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java index 446c4f7e335a..899287886009 100644 --- a/services/core/java/com/android/server/notification/NotificationComparator.java +++ b/services/core/java/com/android/server/notification/NotificationComparator.java @@ -25,7 +25,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.telecom.TelecomManager; -import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; import com.android.internal.util.NotificationMessagingUtil; import java.util.Comparator; @@ -39,7 +38,6 @@ public class NotificationComparator private final Context mContext; private final NotificationMessagingUtil mMessagingUtil; - private final boolean mSortByInterruptiveness; private String mDefaultPhoneApp; public NotificationComparator(Context context) { @@ -47,8 +45,6 @@ public class NotificationComparator mContext.registerReceiver(mPhoneAppBroadcastReceiver, new IntentFilter(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED)); mMessagingUtil = new NotificationMessagingUtil(mContext); - mSortByInterruptiveness = !SystemUiSystemPropertiesFlags.getResolver().isEnabled( - SystemUiSystemPropertiesFlags.NotificationFlags.NO_SORT_BY_INTERRUPTIVENESS); } @Override @@ -139,14 +135,6 @@ public class NotificationComparator return -1 * Integer.compare(leftPriority, rightPriority); } - if (mSortByInterruptiveness) { - final boolean leftInterruptive = left.isInterruptive(); - final boolean rightInterruptive = right.isInterruptive(); - if (leftInterruptive != rightInterruptive) { - return -1 * Boolean.compare(leftInterruptive, rightInterruptive); - } - } - // then break ties by time, most recent first return -1 * Long.compare(left.getRankingTimeMs(), right.getRankingTimeMs()); } diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 1b5272514b63..dd3604eb02a5 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -1519,6 +1519,7 @@ public class ComputerEngine implements Computer { ai.privateFlags = ps.getPrivateFlags(); pi.applicationInfo = PackageInfoUtils.generateDelegateApplicationInfo( ai, flags, state, userId); + pi.signingInfo = ps.getSigningInfo(); if (DEBUG_PACKAGE_INFO) { Log.v(TAG, "ps.pkg is n/a for [" diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index a5a4594ce6ac..b5ec1366fec6 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -470,7 +470,6 @@ final class DeletePackageHelper { // We need to set it back to 'installed' so the uninstall // broadcasts will be sent correctly. if (DEBUG_REMOVE) Slog.d(TAG, "Not installed by other users, full delete"); - ps.setPkg(null); ps.setInstalled(true, userId); mPm.mSettings.writeKernelMappingLPr(ps); clearPackageStateAndReturn = false; diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 7e1560ada784..31869b8ea0b5 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -2417,7 +2417,8 @@ final class InstallPackageHelper { final InstallRequest installRequest = reconciledPkg.mInstallRequest; final boolean instantApp = ((installRequest.getScanFlags() & SCAN_AS_INSTANT_APP) != 0); final boolean isApex = ((installRequest.getScanFlags() & SCAN_AS_APEX) != 0); - final AndroidPackage pkg = installRequest.getScannedPackageSetting().getPkg(); + final PackageSetting ps = installRequest.getScannedPackageSetting(); + final AndroidPackage pkg = ps.getPkg(); final String packageName = pkg.getPackageName(); final String codePath = pkg.getPath(); final boolean onIncremental = mIncrementalManager != null @@ -2517,7 +2518,7 @@ final class InstallPackageHelper { // Compile the layout resources. if (SystemProperties.getBoolean(PRECOMPILE_LAYOUTS, false)) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "compileLayouts"); - mViewCompiler.compileLayouts(pkg); + mViewCompiler.compileLayouts(ps, pkg.getBaseApkPath()); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 12f3aa9780ea..e73eced77f2a 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -86,7 +86,10 @@ import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.os.ResultReceiver; import android.os.ServiceManager; +import android.os.ShellCallback; +import android.os.ShellCommand; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; @@ -127,6 +130,9 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.ExecutionException; +import java.util.function.BiConsumer; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; /** * Service that manages requests and callbacks for launchers that support @@ -215,7 +221,8 @@ public class LauncherAppsService extends SystemService { final LauncherAppsServiceInternal mInternal; - private RemoteCallbackList<IDumpCallback> mDumpCallbacks = new RemoteCallbackList<>(); + @NonNull + private final RemoteCallbackList<IDumpCallback> mDumpCallbacks = new RemoteCallbackList<>(); public LauncherAppsImpl(Context context) { mContext = context; @@ -1462,46 +1469,124 @@ public class LauncherAppsService extends SystemService { getActivityOptionsForLauncher(opts), user.getIdentifier()); } + @Override + public void onShellCommand(FileDescriptor in, @NonNull FileDescriptor out, + @NonNull FileDescriptor err, @Nullable String[] args, ShellCallback cb, + @Nullable ResultReceiver receiver) { + final int callingUid = injectBinderCallingUid(); + if (!(callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID)) { + throw new SecurityException("Caller must be shell"); + } + + final long token = injectClearCallingIdentity(); + try { + int status = (new LauncherAppsShellCommand()) + .exec(this, in, out, err, args, cb, receiver); + if (receiver != null) { + receiver.send(status, null); + } + } finally { + injectRestoreCallingIdentity(token); + } + } + + /** Handles Shell commands for LauncherAppsService */ + private class LauncherAppsShellCommand extends ShellCommand { + @Override + public int onCommand(@Nullable String cmd) { + if ("dump-view-hierarchies".equals(cmd)) { + dumpViewCaptureDataToShell(); + return 0; + } else { + return handleDefaultCommands(cmd); + } + } + + private void dumpViewCaptureDataToShell() { + try (ZipOutputStream zipOs = new ZipOutputStream(getRawOutputStream())) { + forEachViewCaptureWindow((fileName, is) -> { + try { + zipOs.putNextEntry(new ZipEntry("FS" + fileName)); + is.transferTo(zipOs); + zipOs.closeEntry(); + } catch (IOException e) { + getErrPrintWriter().write("Failed to output " + fileName + + " data to shell: " + e.getMessage()); + } + }); + } catch (IOException e) { + getErrPrintWriter().write("Failed to create or close zip output stream: " + + e.getMessage()); + } + } + + @Override + public void onHelp() { + final PrintWriter pw = getOutPrintWriter(); + pw.println("Usage: cmd launcherapps COMMAND [options ...]"); + pw.println(); + pw.println("cmd launcherapps dump-view-hierarchies"); + pw.println(" Output captured view hierarchies. Files will be generated in "); + pw.println(" `" + WM_TRACE_DIR + "`. After pulling the data to your device,"); + pw.println(" you can upload / visualize it at `go/winscope`."); + pw.println(); + } + } /** * Using a pipe, outputs view capture data to the wmtrace dir */ - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, + @Nullable String[] args) { super.dump(fd, pw, args); // Before the wmtrace directory is picked up by dumpstate service, some processes need // to write their data to that location. They can do that via these dumpCallbacks. - int i = mDumpCallbacks.beginBroadcast(); - while (i > 0) { - i--; - dumpDataToWmTrace((String) mDumpCallbacks.getBroadcastCookie(i) + "_" + i, - mDumpCallbacks.getBroadcastItem(i)); - } - mDumpCallbacks.finishBroadcast(); + forEachViewCaptureWindow(this::dumpViewCaptureDataToWmTrace); } - private void dumpDataToWmTrace(String name, IDumpCallback cb) { - ParcelFileDescriptor[] pipe; + private void dumpViewCaptureDataToWmTrace(@NonNull String fileName, + @NonNull InputStream is) { + Path outPath = Paths.get(fileName); try { - pipe = ParcelFileDescriptor.createPipe(); - cb.onDump(pipe[1]); - } catch (IOException | RemoteException e) { - Log.d(TAG, "failed to pipe view capture data", e); - return; + Files.copy(is, outPath, StandardCopyOption.REPLACE_EXISTING); + Files.setPosixFilePermissions(outPath, WM_TRACE_FILE_PERMISSIONS); + } catch (IOException e) { + Log.d(TAG, "failed to write data to " + fileName + " in wmtrace dir", e); } + } - Path path = Paths.get(WM_TRACE_DIR + Paths.get(name + VC_FILE_SUFFIX).getFileName()); - try (InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pipe[0])) { - Files.copy(is, path, StandardCopyOption.REPLACE_EXISTING); - Files.setPosixFilePermissions(path, WM_TRACE_FILE_PERMISSIONS); - } catch (IOException e) { - Log.d(TAG, "failed to write data to file in wmtrace dir", e); + /** + * IDumpCallback.onDump alerts the in-process ViewCapture instance to start sending data + * to LauncherAppsService via the pipe's input provided. This data (as well as an output + * file name) is provided to the consumer via an InputStream to output where it wants (for + * example, the winscope trace directory or the shell's stdout). + */ + private void forEachViewCaptureWindow( + @NonNull BiConsumer<String, InputStream> outputtingConsumer) { + for (int i = mDumpCallbacks.beginBroadcast() - 1; i >= 0; i--) { + String packageName = (String) mDumpCallbacks.getBroadcastCookie(i); + String fileName = WM_TRACE_DIR + packageName + "_" + i + VC_FILE_SUFFIX; + + try { + // Order is important here. OnDump needs to be called before the BiConsumer + // accepts & starts blocking on reading the input stream. + ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); + mDumpCallbacks.getBroadcastItem(i).onDump(pipe[1]); + + InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pipe[0]); + outputtingConsumer.accept(fileName, is); + is.close(); + } catch (Exception e) { + Log.d(TAG, "failed to pipe view capture data", e); + } } + mDumpCallbacks.finishBroadcast(); } @RequiresPermission(READ_FRAME_BUFFER) @Override - public void registerDumpCallback(IDumpCallback cb) { + public void registerDumpCallback(@NonNull IDumpCallback cb) { int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER); if (PERMISSION_GRANTED == status) { String name = mContext.getPackageManager().getNameForUid(Binder.getCallingUid()); @@ -1513,7 +1598,7 @@ public class LauncherAppsService extends SystemService { @RequiresPermission(READ_FRAME_BUFFER) @Override - public void unRegisterDumpCallback(IDumpCallback cb) { + public void unRegisterDumpCallback(@NonNull IDumpCallback cb) { int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER); if (PERMISSION_GRANTED == status) { mDumpCallbacks.unregister(cb); diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java index 4e7521070132..82587c529e29 100644 --- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java +++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java @@ -745,6 +745,13 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal { mService.setPackageStoppedState(snapshot(), packageName, stopped, userId); } + @Override + public void notifyComponentUsed(@NonNull String packageName, + @UserIdInt int userId, @NonNull String recentCallingPackage) { + mService.notifyComponentUsed(snapshot(), packageName, userId, + recentCallingPackage); + } + @NonNull @Override @Deprecated diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 770ed8b40d2e..dc42644b04c0 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -4582,6 +4582,13 @@ public class PackageManagerService implements PackageSender, TestUtilityService } } + void notifyComponentUsed(@NonNull Computer snapshot, @NonNull String packageName, + @UserIdInt int userId, @NonNull String recentCallingPackage) { + PackageManagerService.this + .setPackageStoppedState(snapshot, packageName, false /* stopped */, + userId); + } + public class IPackageManagerImpl extends IPackageManagerBase { public IPackageManagerImpl() { diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index cf4d1b0439d4..dee31ec460d7 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -20,6 +20,7 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -80,9 +81,38 @@ import java.util.UUID; * @hide */ @DataClass(genGetters = true, genConstructor = false, genSetters = false, genBuilder = false) -@DataClass.Suppress({"getSnapshot", }) +@DataClass.Suppress({"getSnapshot", "getBooleans"}) public class PackageSetting extends SettingBase implements PackageStateInternal { + // Use a bitset to store boolean data to save memory + private static class Booleans { + @IntDef({ + INSTALL_PERMISSION_FIXED, + DEFAULT_TO_DEVICE_PROTECTED_STORAGE, + UPDATE_AVAILABLE, + FORCE_QUERYABLE_OVERRIDE + }) + public @interface Flags { + } + private static final int INSTALL_PERMISSION_FIXED = 1; + private static final int DEFAULT_TO_DEVICE_PROTECTED_STORAGE = 1 << 1; + private static final int UPDATE_AVAILABLE = 1 << 2; + private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 3; + } + private int mBooleans; + + private void setBoolean(@Booleans.Flags int flag, boolean value) { + if (value) { + mBooleans |= flag; + } else { + mBooleans &= ~flag; + } + } + + private boolean getBoolean(@Booleans.Flags int flag) { + return (mBooleans & flag) != 0; + } + /** * The shared user ID lets us link this object to {@link SharedUserSetting}. */ @@ -160,8 +190,6 @@ public class PackageSetting extends SettingBase implements PackageStateInternal @NonNull private PackageSignatures signatures; - private boolean installPermissionsFixed; - @NonNull private PackageKeySetData keySetData = new PackageKeySetData(); @@ -179,11 +207,6 @@ public class PackageSetting extends SettingBase implements PackageStateInternal /** @see PackageState#getCategoryOverride() */ private int categoryOverride = ApplicationInfo.CATEGORY_UNDEFINED; - /** @see PackageState#isUpdateAvailable() */ - private boolean updateAvailable; - - private boolean forceQueryableOverride; - @NonNull private final PackageStateUnserialized pkgState = new PackageStateUnserialized(this); @@ -259,7 +282,8 @@ public class PackageSetting extends SettingBase implements PackageStateInternal this.mRealName = realPkgName; } - PackageSetting(@NonNull PackageSetting original, boolean sealedSnapshot) { + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public PackageSetting(@NonNull PackageSetting original, boolean sealedSnapshot) { super(original); copyPackageSetting(original, sealedSnapshot); if (sealedSnapshot) { @@ -363,7 +387,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal } public PackageSetting setForceQueryableOverride(boolean forceQueryableOverride) { - this.forceQueryableOverride = forceQueryableOverride; + setBoolean(Booleans.FORCE_QUERYABLE_OVERRIDE, forceQueryableOverride); onChanged(); return this; } @@ -487,13 +511,20 @@ public class PackageSetting extends SettingBase implements PackageStateInternal return this; } + public PackageSetting setDefaultToDeviceProtectedStorage( + boolean defaultToDeviceProtectedStorage) { + setBoolean(Booleans.DEFAULT_TO_DEVICE_PROTECTED_STORAGE, defaultToDeviceProtectedStorage); + onChanged(); + return this; + } + @Override public boolean isExternalStorage() { return (getFlags() & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0; } public PackageSetting setUpdateAvailable(boolean updateAvailable) { - this.updateAvailable = updateAvailable; + setBoolean(Booleans.UPDATE_AVAILABLE, updateAvailable); onChanged(); return this; } @@ -585,7 +616,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal } public PackageSetting setInstallPermissionsFixed(boolean installPermissionsFixed) { - this.installPermissionsFixed = installPermissionsFixed; + setBoolean(Booleans.INSTALL_PERMISSION_FIXED, installPermissionsFixed); return this; } @@ -635,6 +666,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal public void copyPackageSetting(PackageSetting other, boolean sealedSnapshot) { super.copySettingBase(other); + mBooleans = other.mBooleans; mSharedUserAppId = other.mSharedUserAppId; mLoadingProgress = other.mLoadingProgress; mLoadingCompletedTime = other.mLoadingCompletedTime; @@ -652,13 +684,10 @@ public class PackageSetting extends SettingBase implements PackageStateInternal lastUpdateTime = other.lastUpdateTime; versionCode = other.versionCode; signatures = other.signatures; - installPermissionsFixed = other.installPermissionsFixed; keySetData = new PackageKeySetData(other.keySetData); installSource = other.installSource; volumeUuid = other.volumeUuid; categoryOverride = other.categoryOverride; - updateAvailable = other.updateAvailable; - forceQueryableOverride = other.forceQueryableOverride; mDomainSetId = other.mDomainSetId; mAppMetadataFilePath = other.mAppMetadataFilePath; @@ -1282,7 +1311,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal @NonNull @Override public List<SharedLibrary> getSharedLibraryDependencies() { - return (List<SharedLibrary>) (List<?>) pkgState.getUsesLibraryInfos(); + return Collections.unmodifiableList(pkgState.getUsesLibraryInfos()); } @NonNull @@ -1294,7 +1323,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal @NonNull @Override public List<String> getUsesLibraryFiles() { - return pkgState.getUsesLibraryFiles(); + return Collections.unmodifiableList(pkgState.getUsesLibraryFiles()); } @NonNull @@ -1474,6 +1503,32 @@ public class PackageSetting extends SettingBase implements PackageStateInternal return getAndroidPackage() != null && getAndroidPackage().isApex(); } + @Override + public boolean isForceQueryableOverride() { + return getBoolean(Booleans.FORCE_QUERYABLE_OVERRIDE); + } + + /** + * @see PackageState#isUpdateAvailable() + */ + @Override + public boolean isUpdateAvailable() { + return getBoolean(Booleans.UPDATE_AVAILABLE); + } + + @Override + public boolean isInstallPermissionsFixed() { + return getBoolean(Booleans.INSTALL_PERMISSION_FIXED); + } + + /** + * @see PackageState#isDefaultToDeviceProtectedStorage() + */ + @Override + public boolean isDefaultToDeviceProtectedStorage() { + return getBoolean(Booleans.DEFAULT_TO_DEVICE_PROTECTED_STORAGE); + } + // Code below generated by codegen v1.0.23. @@ -1576,11 +1631,6 @@ public class PackageSetting extends SettingBase implements PackageStateInternal } @DataClass.Generated.Member - public boolean isInstallPermissionsFixed() { - return installPermissionsFixed; - } - - @DataClass.Generated.Member public @NonNull PackageKeySetData getKeySetData() { return keySetData; } @@ -1606,19 +1656,6 @@ public class PackageSetting extends SettingBase implements PackageStateInternal return categoryOverride; } - /** - * @see PackageState#isUpdateAvailable() - */ - @DataClass.Generated.Member - public boolean isUpdateAvailable() { - return updateAvailable; - } - - @DataClass.Generated.Member - public boolean isForceQueryableOverride() { - return forceQueryableOverride; - } - @DataClass.Generated.Member public @NonNull PackageStateUnserialized getPkgState() { return pkgState; @@ -1635,10 +1672,10 @@ public class PackageSetting extends SettingBase implements PackageStateInternal } @DataClass.Generated( - time = 1688743336932L, + time = 1691185420362L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java", - inputSignatures = "private int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate boolean updateAvailable\nprivate boolean forceQueryableOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isAnyInstalled(int[])\n int[] queryInstalledUsers(int[],boolean)\n long getCeDataInode(int)\n void setCeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,com.android.server.pm.pkg.ArchiveState)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)") + inputSignatures = "private int mBooleans\nprivate int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic com.android.server.pm.PackageSetting setDefaultToDeviceProtectedStorage(boolean)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isAnyInstalled(int[])\n int[] queryInstalledUsers(int[],boolean)\n long getCeDataInode(int)\n void setCeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final int INSTALL_PERMISSION_FIXED\nprivate static final int DEFAULT_TO_DEVICE_PROTECTED_STORAGE\nprivate static final int UPDATE_AVAILABLE\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index 59314a26ab97..6d3b26cc2fd4 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -135,6 +135,7 @@ final class RemovePackageHelper { cacher.cleanCachedResult(codePath); } + // Used for system apps only public void removePackage(AndroidPackage pkg, boolean chatty) { synchronized (mPm.mInstallLock) { removePackageLI(pkg, chatty); @@ -284,6 +285,13 @@ final class RemovePackageHelper { } removePackageLI(deletedPs.getPackageName(), (flags & PackageManager.DELETE_CHATTY) != 0); + if (!deletedPs.isSystem()) { + // A non-system app's AndroidPackage object has been removed from the service. + // Explicitly nullify the corresponding app's PackageSetting's pkg object to + // prevent any future usage of it, in case the PackageSetting object will remain because + // of DELETE_KEEP_DATA. + deletedPs.setPkg(null); + } if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) { final AndroidPackage resolvedPkg; diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java index 1cd44e62e1f5..f4dca3fe064b 100644 --- a/services/core/java/com/android/server/pm/ScanPackageUtils.java +++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java @@ -462,6 +462,8 @@ final class ScanPackageUtils { + " to " + volumeUuid); pkgSetting.setVolumeUuid(volumeUuid); } + pkgSetting.setDefaultToDeviceProtectedStorage( + parsedPackage.isDefaultToDeviceProtectedStorage()); SharedLibraryInfo sdkLibraryInfo = null; if (!TextUtils.isEmpty(parsedPackage.getSdkLibraryName())) { diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index e5ad01f9880e..c6135e0f582e 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -2914,29 +2914,28 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile FileUtils.setPermissions(fstr.getFD(), 0640, SYSTEM_UID, PACKAGE_INFO_GID); StringBuilder sb = new StringBuilder(); - for (final PackageSetting pkg : mPackages.values()) { + for (final PackageSetting ps : mPackages.values()) { // TODO(b/135203078): This doesn't handle multiple users - final String dataPath = pkg.getPkg() == null ? null : - PackageInfoUtils.getDataDir(pkg.getPkg(), UserHandle.USER_SYSTEM) - .getAbsolutePath(); + final String dataPath = PackageInfoUtils.getDataDir(ps, UserHandle.USER_SYSTEM) + .getAbsolutePath(); - if (pkg.getPkg() == null || dataPath == null) { - if (!"android".equals(pkg.getPackageName())) { - Slog.w(TAG, "Skipping " + pkg + " due to missing metadata"); + if (ps.getPkg() == null || dataPath == null) { + if (!"android".equals(ps.getPackageName())) { + Slog.w(TAG, "Skipping " + ps + " due to missing metadata"); } continue; } - if (pkg.getPkg().isApex()) { + if (ps.getPkg().isApex()) { // Don't persist APEX which doesn't have a valid app id and will cause parsing // error in libpackagelistparser continue; } - final boolean isDebug = pkg.getPkg().isDebuggable(); + final boolean isDebug = ps.getPkg().isDebuggable(); final IntArray gids = new IntArray(); for (final int userId : userIds) { gids.addAll(mPermissionDataProvider.getGidsForUid(UserHandle.getUid(userId, - pkg.getAppId()))); + ps.getAppId()))); } // Avoid any application that has a space in its path. @@ -2964,13 +2963,13 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile // system/core/libpackagelistparser // sb.setLength(0); - sb.append(pkg.getPkg().getPackageName()); + sb.append(ps.getPkg().getPackageName()); sb.append(" "); - sb.append(pkg.getPkg().getUid()); + sb.append(ps.getPkg().getUid()); sb.append(isDebug ? " 1 " : " 0 "); sb.append(dataPath); sb.append(" "); - sb.append(pkg.getSeInfo()); + sb.append(ps.getSeInfo()); sb.append(" "); final int gidsSize = gids.size(); if (gids != null && gids.size() > 0) { @@ -2983,19 +2982,19 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile sb.append("none"); } sb.append(" "); - sb.append(pkg.getPkg().isProfileableByShell() ? "1" : "0"); + sb.append(ps.getPkg().isProfileableByShell() ? "1" : "0"); sb.append(" "); - sb.append(pkg.getPkg().getLongVersionCode()); + sb.append(ps.getPkg().getLongVersionCode()); sb.append(" "); - sb.append(pkg.getPkg().isProfileable() ? "1" : "0"); + sb.append(ps.getPkg().isProfileable() ? "1" : "0"); sb.append(" "); - if (pkg.isSystem()) { + if (ps.isSystem()) { sb.append("@system"); - } else if (pkg.isProduct()) { + } else if (ps.isProduct()) { sb.append("@product"); - } else if (pkg.getInstallSource().mInstallerPackageName != null - && !pkg.getInstallSource().mInstallerPackageName.isEmpty()) { - sb.append(pkg.getInstallSource().mInstallerPackageName); + } else if (ps.getInstallSource().mInstallerPackageName != null + && !ps.getInstallSource().mInstallerPackageName.isEmpty()) { + sb.append(ps.getInstallSource().mInstallerPackageName); } else { sb.append("@null"); } @@ -3123,6 +3122,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile if (pkg.getVolumeUuid() != null) { serializer.attribute(null, "volumeUuid", pkg.getVolumeUuid()); } + serializer.attributeBoolean(null, "defaultToDeviceProtectedStorage", + pkg.isDefaultToDeviceProtectedStorage()); if (pkg.getCategoryOverride() != ApplicationInfo.CATEGORY_UNDEFINED) { serializer.attributeInt(null, "categoryHint", pkg.getCategoryOverride()); } @@ -3911,6 +3912,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile String installInitiatingPackageName = null; boolean installInitiatorUninstalled = false; String volumeUuid = null; + boolean defaultToDeviceProtectedStorage = false; boolean updateAvailable = false; int categoryHint = ApplicationInfo.CATEGORY_UNDEFINED; int pkgFlags = 0; @@ -3960,6 +3962,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile installInitiatorUninstalled = parser.getAttributeBoolean(null, "installInitiatorUninstalled", false); volumeUuid = parser.getAttributeValue(null, "volumeUuid"); + defaultToDeviceProtectedStorage = parser.getAttributeBoolean( + null, "defaultToDeviceProtectedStorage", false); categoryHint = parser.getAttributeInt(null, "categoryHint", ApplicationInfo.CATEGORY_UNDEFINED); appMetadataFilePath = parser.getAttributeValue(null, "appMetadataFilePath"); @@ -4099,6 +4103,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile installInitiatorUninstalled); packageSetting.setInstallSource(installSource) .setVolumeUuid(volumeUuid) + .setDefaultToDeviceProtectedStorage(defaultToDeviceProtectedStorage) .setCategoryOverride(categoryHint) .setLegacyNativeLibraryPath(legacyNativeLibraryPathStr) .setPrimaryCpuAbi(primaryCpuAbiString) @@ -4886,6 +4891,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile pw.print("]"); } pw.println(); + File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.myUserId()); + pw.print(prefix); pw.print(" dataDir="); pw.println(dataDir.getAbsolutePath()); if (pkg != null) { pw.print(prefix); pw.print(" versionName="); pw.println(pkg.getVersionName()); pw.print(prefix); pw.print(" usesNonSdkApi="); pw.println(pkg.isNonSdkApiRequested()); @@ -4917,8 +4924,6 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile pw.append(prefix).append(" queriesIntents=") .println(ps.getPkg().getQueriesIntents()); } - File dataDir = PackageInfoUtils.getDataDir(pkg, UserHandle.myUserId()); - pw.print(prefix); pw.print(" dataDir="); pw.println(dataDir.getAbsolutePath()); pw.print(prefix); pw.print(" supportsScreens=["); boolean first = true; if (pkg.isSmallScreensSupported()) { diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING index e2bbaffe5058..04d1da61f3cc 100644 --- a/services/core/java/com/android/server/pm/TEST_MAPPING +++ b/services/core/java/com/android/server/pm/TEST_MAPPING @@ -102,7 +102,9 @@ "include-filter": "android.appsecurity.cts.EphemeralTest#testGetSearchableInfo" } ] - }, + } + ], + "presubmit-large":[ { "name": "CtsPackageManagerTestCases", "options": [ @@ -111,6 +113,9 @@ }, { "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-filter": "android.content.pm.cts.PackageManagerShellCommandMultiUserTest" } ] } @@ -132,6 +137,14 @@ }, { "name": "CtsAppEnumerationTestCases" + }, + { + "name": "CtsPackageManagerTestCases", + "options": [ + { + "include-filter": "android.content.pm.cts.PackageManagerShellCommandMultiUserTest" + } + ] } ], "imports": [ diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java index d88b66b412e7..31856f1630bb 100644 --- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java +++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java @@ -61,7 +61,7 @@ import com.android.server.pm.PackageManagerServiceCompilerMapping; import com.android.server.pm.PackageManagerServiceUtils; import com.android.server.pm.parsing.PackageInfoUtils; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.PackageState; +import com.android.server.pm.pkg.PackageStateInternal; import dalvik.system.DexFile; import dalvik.system.VMRuntime; @@ -542,14 +542,14 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub { /** * Compile layout resources in a given package. */ - public boolean compileLayouts(@NonNull PackageState packageState, @NonNull AndroidPackage pkg) { + public boolean compileLayouts(@NonNull PackageStateInternal ps, @NonNull AndroidPackage pkg) { try { final String packageName = pkg.getPackageName(); final String apkPath = pkg.getSplits().get(0).getPath(); // TODO(b/143971007): Use a cross-user directory - File dataDir = PackageInfoUtils.getDataDir(pkg, UserHandle.myUserId()); + File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.myUserId()); final String outDexFile = dataDir.getAbsolutePath() + "/code_cache/compiled_view.dex"; - if (packageState.isPrivileged() || pkg.isUseEmbeddedDex() + if (ps.isPrivileged() || pkg.isUseEmbeddedDex() || pkg.isDefaultToDeviceProtectedStorage()) { // Privileged apps prefer to load trusted code so they don't use compiled views. // If the app is not privileged but prefers code integrity, also avoid compiling diff --git a/services/core/java/com/android/server/pm/dex/ViewCompiler.java b/services/core/java/com/android/server/pm/dex/ViewCompiler.java index 9ce648f12ffe..6405ea5667d3 100644 --- a/services/core/java/com/android/server/pm/dex/ViewCompiler.java +++ b/services/core/java/com/android/server/pm/dex/ViewCompiler.java @@ -23,7 +23,7 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.server.pm.Installer; import com.android.server.pm.parsing.PackageInfoUtils; -import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.PackageStateInternal; import java.io.File; @@ -37,12 +37,11 @@ public class ViewCompiler { mInstaller = installer; } - public boolean compileLayouts(AndroidPackage pkg) { + public boolean compileLayouts(PackageStateInternal ps, String apkPath) { try { - final String packageName = pkg.getPackageName(); - final String apkPath = pkg.getBaseApkPath(); + final String packageName = ps.getPackageName(); // TODO(b/143971007): Use a cross-user directory - File dataDir = PackageInfoUtils.getDataDir(pkg, UserHandle.myUserId()); + File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.myUserId()); final String outDexFile = dataDir.getAbsolutePath() + "/code_cache/compiled_view.dex"; Log.i("PackageManager", "Compiling layouts in " + packageName + " (" + apkPath + ") to " + outDexFile); @@ -50,7 +49,7 @@ public class ViewCompiler { try { synchronized (mInstallLock) { return mInstaller.compileLayouts(apkPath, packageName, outDexFile, - pkg.getUid()); + ps.getAppId()); } } finally { Binder.restoreCallingIdentity(callingId); diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index d55f85cde5af..94e959968179 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -1082,18 +1082,18 @@ public class PackageInfoUtils { } @NonNull - public static File getDataDir(AndroidPackage pkg, int userId) { - if ("android".equals(pkg.getPackageName())) { + public static File getDataDir(PackageStateInternal ps, int userId) { + if ("android".equals(ps.getPackageName())) { return Environment.getDataSystemDirectory(); } - if (pkg.isDefaultToDeviceProtectedStorage() + if (ps.isDefaultToDeviceProtectedStorage() && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) { - return Environment.getDataUserDePackageDirectory(pkg.getVolumeUuid(), userId, - pkg.getPackageName()); + return Environment.getDataUserDePackageDirectory(ps.getVolumeUuid(), userId, + ps.getPackageName()); } else { - return Environment.getDataUserCePackageDirectory(pkg.getVolumeUuid(), userId, - pkg.getPackageName()); + return Environment.getDataUserCePackageDirectory(ps.getVolumeUuid(), userId, + ps.getPackageName()); } } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 789719527c8d..b01a89e672be 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -72,7 +72,6 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; -import com.android.internal.util.function.QuadFunction; import com.android.internal.util.function.TriFunction; import com.android.server.LocalServices; import com.android.server.pm.UserManagerInternal; @@ -94,6 +93,7 @@ import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiFunction; /** * Manages all permissions and handles permissions related tasks. @@ -233,11 +233,11 @@ public class PermissionManagerService extends IPermissionManager.Stub { } if (checkPermissionDelegate == null) { - return mPermissionManagerServiceImpl.checkPermission(packageName, permissionName, - deviceId, userId); + return mPermissionManagerServiceImpl.checkPermission( + packageName, permissionName, userId); } - return checkPermissionDelegate.checkPermission(packageName, permissionName, - deviceId, userId, mPermissionManagerServiceImpl::checkPermission); + return checkPermissionDelegate.checkPermission(packageName, permissionName, userId, + mPermissionManagerServiceImpl::checkPermission); } @Override @@ -254,10 +254,10 @@ public class PermissionManagerService extends IPermissionManager.Stub { } if (checkPermissionDelegate == null) { - return mPermissionManagerServiceImpl.checkUidPermission(uid, permissionName, deviceId); + return mPermissionManagerServiceImpl.checkUidPermission(uid, permissionName); } return checkPermissionDelegate.checkUidPermission(uid, permissionName, - deviceId, mPermissionManagerServiceImpl::checkUidPermission); + mPermissionManagerServiceImpl::checkUidPermission); } @Override @@ -511,14 +511,14 @@ public class PermissionManagerService extends IPermissionManager.Stub { public int getPermissionFlags(String packageName, String permissionName, int deviceId, int userId) { return mPermissionManagerServiceImpl - .getPermissionFlags(packageName, permissionName, deviceId, userId); + .getPermissionFlags(packageName, permissionName, userId); } @Override public void updatePermissionFlags(String packageName, String permissionName, int flagMask, int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId, int userId) { mPermissionManagerServiceImpl.updatePermissionFlags(packageName, permissionName, flagMask, - flagValues, checkAdjustPolicyFlagPermission, deviceId, userId); + flagValues, checkAdjustPolicyFlagPermission, userId); } @Override @@ -560,15 +560,14 @@ public class PermissionManagerService extends IPermissionManager.Stub { @Override public void grantRuntimePermission(String packageName, String permissionName, int deviceId, int userId) { - mPermissionManagerServiceImpl.grantRuntimePermission(packageName, permissionName, - deviceId, userId); + mPermissionManagerServiceImpl.grantRuntimePermission(packageName, permissionName, userId); } @Override public void revokeRuntimePermission(String packageName, String permissionName, int deviceId, int userId, String reason) { mPermissionManagerServiceImpl.revokeRuntimePermission(packageName, permissionName, - deviceId, userId, reason); + userId, reason); } @Override @@ -581,14 +580,14 @@ public class PermissionManagerService extends IPermissionManager.Stub { public boolean shouldShowRequestPermissionRationale(String packageName, String permissionName, int deviceId, int userId) { return mPermissionManagerServiceImpl.shouldShowRequestPermissionRationale(packageName, - permissionName, deviceId, userId); + permissionName, userId); } @Override public boolean isPermissionRevokedByPolicy(String packageName, String permissionName, int deviceId, int userId) { - return mPermissionManagerServiceImpl.isPermissionRevokedByPolicy(packageName, - permissionName, deviceId, userId); + return mPermissionManagerServiceImpl + .isPermissionRevokedByPolicy(packageName, permissionName, userId); } @Override @@ -869,7 +868,6 @@ public class PermissionManagerService extends IPermissionManager.Stub { * * @param packageName the name of the package to be checked * @param permissionName the name of the permission to be checked - * @param deviceId The device ID * @param userId the user ID * @param superImpl the original implementation that can be delegated to * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if the package has @@ -878,21 +876,20 @@ public class PermissionManagerService extends IPermissionManager.Stub { * @see android.content.pm.PackageManager#checkPermission(String, String) */ int checkPermission(@NonNull String packageName, @NonNull String permissionName, - int deviceId, @UserIdInt int userId, - @NonNull QuadFunction<String, String, Integer, Integer, Integer> superImpl); + @UserIdInt int userId, + @NonNull TriFunction<String, String, Integer, Integer> superImpl); /** * Check whether the given UID has been granted the specified permission. * * @param uid the UID to be checked * @param permissionName the name of the permission to be checked - * @param deviceId The device ID * @param superImpl the original implementation that can be delegated to * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if the package has * the permission, or {@link android.content.pm.PackageManager#PERMISSION_DENIED} otherwise */ - int checkUidPermission(int uid, @NonNull String permissionName, int deviceId, - TriFunction<Integer, String, Integer, Integer> superImpl); + int checkUidPermission(int uid, @NonNull String permissionName, + BiFunction<Integer, String, Integer> superImpl); /** * @return list of delegated permissions @@ -921,32 +918,31 @@ public class PermissionManagerService extends IPermissionManager.Stub { @Override public int checkPermission(@NonNull String packageName, @NonNull String permissionName, - int deviceId, int userId, - @NonNull QuadFunction<String, String, Integer, Integer, Integer> superImpl) { + int userId, @NonNull TriFunction<String, String, Integer, Integer> superImpl) { if (mDelegatedPackageName.equals(packageName) && isDelegatedPermission(permissionName)) { final long identity = Binder.clearCallingIdentity(); try { - return superImpl.apply("com.android.shell", permissionName, deviceId, userId); + return superImpl.apply("com.android.shell", permissionName, userId); } finally { Binder.restoreCallingIdentity(identity); } } - return superImpl.apply(packageName, permissionName, deviceId, userId); + return superImpl.apply(packageName, permissionName, userId); } @Override - public int checkUidPermission(int uid, @NonNull String permissionName, int deviceId, - @NonNull TriFunction<Integer, String, Integer, Integer> superImpl) { + public int checkUidPermission(int uid, @NonNull String permissionName, + @NonNull BiFunction<Integer, String, Integer> superImpl) { if (uid == mDelegatedUid && isDelegatedPermission(permissionName)) { final long identity = Binder.clearCallingIdentity(); try { - return superImpl.apply(Process.SHELL_UID, permissionName, deviceId); + return superImpl.apply(Process.SHELL_UID, permissionName); } finally { Binder.restoreCallingIdentity(identity); } } - return superImpl.apply(uid, permissionName, deviceId); + return superImpl.apply(uid, permissionName); } @Override diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index 6764e087ff04..4353c5787d4b 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -681,7 +681,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } @Override - public int getPermissionFlags(String packageName, String permName, int deviceId, int userId) { + public int getPermissionFlags(String packageName, String permName, int userId) { final int callingUid = Binder.getCallingUid(); return getPermissionFlagsInternal(packageName, permName, callingUid, userId); } @@ -724,7 +724,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt @Override public void updatePermissionFlags(String packageName, String permName, int flagMask, - int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId, int userId) { + int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) { final int callingUid = Binder.getCallingUid(); boolean overridePolicy = false; @@ -908,12 +908,8 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } } - private int checkPermission(String pkgName, String permName, int userId) { - return checkPermission(pkgName, permName, Context.DEVICE_ID_DEFAULT, userId); - } - @Override - public int checkPermission(String pkgName, String permName, int deviceId, int userId) { + public int checkPermission(String pkgName, String permName, int userId) { if (!mUserManagerInt.exists(userId)) { return PackageManager.PERMISSION_DENIED; } @@ -979,12 +975,8 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt return true; } - private int checkUidPermission(int uid, String permName) { - return checkUidPermission(uid, permName, Context.DEVICE_ID_DEFAULT); - } - @Override - public int checkUidPermission(int uid, String permName, int deviceId) { + public int checkUidPermission(int uid, String permName) { final int userId = UserHandle.getUserId(uid); if (!mUserManagerInt.exists(userId)) { return PackageManager.PERMISSION_DENIED; @@ -1303,8 +1295,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } @Override - public void grantRuntimePermission(String packageName, String permName, int deviceId, - int userId) { + public void grantRuntimePermission(String packageName, String permName, final int userId) { final int callingUid = Binder.getCallingUid(); final boolean overridePolicy = checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY) @@ -1477,11 +1468,11 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } @Override - public void revokeRuntimePermission(String packageName, String permName, int deviceId, - int userId, String reason) { + public void revokeRuntimePermission(String packageName, String permName, int userId, + String reason) { final int callingUid = Binder.getCallingUid(); final boolean overridePolicy = - checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY, deviceId) + checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY) == PackageManager.PERMISSION_GRANTED; revokeRuntimePermissionInternal(packageName, permName, overridePolicy, callingUid, userId, @@ -1868,7 +1859,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt @Override public boolean shouldShowRequestPermissionRationale(String packageName, String permName, - int deviceId, @UserIdInt int userId) { + @UserIdInt int userId) { final int callingUid = Binder.getCallingUid(); if (UserHandle.getCallingUserId() != userId) { mContext.enforceCallingPermission( @@ -1931,8 +1922,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } @Override - public boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId, - int userId) { + public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) { if (UserHandle.getCallingUserId() != userId) { mContext.enforceCallingPermission( android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, @@ -2069,8 +2059,8 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt continue; } boolean isSystemOrPolicyFixed = (getPermissionFlags(newPackage.getPackageName(), - permInfo.name, Context.DEVICE_ID_DEFAULT, userId) & ( - FLAG_PERMISSION_SYSTEM_FIXED | FLAG_PERMISSION_POLICY_FIXED)) != 0; + permInfo.name, userId) & (FLAG_PERMISSION_SYSTEM_FIXED + | FLAG_PERMISSION_POLICY_FIXED)) != 0; if (isSystemOrPolicyFixed) { continue; } @@ -2236,8 +2226,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt for (final int userId : userIds) { final int permissionState = checkPermission(packageName, permName, userId); - final int flags = getPermissionFlags(packageName, permName, - Context.DEVICE_ID_DEFAULT, userId); + final int flags = getPermissionFlags(packageName, permName, userId); final int flagMask = FLAG_PERMISSION_SYSTEM_FIXED | FLAG_PERMISSION_POLICY_FIXED | FLAG_PERMISSION_GRANTED_BY_DEFAULT @@ -5133,7 +5122,8 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt @NonNull @Override - public Set<String> getGrantedPermissions(@NonNull String packageName, @UserIdInt int userId) { + public Set<String> getGrantedPermissions(@NonNull String packageName, + @UserIdInt int userId) { Objects.requireNonNull(packageName, "packageName"); Preconditions.checkArgumentNonNegative(userId, "userId"); return getGrantedPermissionsInternal(packageName, userId); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java index 2d824aa1ba13..128f847715ab 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java @@ -25,6 +25,7 @@ import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.permission.SplitPermissionInfoParcelable; import android.permission.IOnPermissionsChangeListener; +import android.permission.PermissionManager; import android.permission.PermissionManagerInternal; import com.android.server.pm.pkg.AndroidPackage; @@ -136,16 +137,14 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte void removePermission(String permName); /** - * Gets the permission state flags associated with a permission. + * Gets the state flags associated with a permission. * * @param packageName the package name for which to get the flags * @param permName the permission for which to get the flags - * @param deviceId The device for which to get the flags * @param userId the user for which to get permission flags * @return the permission flags */ - int getPermissionFlags(String packageName, String permName, int deviceId, - @UserIdInt int userId); + int getPermissionFlags(String packageName, String permName, int userId); /** * Updates the flags associated with a permission by replacing the flags in the specified mask @@ -155,11 +154,10 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte * @param permName The permission for which to update the flags * @param flagMask The flags which to replace * @param flagValues The flags with which to replace - * @param deviceId The device for which to update the permission flags * @param userId The user for which to update the permission flags */ - void updatePermissionFlags(String packageName, String permName, int flagMask, int flagValues, - boolean checkAdjustPolicyFlagPermission, int deviceId, @UserIdInt int userId); + void updatePermissionFlags(String packageName, String permName, int flagMask, + int flagValues, boolean checkAdjustPolicyFlagPermission, int userId); /** * Update the permission flags for all packages and runtime permissions of a user in order @@ -293,13 +291,11 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte * * @param packageName the package to which to grant the permission * @param permName the permission name to grant - * @param deviceId the device for which to grant the permission * @param userId the user for which to grant the permission * - * @see #revokeRuntimePermission(String, String, int, int, String) + * @see #revokeRuntimePermission(String, String, android.os.UserHandle, String) */ - void grantRuntimePermission(String packageName, String permName, int deviceId, - @UserIdInt int userId); + void grantRuntimePermission(String packageName, String permName, int userId); /** * Revoke a runtime permission that was previously granted by @@ -314,14 +310,13 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte * * @param packageName the package from which to revoke the permission * @param permName the permission name to revoke - * @param deviceId the device for which to revoke the permission * @param userId the user for which to revoke the permission * @param reason the reason for the revoke, or {@code null} for unspecified * - * @see #grantRuntimePermission(String, String, int, int) + * @see #grantRuntimePermission(String, String, android.os.UserHandle) */ - void revokeRuntimePermission(String packageName, String permName, int deviceId, - @UserIdInt int userId, String reason); + void revokeRuntimePermission(String packageName, String permName, int userId, + String reason); /** * Revoke the POST_NOTIFICATIONS permission, without killing the app. This method must ONLY BE @@ -338,29 +333,24 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte * does not clearly communicate to the user what would be the benefit from grating this * permission. * - * @param packageName the package name * @param permName a permission your app wants to request - * @param deviceId the device for which to check the permission - * @param userId the user for which to check the permission * @return whether you can show permission rationale UI */ boolean shouldShowRequestPermissionRationale(String packageName, String permName, - int deviceId, @UserIdInt int userId); + @UserIdInt int userId); /** - * Checks whether a particular permission has been revoked for a package by policy. Typically, + * Checks whether a particular permissions has been revoked for a package by policy. Typically * the device owner or the profile owner may apply such a policy. The user cannot grant policy * revoked permissions, hence the only way for an app to get such a permission is by a policy * change. * * @param packageName the name of the package you are checking against * @param permName the name of the permission you are checking for - * @param deviceId the device for which you are checking the permission - * @param userId the device for which you are checking the permission + * * @return whether the permission is restricted by policy */ - boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId, - @UserIdInt int userId); + boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId); /** * Get set of permissions that have been split into more granular or dependent permissions. @@ -383,25 +373,14 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte List<SplitPermissionInfoParcelable> getSplitPermissions(); /** - * Check whether a permission is granted or not to a package. - * - * @param pkgName package name - * @param permName permission name - * @param deviceId device ID - * @param userId user ID - * @return permission result {@link PackageManager.PermissionResult} + * TODO:theianchen add doc describing this is the old checkPermissionImpl */ - int checkPermission(String pkgName, String permName, int deviceId, @UserIdInt int userId); + int checkPermission(String pkgName, String permName, int userId); /** - * Check whether a permission is granted or not to an UID. - * - * @param uid UID - * @param permName permission name - * @param deviceId device ID - * @return permission result {@link PackageManager.PermissionResult} + * TODO:theianchen add doc describing this is the old checkUidPermissionImpl */ - int checkUidPermission(int uid, String permName, int deviceId); + int checkUidPermission(int uid, String permName); /** * Get all the package names requesting app op permissions. @@ -421,11 +400,15 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte @UserIdInt int userId); /** - * Reset the runtime permission state changes for a package for all devices. + * Reset the runtime permission state changes for a package. * * TODO(zhanghai): Turn this into package change callback? + * + * @param pkg the package + * @param userId the user ID */ - void resetRuntimePermissions(@NonNull AndroidPackage pkg, @UserIdInt int userId); + void resetRuntimePermissions(@NonNull AndroidPackage pkg, + @UserIdInt int userId); /** * Reset the runtime permission state changes for all packages in a user. @@ -466,8 +449,8 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte /** * Get all the permissions granted to a package. * - * @param packageName package name - * @param userId user ID + * @param packageName the name of the package + * @param userId the user ID * @return the names of the granted permissions */ @NonNull diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java index dacb8c6890a0..7f98e2163178 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java @@ -120,21 +120,21 @@ public class PermissionManagerServiceLoggingDecorator implements PermissionManag } @Override - public int getPermissionFlags(String packageName, String permName, int deviceId, int userId) { + public int getPermissionFlags(String packageName, String permName, int userId) { Log.i(LOG_TAG, "getPermissionFlags(packageName = " + packageName + ", permName = " - + permName + ", deviceId = " + deviceId + ", userId = " + userId + ")"); - return mService.getPermissionFlags(packageName, permName, deviceId, userId); + + permName + ", userId = " + userId + ")"); + return mService.getPermissionFlags(packageName, permName, userId); } @Override public void updatePermissionFlags(String packageName, String permName, int flagMask, - int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId, int userId) { + int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) { Log.i(LOG_TAG, "updatePermissionFlags(packageName = " + packageName + ", permName = " + permName + ", flagMask = " + flagMask + ", flagValues = " + flagValues + ", checkAdjustPolicyFlagPermission = " + checkAdjustPolicyFlagPermission - + ", deviceId = " + deviceId + ", userId = " + userId + ")"); + + ", userId = " + userId + ")"); mService.updatePermissionFlags(packageName, permName, flagMask, flagValues, - checkAdjustPolicyFlagPermission, deviceId, userId); + checkAdjustPolicyFlagPermission, userId); } @Override @@ -182,20 +182,18 @@ public class PermissionManagerServiceLoggingDecorator implements PermissionManag } @Override - public void grantRuntimePermission(String packageName, String permName, int deviceId, - int userId) { + public void grantRuntimePermission(String packageName, String permName, int userId) { Log.i(LOG_TAG, "grantRuntimePermission(packageName = " + packageName + ", permName = " - + permName + ", deviceId = " + deviceId + ", userId = " + userId + ")"); - mService.grantRuntimePermission(packageName, permName, deviceId, userId); + + permName + ", userId = " + userId + ")"); + mService.grantRuntimePermission(packageName, permName, userId); } @Override - public void revokeRuntimePermission(String packageName, String permName, int deviceId, - int userId, String reason) { + public void revokeRuntimePermission(String packageName, String permName, int userId, + String reason) { Log.i(LOG_TAG, "revokeRuntimePermission(packageName = " + packageName + ", permName = " - + permName + ", deviceId = " + deviceId + ", userId = " + userId - + ", reason = " + reason + ")"); - mService.revokeRuntimePermission(packageName, permName, deviceId, userId, reason); + + permName + ", userId = " + userId + ", reason = " + reason + ")"); + mService.revokeRuntimePermission(packageName, permName, userId, reason); } @Override @@ -207,20 +205,17 @@ public class PermissionManagerServiceLoggingDecorator implements PermissionManag @Override public boolean shouldShowRequestPermissionRationale(String packageName, String permName, - int deviceId, int userId) { + int userId) { Log.i(LOG_TAG, "shouldShowRequestPermissionRationale(packageName = " + packageName - + ", permName = " + permName + ", deviceId = " + deviceId - + ", userId = " + userId + ")"); - return mService.shouldShowRequestPermissionRationale(packageName, permName, deviceId, - userId); + + ", permName = " + permName + ", userId = " + userId + ")"); + return mService.shouldShowRequestPermissionRationale(packageName, permName, userId); } @Override - public boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId, - int userId) { + public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) { Log.i(LOG_TAG, "isPermissionRevokedByPolicy(packageName = " + packageName + ", permName = " - + permName + ", deviceId = " + deviceId + ", userId = " + userId + ")"); - return mService.isPermissionRevokedByPolicy(packageName, permName, deviceId, userId); + + permName + ", userId = " + userId + ")"); + return mService.isPermissionRevokedByPolicy(packageName, permName, userId); } @Override @@ -230,17 +225,16 @@ public class PermissionManagerServiceLoggingDecorator implements PermissionManag } @Override - public int checkPermission(String pkgName, String permName, int deviceId, int userId) { + public int checkPermission(String pkgName, String permName, int userId) { Log.i(LOG_TAG, "checkPermission(pkgName = " + pkgName + ", permName = " + permName - + ", deviceId = " + deviceId + ", userId = " + userId + ")"); - return mService.checkPermission(pkgName, permName, deviceId, userId); + + ", userId = " + userId + ")"); + return mService.checkPermission(pkgName, permName, userId); } @Override - public int checkUidPermission(int uid, String permName, int deviceId) { - Log.i(LOG_TAG, "checkUidPermission(uid = " + uid + ", permName = " + permName - + ", deviceId = " + deviceId + ")"); - return mService.checkUidPermission(uid, permName, deviceId); + public int checkUidPermission(int uid, String permName) { + Log.i(LOG_TAG, "checkUidPermission(uid = " + uid + ", permName = " + permName + ")"); + return mService.checkUidPermission(uid, permName); } @Override diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java index 35d165b9b54a..d4c6d42deeaa 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java @@ -153,10 +153,9 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer } @Override - public int getPermissionFlags(String packageName, String permName, int deviceId, - @UserIdInt int userId) { - int oldVal = mOldImplementation.getPermissionFlags(packageName, permName, deviceId, userId); - int newVal = mNewImplementation.getPermissionFlags(packageName, permName, deviceId, userId); + public int getPermissionFlags(String packageName, String permName, int userId) { + int oldVal = mOldImplementation.getPermissionFlags(packageName, permName, userId); + int newVal = mNewImplementation.getPermissionFlags(packageName, permName, userId); if (!Objects.equals(oldVal, newVal)) { signalImplDifference("getPermissionFlags"); @@ -166,12 +165,11 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer @Override public void updatePermissionFlags(String packageName, String permName, int flagMask, - int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId, - @UserIdInt int userId) { + int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) { mOldImplementation.updatePermissionFlags(packageName, permName, flagMask, flagValues, - checkAdjustPolicyFlagPermission, deviceId, userId); + checkAdjustPolicyFlagPermission, userId); mNewImplementation.updatePermissionFlags(packageName, permName, flagMask, flagValues, - checkAdjustPolicyFlagPermission, deviceId, userId); + checkAdjustPolicyFlagPermission, userId); } @Override @@ -236,17 +234,16 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer } @Override - public void grantRuntimePermission(String packageName, String permName, int deviceId, - @UserIdInt int userId) { - mOldImplementation.grantRuntimePermission(packageName, permName, deviceId, userId); - mNewImplementation.grantRuntimePermission(packageName, permName, deviceId, userId); + public void grantRuntimePermission(String packageName, String permName, int userId) { + mOldImplementation.grantRuntimePermission(packageName, permName, userId); + mNewImplementation.grantRuntimePermission(packageName, permName, userId); } @Override - public void revokeRuntimePermission(String packageName, String permName, int deviceId, - @UserIdInt int userId, String reason) { - mOldImplementation.revokeRuntimePermission(packageName, permName, deviceId, userId, reason); - mNewImplementation.revokeRuntimePermission(packageName, permName, deviceId, userId, reason); + public void revokeRuntimePermission(String packageName, String permName, int userId, + String reason) { + mOldImplementation.grantRuntimePermission(packageName, permName, userId); + mNewImplementation.grantRuntimePermission(packageName, permName, userId); } @Override @@ -258,11 +255,11 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer @Override public boolean shouldShowRequestPermissionRationale(String packageName, String permName, - int deviceId, @UserIdInt int userId) { - boolean oldVal = mOldImplementation.shouldShowRequestPermissionRationale(packageName, - permName, deviceId, userId); - boolean newVal = mNewImplementation.shouldShowRequestPermissionRationale(packageName, - permName, deviceId, userId); + int userId) { + boolean oldVal = mOldImplementation + .shouldShowRequestPermissionRationale(packageName, permName, userId); + boolean newVal = mNewImplementation + .shouldShowRequestPermissionRationale(packageName, permName, userId); if (!Objects.equals(oldVal, newVal)) { signalImplDifference("shouldShowRequestPermissionRationale"); @@ -271,12 +268,11 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer } @Override - public boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId, - @UserIdInt int userId) { - boolean oldVal = mOldImplementation.isPermissionRevokedByPolicy(packageName, permName, - deviceId, userId); + public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) { + boolean oldVal = mOldImplementation + .isPermissionRevokedByPolicy(packageName, permName, userId); boolean newVal = mNewImplementation.isPermissionRevokedByPolicy(packageName, permName, - deviceId, userId); + userId); if (!Objects.equals(oldVal, newVal)) { signalImplDifference("isPermissionRevokedByPolicy"); @@ -296,9 +292,9 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer } @Override - public int checkPermission(String pkgName, String permName, int deviceId, int userId) { - int oldVal = mOldImplementation.checkPermission(pkgName, permName, deviceId, userId); - int newVal = mNewImplementation.checkPermission(pkgName, permName, deviceId, userId); + public int checkPermission(String pkgName, String permName, int userId) { + int oldVal = mOldImplementation.checkPermission(pkgName, permName, userId); + int newVal = mNewImplementation.checkPermission(pkgName, permName, userId); if (!Objects.equals(oldVal, newVal)) { signalImplDifference("checkPermission"); @@ -307,9 +303,9 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer } @Override - public int checkUidPermission(int uid, String permName, int deviceId) { - int oldVal = mOldImplementation.checkUidPermission(uid, permName, deviceId); - int newVal = mNewImplementation.checkUidPermission(uid, permName, deviceId); + public int checkUidPermission(int uid, String permName) { + int oldVal = mOldImplementation.checkUidPermission(uid, permName); + int newVal = mNewImplementation.checkUidPermission(uid, permName); if (!Objects.equals(oldVal, newVal)) { signalImplDifference("checkUidPermission"); @@ -376,7 +372,7 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer @NonNull @Override - public Set<String> getGrantedPermissions(@NonNull String packageName, @UserIdInt int userId) { + public Set<String> getGrantedPermissions(@NonNull String packageName, int userId) { Set<String> oldVal = mOldImplementation.getGrantedPermissions(packageName, userId); Set<String> newVal = mNewImplementation.getGrantedPermissions(packageName, userId); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java index cbeede0f425c..4e72fae99c9c 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java @@ -158,10 +158,10 @@ public class PermissionManagerServiceTracingDecorator implements PermissionManag } @Override - public int getPermissionFlags(String packageName, String permName, int deviceId, int userId) { + public int getPermissionFlags(String packageName, String permName, int userId) { Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#getPermissionFlags"); try { - return mService.getPermissionFlags(packageName, permName, deviceId, userId); + return mService.getPermissionFlags(packageName, permName, userId); } finally { Trace.traceEnd(TRACE_TAG); } @@ -169,12 +169,12 @@ public class PermissionManagerServiceTracingDecorator implements PermissionManag @Override public void updatePermissionFlags(String packageName, String permName, int flagMask, - int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId, int userId) { + int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) { Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#updatePermissionFlags"); try { mService.updatePermissionFlags(packageName, permName, flagMask, flagValues, - checkAdjustPolicyFlagPermission, deviceId, userId); + checkAdjustPolicyFlagPermission, userId); } finally { Trace.traceEnd(TRACE_TAG); } @@ -253,24 +253,23 @@ public class PermissionManagerServiceTracingDecorator implements PermissionManag } @Override - public void grantRuntimePermission(String packageName, String permName, int deviceId, - int userId) { + public void grantRuntimePermission(String packageName, String permName, int userId) { Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#grantRuntimePermission"); try { - mService.grantRuntimePermission(packageName, permName, deviceId, userId); + mService.grantRuntimePermission(packageName, permName, userId); } finally { Trace.traceEnd(TRACE_TAG); } } @Override - public void revokeRuntimePermission(String packageName, String permName, int deviceId, - int userId, String reason) { + public void revokeRuntimePermission(String packageName, String permName, int userId, + String reason) { Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#revokeRuntimePermission"); try { - mService.revokeRuntimePermission(packageName, permName, deviceId, userId, reason); + mService.revokeRuntimePermission(packageName, permName, userId, reason); } finally { Trace.traceEnd(TRACE_TAG); } @@ -289,24 +288,22 @@ public class PermissionManagerServiceTracingDecorator implements PermissionManag @Override public boolean shouldShowRequestPermissionRationale(String packageName, String permName, - int deviceId, int userId) { + int userId) { Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#shouldShowRequestPermissionRationale"); try { - return mService.shouldShowRequestPermissionRationale( - packageName, permName, deviceId, userId); + return mService.shouldShowRequestPermissionRationale(packageName, permName, userId); } finally { Trace.traceEnd(TRACE_TAG); } } @Override - public boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId, - int userId) { + public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) { Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#isPermissionRevokedByPolicy"); try { - return mService.isPermissionRevokedByPolicy(packageName, permName, deviceId, userId); + return mService.isPermissionRevokedByPolicy(packageName, permName, userId); } finally { Trace.traceEnd(TRACE_TAG); } @@ -324,20 +321,20 @@ public class PermissionManagerServiceTracingDecorator implements PermissionManag } @Override - public int checkPermission(String pkgName, String permName, int deviceId, int userId) { + public int checkPermission(String pkgName, String permName, int userId) { Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#checkPermission"); try { - return mService.checkPermission(pkgName, permName, deviceId, userId); + return mService.checkPermission(pkgName, permName, userId); } finally { Trace.traceEnd(TRACE_TAG); } } @Override - public int checkUidPermission(int uid, String permName, int deviceId) { + public int checkUidPermission(int uid, String permName) { Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#checkUidPermission"); try { - return mService.checkUidPermission(uid, permName, deviceId); + return mService.checkUidPermission(uid, permName); } finally { Trace.traceEnd(TRACE_TAG); } diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java index 2c37876dd261..3f347e465f81 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageState.java +++ b/services/core/java/com/android/server/pm/pkg/PackageState.java @@ -59,11 +59,6 @@ import java.util.Set; public interface PackageState { /* - * Until immutability or read-only caching is enabled, {@link PackageSetting} cannot be - * returned directly, so {@link PackageStateImpl} is used to temporarily copy the data. - * This is a relatively expensive operation since it has to create an object for every package, - * but it's much lighter than the alternative of generating {@link PackageInfo} objects. - * <p> * TODO: Documentation * TODO: Currently missing, should be exposed as API? * - keySetData @@ -350,6 +345,12 @@ public interface PackageState { String getVolumeUuid(); /** + * @see AndroidPackage#isDefaultToDeviceProtectedStorage() + * @hide + */ + boolean isDefaultToDeviceProtectedStorage(); + + /** * @see AndroidPackage#isExternalStorage() * @hide */ diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java deleted file mode 100644 index ba274e046b47..000000000000 --- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java +++ /dev/null @@ -1,759 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.pm.pkg; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManagerInternal; -import android.content.pm.SigningInfo; -import android.content.pm.overlay.OverlayPaths; -import android.os.UserHandle; -import android.util.ArraySet; -import android.util.SparseArray; - -import com.android.internal.util.DataClass; -import com.android.server.pm.PackageManagerService; -import com.android.server.pm.PackageSetting; -import com.android.server.pm.Settings; - -import java.io.File; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Consumer; - -/** - * Because a {@link PackageSetting} cannot be returned from {@link Settings} without holding the - * {@link PackageManagerService#mLock}, this class serves as a memory snapshot of the state of a - * single package, for use with {@link PackageManagerInternal#getPackageState(String)} and {@link - * PackageManagerInternal#forEachPackageState(boolean, Consumer)}. - * - * @hide - */ -@DataClass(genConstructor = false) -@DataClass.Suppress({"mUserStates"}) -public class PackageStateImpl implements PackageState { - - public static PackageState copy(@NonNull PackageStateInternal pkgSetting) { - return new PackageStateImpl(pkgSetting, pkgSetting.getPkg()); - } - - private static class Booleans { - @IntDef({ - SYSTEM, - EXTERNAL_STORAGE, - PRIVILEGED, - OEM, - VENDOR, - PRODUCT, - SYSTEM_EXT, - REQUIRED_FOR_SYSTEM_USER, - ODM, - FORCE_QUERYABLE_OVERRIDE, - HIDDEN_UNTIL_INSTALLED, - INSTALL_PERMISSIONS_FIXED, - UPDATE_AVAILABLE, - UPDATED_SYSTEM_APP, - APK_IN_UPDATED_APEX, - }) - public @interface Flags { - } - - private static final int SYSTEM = 1; - private static final int EXTERNAL_STORAGE = 1 << 1; - private static final int PRIVILEGED = 1 << 2; - private static final int OEM = 1 << 3; - private static final int VENDOR = 1 << 4; - private static final int PRODUCT = 1 << 5; - private static final int SYSTEM_EXT = 1 << 6; - private static final int REQUIRED_FOR_SYSTEM_USER = 1 << 7; - private static final int ODM = 1 << 8; - private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 9; - private static final int HIDDEN_UNTIL_INSTALLED = 1 << 10; - private static final int INSTALL_PERMISSIONS_FIXED = 1 << 11; - private static final int UPDATE_AVAILABLE = 1 << 12; - private static final int UPDATED_SYSTEM_APP = 1 << 13; - private static final int APK_IN_UPDATED_APEX = 1 << 14; - } - - private int mBooleans; - - private void setBoolean(@Booleans.Flags int flag, boolean value) { - if (value) { - mBooleans |= flag; - } else { - mBooleans &= ~flag; - } - } - - private boolean getBoolean(@Booleans.Flags int flag) { - return (mBooleans & flag) != 0; - } - - @Nullable - private final AndroidPackage mAndroidPackage; - - @NonNull - private final String mPackageName; - @Nullable - private final String mVolumeUuid; - private final int mAppId; - private final int mCategoryOverride; - @Nullable - private final String mCpuAbiOverride; - @ApplicationInfo.HiddenApiEnforcementPolicy - private final int mHiddenApiEnforcementPolicy; - private final long mLastModifiedTime; - private final long mLastUpdateTime; - private final long mLongVersionCode; - @NonNull - private final Map<String, Set<String>> mMimeGroups; - @NonNull - private final File mPath; - @Nullable - private final String mPrimaryCpuAbi; - @Nullable - private final String mSecondaryCpuAbi; - @Nullable - private final String mSeInfo; - private final boolean mHasSharedUser; - private final int mSharedUserAppId; - @NonNull - private final String[] mUsesSdkLibraries; - @NonNull - private final long[] mUsesSdkLibrariesVersionsMajor; - @NonNull - private final String[] mUsesStaticLibraries; - @NonNull - private final long[] mUsesStaticLibrariesVersions; - @NonNull - private final List<SharedLibrary> mUsesLibraries; - @NonNull - private final List<String> mUsesLibraryFiles; - @NonNull - private final long[] mLastPackageUsageTime; - @NonNull - private final SigningInfo mSigningInfo; - @NonNull - private final SparseArray<PackageUserState> mUserStates; - @Nullable - private final String mApexModuleName; - - private PackageStateImpl(@NonNull PackageState pkgState, @Nullable AndroidPackage pkg) { - mAndroidPackage = pkg; - - setBoolean(Booleans.SYSTEM, pkgState.isSystem()); - setBoolean(Booleans.EXTERNAL_STORAGE, pkgState.isExternalStorage()); - setBoolean(Booleans.PRIVILEGED, pkgState.isPrivileged()); - setBoolean(Booleans.OEM, pkgState.isOem()); - setBoolean(Booleans.VENDOR, pkgState.isVendor()); - setBoolean(Booleans.PRODUCT, pkgState.isProduct()); - setBoolean(Booleans.SYSTEM_EXT, pkgState.isSystemExt()); - setBoolean(Booleans.REQUIRED_FOR_SYSTEM_USER, pkgState.isRequiredForSystemUser()); - setBoolean(Booleans.ODM, pkgState.isOdm()); - - mPackageName = pkgState.getPackageName(); - mVolumeUuid = pkgState.getVolumeUuid(); - mAppId = pkgState.getAppId(); - mCategoryOverride = pkgState.getCategoryOverride(); - mCpuAbiOverride = pkgState.getCpuAbiOverride(); - mHiddenApiEnforcementPolicy = pkgState.getHiddenApiEnforcementPolicy(); - mLastModifiedTime = pkgState.getLastModifiedTime(); - mLastUpdateTime = pkgState.getLastUpdateTime(); - mLongVersionCode = pkgState.getVersionCode(); - mMimeGroups = Collections.unmodifiableMap(pkgState.getMimeGroups()); - mPath = pkgState.getPath(); - mPrimaryCpuAbi = pkgState.getPrimaryCpuAbi(); - mSecondaryCpuAbi = pkgState.getSecondaryCpuAbi(); - mSeInfo = pkgState.getSeInfo(); - mHasSharedUser = pkgState.hasSharedUser(); - mSharedUserAppId = pkgState.getSharedUserAppId(); - mUsesSdkLibraries = pkgState.getUsesSdkLibraries(); - mUsesSdkLibrariesVersionsMajor = pkgState.getUsesSdkLibrariesVersionsMajor(); - mUsesStaticLibraries = pkgState.getUsesStaticLibraries(); - mUsesStaticLibrariesVersions = pkgState.getUsesStaticLibrariesVersions(); - mUsesLibraries = Collections.unmodifiableList(pkgState.getSharedLibraryDependencies()); - mUsesLibraryFiles = Collections.unmodifiableList(pkgState.getUsesLibraryFiles()); - setBoolean(Booleans.FORCE_QUERYABLE_OVERRIDE, pkgState.isForceQueryableOverride()); - setBoolean(Booleans.HIDDEN_UNTIL_INSTALLED, pkgState.isHiddenUntilInstalled()); - setBoolean(Booleans.INSTALL_PERMISSIONS_FIXED, pkgState.isInstallPermissionsFixed()); - setBoolean(Booleans.UPDATE_AVAILABLE, pkgState.isUpdateAvailable()); - mLastPackageUsageTime = pkgState.getLastPackageUsageTime(); - setBoolean(Booleans.UPDATED_SYSTEM_APP, pkgState.isUpdatedSystemApp()); - setBoolean(Booleans.APK_IN_UPDATED_APEX, pkgState.isApkInUpdatedApex()); - mSigningInfo = pkgState.getSigningInfo(); - - SparseArray<? extends PackageUserState> userStates = pkgState.getUserStates(); - int userStatesSize = userStates.size(); - mUserStates = new SparseArray<>(userStatesSize); - for (int index = 0; index < userStatesSize; index++) { - mUserStates.put(userStates.keyAt(index), - UserStateImpl.copy(userStates.valueAt(index))); - } - - mApexModuleName = pkgState.getApexModuleName(); - } - - @NonNull - @Override - public PackageUserState getStateForUser(@NonNull UserHandle user) { - PackageUserState userState = getUserStates().get(user.getIdentifier()); - return userState == null ? PackageUserState.DEFAULT : userState; - } - - @Override - public boolean isExternalStorage() { - return getBoolean(Booleans.EXTERNAL_STORAGE); - } - - @Override - public boolean isForceQueryableOverride() { - return getBoolean(Booleans.FORCE_QUERYABLE_OVERRIDE); - } - - @Override - public boolean isHiddenUntilInstalled() { - return getBoolean(Booleans.HIDDEN_UNTIL_INSTALLED); - } - - @Override - public boolean isInstallPermissionsFixed() { - return getBoolean(Booleans.INSTALL_PERMISSIONS_FIXED); - } - - @Override - public boolean isOdm() { - return getBoolean(Booleans.ODM); - } - - @Override - public boolean isOem() { - return getBoolean(Booleans.OEM); - } - - @Override - public boolean isPrivileged() { - return getBoolean(Booleans.PRIVILEGED); - } - - @Override - public boolean isProduct() { - return getBoolean(Booleans.PRODUCT); - } - - @Override - public boolean isRequiredForSystemUser() { - return getBoolean(Booleans.REQUIRED_FOR_SYSTEM_USER); - } - - @Override - public boolean isSystem() { - return getBoolean(Booleans.SYSTEM); - } - - @Override - public boolean isSystemExt() { - return getBoolean(Booleans.SYSTEM_EXT); - } - - @Override - public boolean isUpdateAvailable() { - return getBoolean(Booleans.UPDATE_AVAILABLE); - } - - @Override - public boolean isUpdatedSystemApp() { - return getBoolean(Booleans.UPDATED_SYSTEM_APP); - } - - @Override - public boolean isApkInUpdatedApex() { - return getBoolean(Booleans.APK_IN_UPDATED_APEX); - } - - @Override - public boolean isVendor() { - return getBoolean(Booleans.VENDOR); - } - - @Override - public long getVersionCode() { - return mLongVersionCode; - } - - @Override - public boolean hasSharedUser() { - return mHasSharedUser; - } - - @Override - public boolean isApex() { - return getAndroidPackage() != null && getAndroidPackage().isApex(); - } - - /** - * @hide - */ - @DataClass(genConstructor = false) - public static class UserStateImpl implements PackageUserState { - - public static PackageUserState copy(@NonNull PackageUserState state) { - return new UserStateImpl(state); - } - - private static class Booleans { - @IntDef({ - HIDDEN, - INSTALLED, - INSTANT_APP, - NOT_LAUNCHED, - STOPPED, - SUSPENDED, - VIRTUAL_PRELOAD, - }) - public @interface Flags { - } - - private static final int HIDDEN = 1; - private static final int INSTALLED = 1 << 1; - private static final int INSTANT_APP = 1 << 2; - private static final int NOT_LAUNCHED = 1 << 3; - private static final int STOPPED = 1 << 4; - private static final int SUSPENDED = 1 << 5; - private static final int VIRTUAL_PRELOAD = 1 << 6; - } - - private int mBooleans; - - private void setBoolean(@Booleans.Flags int flag, boolean value) { - if (value) { - mBooleans |= flag; - } else { - mBooleans &= ~flag; - } - } - - private boolean getBoolean(@Booleans.Flags int flag) { - return (mBooleans & flag) != 0; - } - - private final long mCeDataInode; - @NonNull - private final ArraySet<String> mDisabledComponents; - @PackageManager.DistractionRestriction - private final int mDistractionFlags; - @NonNull - private final ArraySet<String> mEnabledComponents; - private final int mEnabledState; - @Nullable - private final String mHarmfulAppWarning; - @PackageManager.InstallReason - private final int mInstallReason; - @Nullable - private final String mLastDisableAppCaller; - @NonNull - private final OverlayPaths mOverlayPaths; - @NonNull - private final Map<String, OverlayPaths> mSharedLibraryOverlayPaths; - @PackageManager.UninstallReason - private final int mUninstallReason; - @Nullable - private final String mSplashScreenTheme; - @PackageManager.UserMinAspectRatio - private final int mMinAspectRatio; - private final long mFirstInstallTimeMillis; - @Nullable - private final ArchiveState mArchiveState; - - private UserStateImpl(@NonNull PackageUserState userState) { - mCeDataInode = userState.getCeDataInode(); - mDisabledComponents = userState.getDisabledComponents(); - mDistractionFlags = userState.getDistractionFlags(); - mEnabledComponents = userState.getEnabledComponents(); - mEnabledState = userState.getEnabledState(); - mHarmfulAppWarning = userState.getHarmfulAppWarning(); - mInstallReason = userState.getInstallReason(); - mLastDisableAppCaller = userState.getLastDisableAppCaller(); - mOverlayPaths = userState.getOverlayPaths(); - mSharedLibraryOverlayPaths = userState.getSharedLibraryOverlayPaths(); - mUninstallReason = userState.getUninstallReason(); - mSplashScreenTheme = userState.getSplashScreenTheme(); - mMinAspectRatio = userState.getMinAspectRatio(); - setBoolean(Booleans.HIDDEN, userState.isHidden()); - setBoolean(Booleans.INSTALLED, userState.isInstalled()); - setBoolean(Booleans.INSTANT_APP, userState.isInstantApp()); - setBoolean(Booleans.NOT_LAUNCHED, userState.isNotLaunched()); - setBoolean(Booleans.STOPPED, userState.isStopped()); - setBoolean(Booleans.SUSPENDED, userState.isSuspended()); - setBoolean(Booleans.VIRTUAL_PRELOAD, userState.isVirtualPreload()); - mFirstInstallTimeMillis = userState.getFirstInstallTimeMillis(); - mArchiveState = userState.getArchiveState(); - } - - @Override - public boolean isHidden() { - return getBoolean(Booleans.HIDDEN); - } - - @Override - public boolean isInstalled() { - return getBoolean(Booleans.INSTALLED); - } - - @Override - public boolean isInstantApp() { - return getBoolean(Booleans.INSTANT_APP); - } - - @Override - public boolean isNotLaunched() { - return getBoolean(Booleans.NOT_LAUNCHED); - } - - @Override - public boolean isStopped() { - return getBoolean(Booleans.STOPPED); - } - - @Override - public boolean isSuspended() { - return getBoolean(Booleans.SUSPENDED); - } - - @Override - public boolean isVirtualPreload() { - return getBoolean(Booleans.VIRTUAL_PRELOAD); - } - - @Override - public boolean isComponentEnabled(String componentName) { - return mEnabledComponents.contains(componentName); - } - - @Override - public boolean isComponentDisabled(String componentName) { - return mDisabledComponents.contains(componentName); - } - - @Override - public OverlayPaths getAllOverlayPaths() { - if (mOverlayPaths == null && mSharedLibraryOverlayPaths == null) { - return null; - } - final OverlayPaths.Builder newPaths = new OverlayPaths.Builder(); - newPaths.addAll(mOverlayPaths); - if (mSharedLibraryOverlayPaths != null) { - for (final OverlayPaths libOverlayPaths : mSharedLibraryOverlayPaths.values()) { - newPaths.addAll(libOverlayPaths); - } - } - return newPaths.build(); - } - - - - // Code below generated by codegen v1.0.23. - // - // DO NOT MODIFY! - // CHECKSTYLE:OFF Generated code - // - // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java - // - // To exclude the generated code from IntelliJ auto-formatting enable (one-time): - // Settings > Editor > Code Style > Formatter Control - //@formatter:off - - - @DataClass.Generated.Member - public int getBooleans() { - return mBooleans; - } - - @DataClass.Generated.Member - public long getCeDataInode() { - return mCeDataInode; - } - - @DataClass.Generated.Member - public @NonNull ArraySet<String> getDisabledComponents() { - return mDisabledComponents; - } - - @DataClass.Generated.Member - public @PackageManager.DistractionRestriction int getDistractionFlags() { - return mDistractionFlags; - } - - @DataClass.Generated.Member - public @NonNull ArraySet<String> getEnabledComponents() { - return mEnabledComponents; - } - - @DataClass.Generated.Member - public int getEnabledState() { - return mEnabledState; - } - - @DataClass.Generated.Member - public @Nullable String getHarmfulAppWarning() { - return mHarmfulAppWarning; - } - - @DataClass.Generated.Member - public @PackageManager.InstallReason int getInstallReason() { - return mInstallReason; - } - - @DataClass.Generated.Member - public @Nullable String getLastDisableAppCaller() { - return mLastDisableAppCaller; - } - - @DataClass.Generated.Member - public @NonNull OverlayPaths getOverlayPaths() { - return mOverlayPaths; - } - - @DataClass.Generated.Member - public @NonNull Map<String,OverlayPaths> getSharedLibraryOverlayPaths() { - return mSharedLibraryOverlayPaths; - } - - @DataClass.Generated.Member - public @PackageManager.UninstallReason int getUninstallReason() { - return mUninstallReason; - } - - @DataClass.Generated.Member - public @Nullable String getSplashScreenTheme() { - return mSplashScreenTheme; - } - - @DataClass.Generated.Member - public @PackageManager.UserMinAspectRatio int getMinAspectRatio() { - return mMinAspectRatio; - } - - @DataClass.Generated.Member - public long getFirstInstallTimeMillis() { - return mFirstInstallTimeMillis; - } - - @DataClass.Generated.Member - public @Nullable ArchiveState getArchiveState() { - return mArchiveState; - } - - @DataClass.Generated.Member - public @NonNull UserStateImpl setBooleans( int value) { - mBooleans = value; - return this; - } - - @DataClass.Generated( - time = 1689171425723L, - codegenVersion = "1.0.23", - sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java", - inputSignatures = "private int mBooleans\nprivate final long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate final long mFirstInstallTimeMillis\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.ArchiveState mArchiveState\npublic static com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final int HIDDEN\nprivate static final int INSTALLED\nprivate static final int INSTANT_APP\nprivate static final int NOT_LAUNCHED\nprivate static final int STOPPED\nprivate static final int SUSPENDED\nprivate static final int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)") - @Deprecated - private void __metadata() {} - - - //@formatter:on - // End of generated code - - } - - - - // Code below generated by codegen v1.0.23. - // - // DO NOT MODIFY! - // CHECKSTYLE:OFF Generated code - // - // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java - // - // To exclude the generated code from IntelliJ auto-formatting enable (one-time): - // Settings > Editor > Code Style > Formatter Control - //@formatter:off - - - @DataClass.Generated.Member - public int getBooleans() { - return mBooleans; - } - - @DataClass.Generated.Member - public @Nullable AndroidPackage getAndroidPackage() { - return mAndroidPackage; - } - - @DataClass.Generated.Member - public @NonNull String getPackageName() { - return mPackageName; - } - - @DataClass.Generated.Member - public @Nullable String getVolumeUuid() { - return mVolumeUuid; - } - - @DataClass.Generated.Member - public int getAppId() { - return mAppId; - } - - @DataClass.Generated.Member - public int getCategoryOverride() { - return mCategoryOverride; - } - - @DataClass.Generated.Member - public @Nullable String getCpuAbiOverride() { - return mCpuAbiOverride; - } - - @DataClass.Generated.Member - public @ApplicationInfo.HiddenApiEnforcementPolicy int getHiddenApiEnforcementPolicy() { - return mHiddenApiEnforcementPolicy; - } - - @DataClass.Generated.Member - public long getLastModifiedTime() { - return mLastModifiedTime; - } - - @DataClass.Generated.Member - public long getLastUpdateTime() { - return mLastUpdateTime; - } - - @DataClass.Generated.Member - public long getLongVersionCode() { - return mLongVersionCode; - } - - @DataClass.Generated.Member - public @NonNull Map<String,Set<String>> getMimeGroups() { - return mMimeGroups; - } - - @DataClass.Generated.Member - public @NonNull File getPath() { - return mPath; - } - - @DataClass.Generated.Member - public @Nullable String getPrimaryCpuAbi() { - return mPrimaryCpuAbi; - } - - @DataClass.Generated.Member - public @Nullable String getSecondaryCpuAbi() { - return mSecondaryCpuAbi; - } - - @DataClass.Generated.Member - public @Nullable String getSeInfo() { - return mSeInfo; - } - - @DataClass.Generated.Member - public boolean isHasSharedUser() { - return mHasSharedUser; - } - - @DataClass.Generated.Member - public int getSharedUserAppId() { - return mSharedUserAppId; - } - - @DataClass.Generated.Member - public @NonNull String[] getUsesSdkLibraries() { - return mUsesSdkLibraries; - } - - @DataClass.Generated.Member - public @NonNull long[] getUsesSdkLibrariesVersionsMajor() { - return mUsesSdkLibrariesVersionsMajor; - } - - @DataClass.Generated.Member - public @NonNull String[] getUsesStaticLibraries() { - return mUsesStaticLibraries; - } - - @DataClass.Generated.Member - public @NonNull long[] getUsesStaticLibrariesVersions() { - return mUsesStaticLibrariesVersions; - } - - @DataClass.Generated.Member - public @NonNull List<SharedLibrary> getSharedLibraryDependencies() { - return mUsesLibraries; - } - - @DataClass.Generated.Member - public @NonNull List<String> getUsesLibraryFiles() { - return mUsesLibraryFiles; - } - - @DataClass.Generated.Member - public @NonNull long[] getLastPackageUsageTime() { - return mLastPackageUsageTime; - } - - @DataClass.Generated.Member - public @NonNull SigningInfo getSigningInfo() { - return mSigningInfo; - } - - @DataClass.Generated.Member - public @NonNull SparseArray<PackageUserState> getUserStates() { - return mUserStates; - } - - @DataClass.Generated.Member - public @Nullable String getApexModuleName() { - return mApexModuleName; - } - - @DataClass.Generated.Member - public @NonNull PackageStateImpl setBooleans( int value) { - mBooleans = value; - return this; - } - - @DataClass.Generated( - time = 1689171425753L, - codegenVersion = "1.0.23", - sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java", - inputSignatures = "private int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final int mAppId\nprivate final int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy int mHiddenApiEnforcementPolicy\nprivate final long mLastModifiedTime\nprivate final long mLastUpdateTime\nprivate final long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSeInfo\nprivate final boolean mHasSharedUser\nprivate final int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibrary> mUsesLibraries\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\nprivate final @android.annotation.Nullable java.lang.String mApexModuleName\npublic static com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override boolean isApex()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final int SYSTEM\nprivate static final int EXTERNAL_STORAGE\nprivate static final int PRIVILEGED\nprivate static final int OEM\nprivate static final int VENDOR\nprivate static final int PRODUCT\nprivate static final int SYSTEM_EXT\nprivate static final int REQUIRED_FOR_SYSTEM_USER\nprivate static final int ODM\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int HIDDEN_UNTIL_INSTALLED\nprivate static final int INSTALL_PERMISSIONS_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int UPDATED_SYSTEM_APP\nprivate static final int APK_IN_UPDATED_APEX\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)") - @Deprecated - private void __metadata() {} - - - //@formatter:on - // End of generated code - -} diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java index 6ac7c34d844b..d8c8af6c8fd1 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java @@ -17,6 +17,7 @@ package com.android.server.pm.pkg; import android.annotation.CurrentTimeMillisLong; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; @@ -45,9 +46,44 @@ import java.util.Objects; /** @hide */ @DataClass(genConstructor = false, genBuilder = false, genEqualsHashCode = true) @DataClass.Suppress({"mOverlayPathsLock", "mOverlayPaths", "mSharedLibraryOverlayPathsLock", - "mSharedLibraryOverlayPaths", "setOverlayPaths", "setCachedOverlayPaths", "getWatchable"}) + "mSharedLibraryOverlayPaths", "setOverlayPaths", "setCachedOverlayPaths", "getWatchable", + "getBooleans" +}) public class PackageUserStateImpl extends WatchableImpl implements PackageUserStateInternal, Snappable { + // Use a bitset to store boolean data to save memory + private static class Booleans { + @IntDef({ + INSTALLED, + STOPPED, + NOT_LAUNCHED, + HIDDEN, + INSTANT_APP, + VIRTUAL_PRELOADED, + }) + public @interface Flags { + } + private static final int INSTALLED = 1; + private static final int STOPPED = 1 << 1; + private static final int NOT_LAUNCHED = 1 << 2; + // Is the app restricted by owner / admin + private static final int HIDDEN = 1 << 3; + private static final int INSTANT_APP = 1 << 4; + private static final int VIRTUAL_PRELOADED = 1 << 5; + } + private int mBooleans; + + private void setBoolean(@Booleans.Flags int flag, boolean value) { + if (value) { + mBooleans |= flag; + } else { + mBooleans &= ~flag; + } + } + + private boolean getBoolean(@Booleans.Flags int flag) { + return (mBooleans & flag) != 0; + } @Nullable protected WatchedArraySet<String> mDisabledComponentsWatched; @@ -55,13 +91,7 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt protected WatchedArraySet<String> mEnabledComponentsWatched; private long mCeDataInode; - private boolean mInstalled = true; - private boolean mStopped; - private boolean mNotLaunched; - private boolean mHidden; // Is the app restricted by owner / admin private int mDistractionFlags; - private boolean mInstantApp; - private boolean mVirtualPreload; @PackageManager.EnabledState private int mEnabledState = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; @PackageManager.InstallReason @@ -122,15 +152,18 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt super(); mWatchable = null; mSnapshot = makeCache(); + setBoolean(Booleans.INSTALLED, true); } public PackageUserStateImpl(@NonNull Watchable watchable) { mWatchable = watchable; mSnapshot = makeCache(); + setBoolean(Booleans.INSTALLED, true); } public PackageUserStateImpl(@NonNull Watchable watchable, PackageUserStateImpl other) { mWatchable = watchable; + mBooleans = other.mBooleans; mDisabledComponentsWatched = other.mDisabledComponentsWatched == null ? null : other.mDisabledComponentsWatched.snapshot(); mEnabledComponentsWatched = other.mEnabledComponentsWatched == null @@ -139,13 +172,7 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt mSharedLibraryOverlayPaths = other.mSharedLibraryOverlayPaths == null ? null : other.mSharedLibraryOverlayPaths.snapshot(); mCeDataInode = other.mCeDataInode; - mInstalled = other.mInstalled; - mStopped = other.mStopped; - mNotLaunched = other.mNotLaunched; - mHidden = other.mHidden; mDistractionFlags = other.mDistractionFlags; - mInstantApp = other.mInstantApp; - mVirtualPreload = other.mVirtualPreload; mEnabledState = other.mEnabledState; mInstallReason = other.mInstallReason; mUninstallReason = other.mUninstallReason; @@ -418,25 +445,25 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt } public @NonNull PackageUserStateImpl setInstalled(boolean value) { - mInstalled = value; + setBoolean(Booleans.INSTALLED, value); onChanged(); return this; } public @NonNull PackageUserStateImpl setStopped(boolean value) { - mStopped = value; + setBoolean(Booleans.STOPPED, value); onChanged(); return this; } public @NonNull PackageUserStateImpl setNotLaunched(boolean value) { - mNotLaunched = value; + setBoolean(Booleans.NOT_LAUNCHED, value); onChanged(); return this; } public @NonNull PackageUserStateImpl setHidden(boolean value) { - mHidden = value; + setBoolean(Booleans.HIDDEN, value); onChanged(); return this; } @@ -448,13 +475,13 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt } public @NonNull PackageUserStateImpl setInstantApp(boolean value) { - mInstantApp = value; + setBoolean(Booleans.INSTANT_APP, value); onChanged(); return this; } public @NonNull PackageUserStateImpl setVirtualPreload(boolean value) { - mVirtualPreload = value; + setBoolean(Booleans.VIRTUAL_PRELOADED, value); onChanged(); return this; } @@ -613,6 +640,38 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt } + @Override + public boolean isInstalled() { + return getBoolean(Booleans.INSTALLED); + } + + @Override + public boolean isStopped() { + return getBoolean(Booleans.STOPPED); + } + + @Override + public boolean isNotLaunched() { + return getBoolean(Booleans.NOT_LAUNCHED); + } + + @Override + public boolean isHidden() { + return getBoolean(Booleans.HIDDEN); + } + + @Override + public boolean isInstantApp() { + return getBoolean(Booleans.INSTANT_APP); + } + + @Override + public boolean isVirtualPreload() { + return getBoolean(Booleans.VIRTUAL_PRELOADED); + } + + + // Code below generated by codegen v1.0.23. // @@ -643,41 +702,11 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt } @DataClass.Generated.Member - public boolean isInstalled() { - return mInstalled; - } - - @DataClass.Generated.Member - public boolean isStopped() { - return mStopped; - } - - @DataClass.Generated.Member - public boolean isNotLaunched() { - return mNotLaunched; - } - - @DataClass.Generated.Member - public boolean isHidden() { - return mHidden; - } - - @DataClass.Generated.Member public int getDistractionFlags() { return mDistractionFlags; } @DataClass.Generated.Member - public boolean isInstantApp() { - return mInstantApp; - } - - @DataClass.Generated.Member - public boolean isVirtualPreload() { - return mVirtualPreload; - } - - @DataClass.Generated.Member public @PackageManager.EnabledState int getEnabledState() { return mEnabledState; } @@ -746,6 +775,12 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt } @DataClass.Generated.Member + public @NonNull PackageUserStateImpl setBooleans( int value) { + mBooleans = value; + return this; + } + + @DataClass.Generated.Member public @NonNull PackageUserStateImpl setDisabledComponentsWatched(@NonNull WatchedArraySet<String> value) { mDisabledComponentsWatched = value; return this; @@ -791,16 +826,11 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt PackageUserStateImpl that = (PackageUserStateImpl) o; //noinspection PointlessBooleanExpression return true + && mBooleans == that.mBooleans && Objects.equals(mDisabledComponentsWatched, that.mDisabledComponentsWatched) && Objects.equals(mEnabledComponentsWatched, that.mEnabledComponentsWatched) && mCeDataInode == that.mCeDataInode - && mInstalled == that.mInstalled - && mStopped == that.mStopped - && mNotLaunched == that.mNotLaunched - && mHidden == that.mHidden && mDistractionFlags == that.mDistractionFlags - && mInstantApp == that.mInstantApp - && mVirtualPreload == that.mVirtualPreload && mEnabledState == that.mEnabledState && mInstallReason == that.mInstallReason && mUninstallReason == that.mUninstallReason @@ -825,16 +855,11 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt // int fieldNameHashCode() { ... } int _hash = 1; + _hash = 31 * _hash + mBooleans; _hash = 31 * _hash + Objects.hashCode(mDisabledComponentsWatched); _hash = 31 * _hash + Objects.hashCode(mEnabledComponentsWatched); _hash = 31 * _hash + Long.hashCode(mCeDataInode); - _hash = 31 * _hash + Boolean.hashCode(mInstalled); - _hash = 31 * _hash + Boolean.hashCode(mStopped); - _hash = 31 * _hash + Boolean.hashCode(mNotLaunched); - _hash = 31 * _hash + Boolean.hashCode(mHidden); _hash = 31 * _hash + mDistractionFlags; - _hash = 31 * _hash + Boolean.hashCode(mInstantApp); - _hash = 31 * _hash + Boolean.hashCode(mVirtualPreload); _hash = 31 * _hash + mEnabledState; _hash = 31 * _hash + mInstallReason; _hash = 31 * _hash + mUninstallReason; @@ -854,10 +879,10 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt } @DataClass.Generated( - time = 1689171513404L, + time = 1691186062924L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java", - inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate boolean mInstalled\nprivate boolean mStopped\nprivate boolean mNotLaunched\nprivate boolean mHidden\nprivate int mDistractionFlags\nprivate boolean mInstantApp\nprivate boolean mVirtualPreload\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nprivate @android.annotation.Nullable com.android.server.pm.pkg.ArchiveState mArchiveState\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setArchiveState(com.android.server.pm.pkg.ArchiveState)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate boolean watchableEquals(com.android.server.utils.Watchable)\nprivate int watchableHashCode()\nprivate boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate int snapshotHashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)") + inputSignatures = "private int mBooleans\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate int mDistractionFlags\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nprivate @android.annotation.Nullable com.android.server.pm.pkg.ArchiveState mArchiveState\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setArchiveState(com.android.server.pm.pkg.ArchiveState)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate boolean watchableEquals(com.android.server.utils.Watchable)\nprivate int watchableHashCode()\nprivate boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate int snapshotHashCode()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isVirtualPreload()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\nprivate static final int INSTALLED\nprivate static final int STOPPED\nprivate static final int NOT_LAUNCHED\nprivate static final int HIDDEN\nprivate static final int INSTANT_APP\nprivate static final int VIRTUAL_PRELOADED\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 75fcca5eec9a..1e6486a17fe7 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -18,7 +18,6 @@ package com.android.server.power; import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.policyToString; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; -import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT; import static android.os.PowerManager.GO_TO_SLEEP_REASON_DISPLAY_GROUPS_TURNED_OFF; import static android.os.PowerManager.GO_TO_SLEEP_REASON_DISPLAY_GROUP_REMOVED; import static android.os.PowerManager.WAKE_REASON_DISPLAY_GROUP_ADDED; @@ -1280,7 +1279,7 @@ public final class PowerManagerService extends SystemService @Override public void onStart() { publishBinderService(Context.POWER_SERVICE, mBinderService, /* allowIsolated= */ false, - DUMP_FLAG_PRIORITY_DEFAULT | DUMP_FLAG_PRIORITY_CRITICAL); + DUMP_FLAG_PRIORITY_CRITICAL); publishLocalService(PowerManagerInternal.class, mLocalService); Watchdog.getInstance().addMonitor(this); diff --git a/services/core/java/com/android/server/power/TEST_MAPPING b/services/core/java/com/android/server/power/TEST_MAPPING index 8374997e3fa2..19086a184068 100644 --- a/services/core/java/com/android/server/power/TEST_MAPPING +++ b/services/core/java/com/android/server/power/TEST_MAPPING @@ -17,15 +17,6 @@ ] }, { - "name": "FrameworksServicesTests", - "options": [ - {"include-filter": "com.android.server.power"}, - {"exclude-filter": "com.android.server.power.BatteryStatsTests"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, - {"exclude-annotation": "androidx.test.filters.FlakyTest"} - ] - }, - { "name": "PowerServiceTests", "options": [ {"include-filter": "com.android.server.power"}, @@ -48,15 +39,13 @@ { "name": "FrameworksServicesTests", "options": [ - {"include-filter": "com.android.server.power"}, - {"exclude-filter": "com.android.server.power.BatteryStatsTests"} + {"include-filter": "com.android.server.power"} ] }, { "name": "PowerServiceTests", "options": [ {"include-filter": "com.android.server.power"}, - {"exclude-filter": "com.android.server.power.BatteryStatsTests"}, {"exclude-annotation": "org.junit.Ignore"} ] } diff --git a/services/core/java/com/android/server/power/batterysaver/TEST_MAPPING b/services/core/java/com/android/server/power/batterysaver/TEST_MAPPING index 17dba7df3f1f..c091b8e68236 100644 --- a/services/core/java/com/android/server/power/batterysaver/TEST_MAPPING +++ b/services/core/java/com/android/server/power/batterysaver/TEST_MAPPING @@ -4,7 +4,13 @@ "name": "CtsLocationCoarseTestCases" }, { - "name": "CtsLocationFineTestCases" + "name": "CtsLocationFineTestCases", + "options": [ + { + // TODO: Wait for test to deflake - b/293934372 + "exclude-filter":"android.location.cts.fine.ScanningSettingsTest" + } + ] }, { "name": "CtsLocationNoneTestCases" diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java index 1a91d252c431..8dcf3e06330f 100644 --- a/services/core/java/com/android/server/power/hint/HintManagerService.java +++ b/services/core/java/com/android/server/power/hint/HintManagerService.java @@ -304,7 +304,8 @@ public final class HintManagerService extends SystemService { return mService; } - private boolean checkTidValid(int uid, int tgid, int [] tids) { + // returns the first invalid tid or null if not found + private Integer checkTidValid(int uid, int tgid, int [] tids) { // Make sure all tids belongs to the same UID (including isolated UID), // tids can belong to different application processes. List<Integer> isolatedPids = null; @@ -326,19 +327,24 @@ public final class HintManagerService extends SystemService { if (isolatedPids == null) { // To avoid deadlock, do not call into AMS if the call is from system. if (uid == Process.SYSTEM_UID) { - return false; + return threadId; } isolatedPids = mAmInternal.getIsolatedProcesses(uid); if (isolatedPids == null) { - return false; + return threadId; } } if (isolatedPids.contains(pidOfThreadId)) { continue; } - return false; + return threadId; } - return true; + return null; + } + + private String formatTidCheckErrMsg(int callingUid, int[] tids, Integer invalidTid) { + return "Tid" + invalidTid + " from list " + Arrays.toString(tids) + + " doesn't belong to the calling application" + callingUid; } @VisibleForTesting @@ -356,8 +362,11 @@ public final class HintManagerService extends SystemService { final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid()); final long identity = Binder.clearCallingIdentity(); try { - if (!checkTidValid(callingUid, callingTgid, tids)) { - throw new SecurityException("Some tid doesn't belong to the application"); + final Integer invalidTid = checkTidValid(callingUid, callingTgid, tids); + if (invalidTid != null) { + final String errMsg = formatTidCheckErrMsg(callingUid, tids, invalidTid); + Slogf.w(TAG, errMsg); + throw new SecurityException(errMsg); } long halSessionPtr = mNativeWrapper.halCreateHintSession(callingTgid, callingUid, @@ -561,8 +570,11 @@ public final class HintManagerService extends SystemService { final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid()); final long identity = Binder.clearCallingIdentity(); try { - if (!checkTidValid(callingUid, callingTgid, tids)) { - throw new SecurityException("Some tid doesn't belong to the application."); + final Integer invalidTid = checkTidValid(callingUid, callingTgid, tids); + if (invalidTid != null) { + final String errMsg = formatTidCheckErrMsg(callingUid, tids, invalidTid); + Slogf.w(TAG, errMsg); + throw new SecurityException(errMsg); } } finally { Binder.restoreCallingIdentity(identity); diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 80c21f458cb7..cf4e845a273b 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -78,6 +78,7 @@ import android.telephony.CellSignalStrengthLte; import android.telephony.CellSignalStrengthNr; import android.telephony.DataConnectionRealTimeInfo; import android.telephony.ModemActivityInfo; +import android.telephony.NetworkRegistrationInfo; import android.telephony.ServiceState; import android.telephony.ServiceState.RegState; import android.telephony.SignalStrength; @@ -181,7 +182,7 @@ public class BatteryStatsImpl extends BatteryStats { // TODO: remove "tcp" from network methods, since we measure total stats. // Current on-disk Parcel version. Must be updated when the format of the parcelable changes - public static final int VERSION = 212; + public static final int VERSION = 213; // The maximum number of names wakelocks we will keep track of // per uid; once the limit is reached, we batch the remaining wakelocks @@ -1041,6 +1042,9 @@ public class BatteryStatsImpl extends BatteryStats { final StopwatchTimer[] mPhoneDataConnectionsTimer = new StopwatchTimer[NUM_DATA_CONNECTION_TYPES]; + int mNrState = -1; + StopwatchTimer mNrNsaTimer; + @RadioAccessTechnology int mActiveRat = RADIO_ACCESS_TECHNOLOGY_OTHER; @@ -6092,15 +6096,16 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") public void notePhoneDataConnectionStateLocked(@NetworkType int dataType, boolean hasData, - @RegState int serviceType, @ServiceState.FrequencyRange int nrFrequency) { - notePhoneDataConnectionStateLocked(dataType, hasData, serviceType, nrFrequency, + @RegState int serviceType, @NetworkRegistrationInfo.NRState int nrState, + @ServiceState.FrequencyRange int nrFrequency) { + notePhoneDataConnectionStateLocked(dataType, hasData, serviceType, nrState, nrFrequency, mClock.elapsedRealtime(), mClock.uptimeMillis()); } @GuardedBy("this") public void notePhoneDataConnectionStateLocked(@NetworkType int dataType, boolean hasData, - @RegState int serviceType, @ServiceState.FrequencyRange int nrFrequency, - long elapsedRealtimeMs, long uptimeMs) { + @RegState int serviceType, @NetworkRegistrationInfo.NRState int nrState, + @ServiceState.FrequencyRange int nrFrequency, long elapsedRealtimeMs, long uptimeMs) { // BatteryStats uses 0 to represent no network type. // Telephony does not have a concept of no network type, and uses 0 to represent unknown. // Unknown is included in DATA_CONNECTION_OTHER. @@ -6123,11 +6128,7 @@ public class BatteryStatsImpl extends BatteryStats { } } - final int newRat = mapNetworkTypeToRadioAccessTechnology(bin); - if (newRat == RADIO_ACCESS_TECHNOLOGY_NR) { - // Note possible frequency change for the NR RAT. - getRatBatteryStatsLocked(newRat).noteFrequencyRange(nrFrequency, elapsedRealtimeMs); - } + if (DEBUG) Log.i(TAG, "Phone Data Connection -> " + dataType + " = " + hasData); if (mPhoneDataConnectionType != bin) { @@ -6138,18 +6139,54 @@ public class BatteryStatsImpl extends BatteryStats { } mPhoneDataConnectionType = bin; mPhoneDataConnectionsTimer[bin].startRunningLocked(elapsedRealtimeMs); + } + + if (mNrState != nrState) { + mHistory.recordNrStateChangeEvent(elapsedRealtimeMs, uptimeMs, nrState); + mNrState = nrState; + } - if (mActiveRat != newRat) { - getRatBatteryStatsLocked(mActiveRat).noteActive(false, elapsedRealtimeMs); - mActiveRat = newRat; + final boolean newNrNsaActive = isNrNsa(bin, nrState); + final boolean nrNsaActive = mNrNsaTimer.isRunningLocked(); + if (newNrNsaActive != nrNsaActive) { + if (newNrNsaActive) { + mNrNsaTimer.startRunningLocked(elapsedRealtimeMs); + } else { + mNrNsaTimer.stopRunningLocked(elapsedRealtimeMs); } - final boolean modemActive = mMobileRadioActiveTimer.isRunningLocked(); - getRatBatteryStatsLocked(newRat).noteActive(modemActive, elapsedRealtimeMs); } + + final int newRat = mapNetworkTypeToRadioAccessTechnology(bin, nrState); + if (newRat == RADIO_ACCESS_TECHNOLOGY_NR) { + // Note possible frequency change for the NR RAT. + getRatBatteryStatsLocked(newRat).noteFrequencyRange(nrFrequency, elapsedRealtimeMs); + } + if (mActiveRat != newRat) { + getRatBatteryStatsLocked(mActiveRat).noteActive(false, elapsedRealtimeMs); + mActiveRat = newRat; + } + final boolean modemActive = mMobileRadioActiveTimer.isRunningLocked(); + getRatBatteryStatsLocked(newRat).noteActive(modemActive, elapsedRealtimeMs); + } + + /** + * Non-standalone (NSA) mode for 5G NR will have an LTE network type. If NR state is + * connected while on an LTE network, the device is in NR NSA mode. + */ + private static boolean isNrNsa(@NetworkType int dataType, + @NetworkRegistrationInfo.NRState int nrState) { + return dataType == TelephonyManager.NETWORK_TYPE_LTE + && nrState == NetworkRegistrationInfo.NR_STATE_CONNECTED; } @RadioAccessTechnology - private static int mapNetworkTypeToRadioAccessTechnology(@NetworkType int dataType) { + private static int mapNetworkTypeToRadioAccessTechnology(@NetworkType int dataType, + @NetworkRegistrationInfo.NRState int nrState) { + if (isNrNsa(dataType, nrState)) { + // Treat an NR NSA connection as RADIO_ACCESS_TECHNOLOGY_NR + return RADIO_ACCESS_TECHNOLOGY_NR; + } + switch (dataType) { case TelephonyManager.NETWORK_TYPE_NR: return RADIO_ACCESS_TECHNOLOGY_NR; @@ -7322,6 +7359,10 @@ public class BatteryStatsImpl extends BatteryStats { return mPhoneDataConnectionsTimer[dataType]; } + @Override public long getNrNsaTime(long elapsedRealtimeUs) { + return mNrNsaTimer.getTotalTimeLocked(elapsedRealtimeUs, STATS_SINCE_CHARGED); + } + @Override public long getActiveRadioDurationMs(@RadioAccessTechnology int rat, @ServiceState.FrequencyRange int frequencyRange, int signalStrength, long elapsedRealtimeMs) { @@ -10955,6 +10996,7 @@ public class BatteryStatsImpl extends BatteryStats { mPhoneDataConnectionsTimer[i] = new StopwatchTimer(mClock, null, -300 - i, null, mOnBatteryTimeBase); } + mNrNsaTimer = new StopwatchTimer(mClock, null, -200 + 2, null, mOnBatteryTimeBase); for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { mNetworkByteActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase); mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase); @@ -11578,6 +11620,7 @@ public class BatteryStatsImpl extends BatteryStats { for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) { mPhoneDataConnectionsTimer[i].reset(false, elapsedRealtimeUs); } + mNrNsaTimer.reset(false, elapsedRealtimeUs); for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { mNetworkByteActivityCounters[i].reset(false, elapsedRealtimeUs); mNetworkPacketActivityCounters[i].reset(false, elapsedRealtimeUs); @@ -15892,6 +15935,7 @@ public class BatteryStatsImpl extends BatteryStats { for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) { mPhoneDataConnectionsTimer[i].readSummaryFromParcelLocked(in); } + mNrNsaTimer.readSummaryFromParcelLocked(in); for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { mNetworkByteActivityCounters[i].readSummaryFromParcelLocked(in); mNetworkPacketActivityCounters[i].readSummaryFromParcelLocked(in); @@ -16395,6 +16439,7 @@ public class BatteryStatsImpl extends BatteryStats { for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) { mPhoneDataConnectionsTimer[i].writeSummaryFromParcelLocked(out, nowRealtime); } + mNrNsaTimer.writeSummaryFromParcelLocked(out, nowRealtime); for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) { mNetworkByteActivityCounters[i].writeSummaryFromParcelLocked(out); mNetworkPacketActivityCounters[i].writeSummaryFromParcelLocked(out); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 614493dca2d8..c909cbe9d4a1 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -6494,6 +6494,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } /** + * @return whether the physical display has a fixed orientation and cannot be rotated. + */ + boolean isDisplayOrientationFixed() { + return (mDisplayInfo.flags & Display.FLAG_ROTATES_WITH_CONTENT) == 0; + } + + /** * @return whether AOD is showing on this display */ boolean isAodShowing() { diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 70edf3a733da..9ef25b6e71b1 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -434,7 +434,8 @@ public class DisplayRotation { final boolean isTv = mContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_LEANBACK); mDefaultFixedToUserRotation = - (isCar || isTv || mService.mIsPc || mDisplayContent.forceDesktopMode()) + (isCar || isTv || mService.mIsPc || mDisplayContent.forceDesktopMode() + || mDisplayContent.isDisplayOrientationFixed()) // For debug purposes the next line turns this feature off with: // $ adb shell setprop config.override_forced_orient true // $ adb shell wm size reset diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 5f3d517b2cdf..02f5c217e5d8 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -81,8 +81,8 @@ class InsetsSourceProvider { private boolean mIsLeashReadyForDispatching; private final Rect mSourceFrame = new Rect(); private final Rect mLastSourceFrame = new Rect(); - private final Rect mLastContainerBounds = new Rect(); private @NonNull Insets mInsetsHint = Insets.NONE; + private boolean mInsetsHintStale = true; private @Flags int mFlagsFromFrameProvider; private @Flags int mFlagsFromServer; @@ -238,6 +238,10 @@ class InsetsSourceProvider { mSource.setFlags(mFlagsFromFrameProvider | mFlagsFromServer); } updateSourceFrameForServerVisibility(); + if (!mLastSourceFrame.equals(mSourceFrame)) { + mLastSourceFrame.set(mSourceFrame); + mInsetsHintStale = true; + } if (mOverrideFrameProviders != null) { // Not necessary to clear the mOverrideFrames here. It will be cleared every time the @@ -279,28 +283,29 @@ class InsetsSourceProvider { // visible. (i.e. No surface, pending insets that were given during layout, etc..) if (mServerVisible) { mSource.setFrame(mSourceFrame); - updateInsetsHint(); } else { mSource.setFrame(0, 0, 0, 0); } } - // To be called when mSourceFrame or the window container bounds is changed. - private void updateInsetsHint() { - if (!mControllable || !mServerVisible) { - return; - } - final Rect bounds = mWindowContainer.getBounds(); - if (mSourceFrame.equals(mLastSourceFrame) && bounds.equals(mLastContainerBounds)) { - return; - } - mLastSourceFrame.set(mSourceFrame); - mLastContainerBounds.set(bounds); - mInsetsHint = mSource.calculateInsets(bounds, true /* ignoreVisibility */); + void onWindowContainerBoundsChanged() { + mInsetsHintStale = true; } @VisibleForTesting Insets getInsetsHint() { + if (!mServerVisible) { + return mInsetsHint; + } + final WindowState win = mWindowContainer.asWindowState(); + if (win != null && win.mGivenInsetsPending) { + return mInsetsHint; + } + if (mInsetsHintStale) { + final Rect bounds = mWindowContainer.getBounds(); + mInsetsHint = mSource.calculateInsets(bounds, true /* ignoreVisibility */); + mInsetsHintStale = false; + } return mInsetsHint; } @@ -359,8 +364,9 @@ class InsetsSourceProvider { mSetLeashPositionConsumer.accept(t); } } - if (!mControl.getInsetsHint().equals(mInsetsHint)) { - mControl.setInsetsHint(mInsetsHint); + final Insets insetsHint = getInsetsHint(); + if (!mControl.getInsetsHint().equals(insetsHint)) { + mControl.setInsetsHint(insetsHint); changed = true; } if (changed) { @@ -494,7 +500,7 @@ class InsetsSourceProvider { mControlTarget = target; updateVisibility(); mControl = new InsetsSourceControl(mSource.getId(), mSource.getType(), leash, - mClientVisible, surfacePosition, mInsetsHint); + mClientVisible, surfacePosition, getInsetsHint()); ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "InsetsSource Control %s for target %s", mControl, mControlTarget); @@ -605,6 +611,9 @@ class InsetsSourceProvider { if (mControllable) { pw.print(prefix + "mInsetsHint="); pw.print(mInsetsHint); + if (mInsetsHintStale) { + pw.print(" stale"); + } pw.println(); } pw.print(prefix); diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index e80cbb302424..9af12ad6e766 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -1851,9 +1851,17 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { 0 /* launchFlags */); task.reparent(launchRoot == null ? toDisplayArea : launchRoot, POSITION_TOP); - // Set the windowing mode to undefined by default to let the root task inherited the - // windowing mode. - task.setWindowingMode(WINDOWING_MODE_UNDEFINED); + // If the task is going to be reparented to the non-fullscreen root TDA and the task + // is set to FULLSCREEN explicitly, we keep the windowing mode as is. Otherwise, the + // task will inherit the display windowing mode unexpectedly. + final boolean keepWindowingMode = launchRoot == null + && task.getRequestedOverrideWindowingMode() == WINDOWING_MODE_FULLSCREEN + && toDisplayArea.getWindowingMode() != WINDOWING_MODE_FULLSCREEN; + if (!keepWindowingMode) { + // Set the windowing mode to undefined to let the root task inherited the + // windowing mode. + task.setWindowingMode(WINDOWING_MODE_UNDEFINED); + } lastReparentedRootTask = task; } // Root task may be removed from this display. Ensure each root task will be processed diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 83949ccae366..d8cc8d386424 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -86,7 +86,6 @@ import android.graphics.Point; import android.graphics.Rect; import android.hardware.HardwareBuffer; import android.os.IBinder; -import android.os.RemoteException; import android.os.UserHandle; import android.util.DisplayMetrics; import android.util.Slog; @@ -1325,14 +1324,12 @@ class TaskFragment extends WindowContainer<WindowContainer> { } } - // Launching this app's activity, make sure the app is no longer - // considered stopped. try { mTaskSupervisor.getActivityMetricsLogger() .notifyBeforePackageUnstopped(next.packageName); - mAtmService.getPackageManager().setPackageStoppedState( - next.packageName, false, next.mUserId); /* TODO: Verify if correct userid */ - } catch (RemoteException e1) { + mAtmService.getPackageManagerInternalLocked().notifyComponentUsed( + next.packageName, next.mUserId, + next.packageName); /* TODO: Verify if correct userid */ } catch (IllegalArgumentException e) { Slog.w(TAG, "Failed trying to unstop package " + next.packageName + ": " + e); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 7a904f810b16..aad17aa16c7f 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -959,20 +959,25 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } // Need to update layers on involved displays since they were all paused while // the animation played. This puts the layers back into the correct order. - mController.mBuildingFinishLayers = true; - try { - for (int i = displays.size() - 1; i >= 0; --i) { - if (displays.valueAt(i) == null) continue; - displays.valueAt(i).assignChildLayers(t); - } - } finally { - mController.mBuildingFinishLayers = false; + for (int i = displays.size() - 1; i >= 0; --i) { + if (displays.valueAt(i) == null) continue; + updateDisplayLayers(displays.valueAt(i), t); } + for (int i = 0; i < info.getRootCount(); ++i) { t.reparent(info.getRoot(i).getLeash(), null); } } + private static void updateDisplayLayers(DisplayContent dc, SurfaceControl.Transaction t) { + dc.mTransitionController.mBuildingFinishLayers = true; + try { + dc.assignChildLayers(t); + } finally { + dc.mTransitionController.mBuildingFinishLayers = false; + } + } + /** * Build a transaction that cleans-up transition-only surfaces (transition root and snapshots). * This will ALWAYS be applied on transition finish just in-case @@ -2346,8 +2351,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { final WindowContainer<?> wc = sortedTargets.get(i).mContainer; // Don't include wallpapers since they are in a different DA. if (isWallpaper(wc)) continue; - final int endDisplayId = getDisplayId(wc); - if (endDisplayId < 0) continue; + final DisplayContent dc = wc.getDisplayContent(); + if (dc == null) continue; + final int endDisplayId = dc.getDisplayId(); // Check if Root was already created for this display with a higher-Z window if (outInfo.findRootIndex(endDisplayId) >= 0) continue; @@ -2369,6 +2375,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName( "Transition Root: " + leashReference.getName()).build(); rootLeash.setUnreleasedWarningCallSite("Transition.calculateTransitionRoots"); + // Update layers to start transaction because we prevent assignment during collect, so + // the layer of transition root can be correct. + updateDisplayLayers(dc, startT); startT.setLayer(rootLeash, leashReference.getLastLayer()); outInfo.addRootLeash(endDisplayId, rootLeash, ancestor.getBounds().left, ancestor.getBounds().top); diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index ae05725f81ff..6432ff081c73 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -558,7 +558,9 @@ class TransitionController { return wc.asWindowState() == null; } // Always allow WindowState to assign layers since it won't affect transition. - return wc.asWindowState() != null || !isPlaying(); + return wc.asWindowState() != null || (!isPlaying() + // Don't assign task while collecting. + && !(wc.asTask() != null && isCollecting())); } @WindowConfiguration.WindowingMode diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 457a555416c1..dae61da26b68 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -1143,6 +1143,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } void onResize() { + if (mControllableInsetProvider != null) { + mControllableInsetProvider.onWindowContainerBoundsChanged(); + } for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); wc.onParentResize(); @@ -1162,6 +1165,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } void onMovedByResize() { + if (mControllableInsetProvider != null) { + mControllableInsetProvider.onWindowContainerBoundsChanged(); + } for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); wc.onMovedByResize(); diff --git a/services/core/java/com/android/server/wm/WindowContextListenerController.java b/services/core/java/com/android/server/wm/WindowContextListenerController.java index 726ae5c61d56..21f251fbc736 100644 --- a/services/core/java/com/android/server/wm/WindowContextListenerController.java +++ b/services/core/java/com/android/server/wm/WindowContextListenerController.java @@ -26,7 +26,8 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_ERROR; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.IWindowToken; +import android.app.servertransaction.WindowContextInfoChangeItem; +import android.app.servertransaction.WindowContextWindowRemovalItem; import android.content.Context; import android.content.res.Configuration; import android.os.Bundle; @@ -140,7 +141,7 @@ class WindowContextListenerController { final WindowContextListenerImpl listener = mListeners.valueAt(i); if (listener.getWindowContainer().getDisplayContent().getDisplayId() == displayId && listener.mHasPendingConfiguration) { - listener.reportConfigToWindowTokenClient(); + listener.dispatchWindowContextInfoChange(); } } } @@ -206,7 +207,7 @@ class WindowContextListenerController { @NonNull private final WindowProcessController mWpc; @NonNull - private final IWindowToken mClientToken; + private final IBinder mClientToken; @NonNull private WindowContainer<?> mContainer; /** @@ -228,7 +229,7 @@ class WindowContextListenerController { @NonNull IBinder clientToken, @NonNull WindowContainer<?> container, @WindowType int type, @Nullable Bundle options) { mWpc = Objects.requireNonNull(wpc); - mClientToken = IWindowToken.Stub.asInterface(clientToken); + mClientToken = clientToken; mContainer = Objects.requireNonNull(container); mType = type; mOptions = options; @@ -270,7 +271,7 @@ class WindowContextListenerController { } private void register(boolean shouldDispatchConfig) { - final IBinder token = mClientToken.asBinder(); + final IBinder token = mClientToken; if (mDeathRecipient == null) { throw new IllegalStateException("Invalid client token: " + token); } @@ -280,7 +281,7 @@ class WindowContextListenerController { private void unregister() { mContainer.unregisterWindowContainerListener(this); - mListeners.remove(mClientToken.asBinder()); + mListeners.remove(mClientToken); } private void clear() { @@ -290,17 +291,17 @@ class WindowContextListenerController { @Override public void onMergedOverrideConfigurationChanged(Configuration mergedOverrideConfig) { - reportConfigToWindowTokenClient(); + dispatchWindowContextInfoChange(); } @Override public void onDisplayChanged(DisplayContent dc) { - reportConfigToWindowTokenClient(); + dispatchWindowContextInfoChange(); } - private void reportConfigToWindowTokenClient() { + private void dispatchWindowContextInfoChange() { if (mDeathRecipient == null) { - throw new IllegalStateException("Invalid client token: " + mClientToken.asBinder()); + throw new IllegalStateException("Invalid client token: " + mClientToken); } final DisplayContent dc = mContainer.getDisplayContent(); if (!dc.isReady()) { @@ -329,19 +330,15 @@ class WindowContextListenerController { mLastReportedConfig.setTo(config); mLastReportedDisplay = displayId; - try { - // TODO(b/290876897): migrate to dispatch through wpc - mClientToken.onConfigurationChanged(config, displayId); - } catch (RemoteException e) { - ProtoLog.w(WM_ERROR, "Could not report config changes to the window token client."); - } + mWpc.scheduleClientTransactionItem(WindowContextInfoChangeItem.obtain( + mClientToken, config, displayId)); mHasPendingConfiguration = false; } @Override public void onRemoved() { if (mDeathRecipient == null) { - throw new IllegalStateException("Invalid client token: " + mClientToken.asBinder()); + throw new IllegalStateException("Invalid client token: " + mClientToken); } final WindowToken windowToken = mContainer.asWindowToken(); if (windowToken != null && windowToken.isFromClient()) { @@ -359,18 +356,13 @@ class WindowContextListenerController { } } mDeathRecipient.unlinkToDeath(); - try { - // TODO(b/290876897): migrate to dispatch through wpc - mClientToken.onWindowTokenRemoved(); - } catch (RemoteException e) { - ProtoLog.w(WM_ERROR, "Could not report token removal to the window token client."); - } + mWpc.scheduleClientTransactionItem(WindowContextWindowRemovalItem.obtain(mClientToken)); unregister(); } @Override public String toString() { - return "WindowContextListenerImpl{clientToken=" + mClientToken.asBinder() + ", " + return "WindowContextListenerImpl{clientToken=" + mClientToken + ", " + "container=" + mContainer + "}"; } @@ -384,11 +376,11 @@ class WindowContextListenerController { } void linkToDeath() throws RemoteException { - mClientToken.asBinder().linkToDeath(this, 0); + mClientToken.linkToDeath(this, 0); } void unlinkToDeath() { - mClientToken.asBinder().unlinkToDeath(this, 0); + mClientToken.unlinkToDeath(this, 0); } } } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index a84749aa6643..427ab7efed40 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -309,6 +309,14 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub applyTransaction(wct, -1 /* syncId */, nextTransition, caller, deferred); if (needsSetReady) { + // TODO(b/294925498): Remove this once we have accurate ready + // tracking. + if (hasActivityLaunch(wct) && !mService.mRootWindowContainer + .allPausedActivitiesComplete()) { + // WCT is launching an activity, so we need to wait for its + // lifecycle events. + return; + } nextTransition.setAllReady(); } }); @@ -344,6 +352,15 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } } + private static boolean hasActivityLaunch(WindowContainerTransaction wct) { + for (int i = 0; i < wct.getHierarchyOps().size(); ++i) { + if (wct.getHierarchyOps().get(i).getType() == HIERARCHY_OP_TYPE_LAUNCH_TASK) { + return true; + } + } + return false; + } + @Override public int startLegacyTransition(int type, @NonNull RemoteAnimationAdapter adapter, @NonNull IWindowContainerTransactionCallback callback, @@ -382,18 +399,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } @Override - public int finishTransition(@NonNull IBinder transitionToken, - @Nullable WindowContainerTransaction t, - @Nullable IWindowContainerTransactionCallback callback) { + public void finishTransition(@NonNull IBinder transitionToken, + @Nullable WindowContainerTransaction t) { enforceTaskPermission("finishTransition()"); final CallerInfo caller = new CallerInfo(); final long ident = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { - int syncId = -1; - if (t != null && callback != null) { - syncId = startSyncWithOrganizer(callback); - } final Transition transition = Transition.fromBinder(transitionToken); // apply the incoming transaction before finish in case it alters the visibility // of the participants. @@ -402,14 +414,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub // changes of the transition participants will only set visible-requested // and still let finishTransition handle the participants. mTransitionController.mFinishingTransition = transition; - applyTransaction(t, syncId, null /*transition*/, caller, transition); + applyTransaction(t, -1 /* syncId */, null /*transition*/, caller, transition); } mTransitionController.finishTransition(transition); mTransitionController.mFinishingTransition = null; - if (syncId >= 0) { - setSyncReady(syncId); - } - return syncId; } } finally { Binder.restoreCallingIdentity(ident); diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index d7d2b4e9dde2..83e864673fa8 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -55,6 +55,7 @@ import android.app.ActivityThread; import android.app.BackgroundStartPrivileges; import android.app.IApplicationThread; import android.app.ProfilerInfo; +import android.app.servertransaction.ClientTransactionItem; import android.app.servertransaction.ConfigurationChangeItem; import android.content.ComponentName; import android.content.Context; @@ -1584,9 +1585,10 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio resolvedConfig.seq = newParentConfig.seq; } - void dispatchConfiguration(Configuration config) { + void dispatchConfiguration(@NonNull Configuration config) { mHasPendingConfigurationChange = false; - if (mThread == null) { + final IApplicationThread thread = mThread; + if (thread == null) { if (Build.IS_DEBUGGABLE && mHasImeService) { // TODO (b/135719017): Temporary log for debugging IME service. Slog.w(TAG_CONFIGURATION, "Unable to send config for IME proc " + mName @@ -1611,10 +1613,11 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio } } - scheduleConfigurationChange(mThread, config); + scheduleConfigurationChange(thread, config); } - private void scheduleConfigurationChange(IApplicationThread thread, Configuration config) { + private void scheduleConfigurationChange(@NonNull IApplicationThread thread, + @NonNull Configuration config) { ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending to proc %s new config %s", mName, config); if (Build.IS_DEBUGGABLE && mHasImeService) { @@ -1622,11 +1625,30 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio Slog.v(TAG_CONFIGURATION, "Sending to IME proc " + mName + " new config " + config); } mHasCachedConfiguration = false; + scheduleClientTransactionItem(thread, ConfigurationChangeItem.obtain( + config, mLastTopActivityDeviceId)); + } + + @VisibleForTesting + void scheduleClientTransactionItem(@NonNull ClientTransactionItem transactionItem) { + final IApplicationThread thread = mThread; + if (thread == null) { + if (Build.IS_DEBUGGABLE) { + Slog.w(TAG_CONFIGURATION, "Unable to send transaction to client proc " + mName + + ": no app thread"); + } + return; + } + scheduleClientTransactionItem(thread, transactionItem); + } + + private void scheduleClientTransactionItem(@NonNull IApplicationThread thread, + @NonNull ClientTransactionItem transactionItem) { try { - mAtm.getLifecycleManager().scheduleTransaction(thread, - ConfigurationChangeItem.obtain(config, mLastTopActivityDeviceId)); + mAtm.getLifecycleManager().scheduleTransaction(thread, transactionItem); } catch (Exception e) { - Slog.e(TAG_CONFIGURATION, "Failed to schedule configuration change: " + mOwner, e); + Slog.e(TAG_CONFIGURATION, "Failed to schedule ClientTransactionItem=" + + transactionItem + " owner=" + mOwner, e); } } diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index e1de05cf6c7c..11c40d7bcd9b 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -69,7 +69,6 @@ static jclass class_gnssPowerStats; -static jmethodID method_reportNiNotification; static jmethodID method_reportGnssPowerStats; static jmethodID method_reportNfwNotification; static jmethodID method_isInEmergencySession; @@ -92,8 +91,6 @@ using android::hardware::hidl_death_recipient; using android::hardware::gnss::V1_0::GnssLocationFlags; using android::hardware::gnss::V1_0::IGnssNavigationMessage; using android::hardware::gnss::V1_0::IGnssNavigationMessageCallback; -using android::hardware::gnss::V1_0::IGnssNi; -using android::hardware::gnss::V1_0::IGnssNiCallback; using android::hardware::gnss::V1_0::IGnssXtra; using android::hardware::gnss::V1_0::IGnssXtraCallback; using android::hardware::gnss::V2_0::ElapsedRealtimeFlags; @@ -127,7 +124,6 @@ using IGnssConfigurationAidl = android::hardware::gnss::IGnssConfiguration; using GnssLocationAidl = android::hardware::gnss::GnssLocation; using IGnssAntennaInfoAidl = android::hardware::gnss::IGnssAntennaInfo; -sp<IGnssNi> gnssNiIface = nullptr; sp<IGnssPowerIndication> gnssPowerIndicationIface = nullptr; std::unique_ptr<android::gnss::GnssHal> gnssHal = nullptr; @@ -195,42 +191,6 @@ Status GnssPowerIndicationCallback::gnssPowerStatsCb(const GnssPowerStats& data) return Status::ok(); } -/* - * GnssNiCallback implements callback methods required by the IGnssNi interface. - */ -struct GnssNiCallback : public IGnssNiCallback { - Return<void> niNotifyCb(const IGnssNiCallback::GnssNiNotification& notification) - override; -}; - -Return<void> GnssNiCallback::niNotifyCb( - const IGnssNiCallback::GnssNiNotification& notification) { - JNIEnv* env = getJniEnv(); - jstring requestorId = env->NewStringUTF(notification.requestorId.c_str()); - jstring text = env->NewStringUTF(notification.notificationMessage.c_str()); - - if (requestorId && text) { - env->CallVoidMethod(mCallbacksObj, method_reportNiNotification, - notification.notificationId, notification.niType, - notification.notifyFlags, notification.timeoutSec, - notification.defaultResponse, requestorId, text, - notification.requestorIdEncoding, - notification.notificationIdEncoding); - } else { - ALOGE("%s: OOM Error\n", __func__); - } - - if (requestorId) { - env->DeleteLocalRef(requestorId); - } - - if (text) { - env->DeleteLocalRef(text); - } - checkAndClearExceptionFromCallback(env, __FUNCTION__); - return Void(); -} - /* Initializes the GNSS service handle. */ static void android_location_gnss_hal_GnssNative_set_gps_service_handle() { gnssHal = std::make_unique<gnss::GnssHal>(); @@ -242,10 +202,6 @@ static void android_location_gnss_hal_GnssNative_class_init_once(JNIEnv* env, jc android_location_gnss_hal_GnssNative_set_gps_service_handle(); // Cache methodIDs and class IDs. - - method_reportNiNotification = env->GetMethodID(clazz, "reportNiNotification", - "(IIIIILjava/lang/String;Ljava/lang/String;II)V"); - method_reportNfwNotification = env->GetMethodID(clazz, "reportNfwNotification", "(Ljava/lang/String;BLjava/lang/String;BLjava/lang/String;BZZ)V"); method_reportGnssPowerStats = @@ -305,7 +261,6 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject gnssAntennaInfoIface = gnssHal->getGnssAntennaInfoInterface(); gnssMeasurementCorrectionsIface = gnssHal->getMeasurementCorrectionsInterface(); gnssDebugIface = gnssHal->getGnssDebugInterface(); - gnssNiIface = gnssHal->getGnssNiInterface(); gnssConfigurationIface = gnssHal->getGnssConfigurationInterface(); gnssGeofencingIface = gnssHal->getGnssGeofenceInterface(); gnssBatchingIface = gnssHal->getGnssBatchingInterface(); @@ -376,15 +331,6 @@ static jboolean android_location_gnss_hal_GnssNative_init(JNIEnv* /* env */, jcl ALOGI("Unable to initialize IGnssGeofencing interface."); } - // Set IGnssNi.hal callback. - sp<IGnssNiCallback> gnssNiCbIface = new GnssNiCallback(); - if (gnssNiIface != nullptr) { - auto status = gnssNiIface->setCallback(gnssNiCbIface); - checkHidlReturn(status, "IGnssNi setCallback() failed."); - } else { - ALOGI("Unable to initialize IGnssNi interface."); - } - // Set IAGnssRil callback. if (agnssRilIface == nullptr || !agnssRilIface->setCallback(std::make_unique<gnss::AGnssRilCallback>())) { @@ -592,18 +538,6 @@ static void android_location_gnss_hal_GnssNative_set_agps_server(JNIEnv* env, jc } } -static void android_location_gnss_hal_GnssNative_send_ni_response(JNIEnv* /* env */, jclass, - jint notifId, jint response) { - if (gnssNiIface == nullptr) { - ALOGE("%s: IGnssNi interface not available.", __func__); - return; - } - - auto result = gnssNiIface->respond(notifId, - static_cast<IGnssNiCallback::GnssUserResponseType>(response)); - checkHidlReturn(result, "IGnssNi respond() failed."); -} - static jstring android_location_gnss_hal_GnssNative_get_internal_state(JNIEnv* env, jclass) { /* * TODO: Create a jobject to represent GnssDebug. @@ -987,8 +921,6 @@ static const JNINativeMethod sLocationProviderMethods[] = { reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_set_agps_server)}, {"native_inject_ni_supl_message_data", "([BII)V", reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_inject_ni_supl_message_data)}, - {"native_send_ni_response", "(II)V", - reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_send_ni_response)}, {"native_get_internal_state", "()Ljava/lang/String;", reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_get_internal_state)}, {"native_is_gnss_visibility_control_supported", "()Z", diff --git a/services/incremental/TEST_MAPPING b/services/incremental/TEST_MAPPING index cdb1fc6f4079..4af880ded953 100644 --- a/services/incremental/TEST_MAPPING +++ b/services/incremental/TEST_MAPPING @@ -27,7 +27,9 @@ }, { "name": "CtsInstalledLoadingProgressHostTests" - }, + } + ], + "presubmit-large": [ { "name": "CtsPackageManagerTestCases", "options": [ diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index caa434366a17..57fa12d20de5 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -354,8 +354,6 @@ public final class SystemServer implements Dumpable { "com.android.server.contentcapture.ContentCaptureManagerService"; private static final String TRANSLATION_MANAGER_SERVICE_CLASS = "com.android.server.translation.TranslationManagerService"; - private static final String SELECTION_TOOLBAR_MANAGER_SERVICE_CLASS = - "com.android.server.selectiontoolbar.SelectionToolbarManagerService"; private static final String MUSIC_RECOGNITION_MANAGER_SERVICE_CLASS = "com.android.server.musicrecognition.MusicRecognitionManagerService"; private static final String AMBIENT_CONTEXT_MANAGER_SERVICE_CLASS = @@ -2738,13 +2736,6 @@ public final class SystemServer implements Dumpable { Slog.d(TAG, "TranslationService not defined by OEM"); } - if (!isTv) { - // Selection toolbar service - t.traceBegin("StartSelectionToolbarManagerService"); - mSystemServiceManager.startService(SELECTION_TOOLBAR_MANAGER_SERVICE_CLASS); - t.traceEnd(); - } - // NOTE: ClipboardService depends on ContentCapture and Autofill t.traceBegin("StartClipboardService"); mSystemServiceManager.startService(ClipboardService.class); diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt index 6a349e237ffe..17474fbe1de4 100644 --- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt @@ -26,7 +26,6 @@ import com.android.server.permission.access.collection.* // ktlint-disable no-wi import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports import com.android.server.permission.access.immutable.IndexedMap import com.android.server.permission.access.permission.AppIdPermissionPolicy -import com.android.server.permission.access.permission.DevicePermissionPolicy import com.android.server.permission.access.util.attributeInt import com.android.server.permission.access.util.attributeInterned import com.android.server.permission.access.util.forEachTag @@ -47,7 +46,6 @@ class AccessPolicy private constructor( getOrPut(policy.subjectScheme) { MutableIndexedMap() }[policy.objectScheme] = policy } addPolicy(AppIdPermissionPolicy()) - addPolicy(DevicePermissionPolicy()) addPolicy(AppIdAppOpPolicy()) addPolicy(PackageAppOpPolicy()) } as IndexedMap<String, IndexedMap<String, SchemePolicy>> diff --git a/services/permission/java/com/android/server/permission/access/AccessState.kt b/services/permission/java/com/android/server/permission/access/AccessState.kt index 94c878a453c9..4ec32ea53f28 100644 --- a/services/permission/java/com/android/server/permission/access/AccessState.kt +++ b/services/permission/java/com/android/server/permission/access/AccessState.kt @@ -329,18 +329,6 @@ typealias MutableAppIdPermissionFlags = private typealias AppIdPermissionFlagsReference = MutableReference<AppIdPermissionFlags, MutableAppIdPermissionFlags> - -typealias DevicePermissionFlags = - IndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>> -typealias MutableDevicePermissionFlags = - MutableIndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>> -typealias AppIdDevicePermissionFlags = - IntReferenceMap<DevicePermissionFlags, MutableDevicePermissionFlags> -typealias MutableAppIdDevicePermissionFlags = - MutableIntReferenceMap<DevicePermissionFlags, MutableDevicePermissionFlags> -private typealias AppIdDevicePermissionFlagsReference = - MutableReference<AppIdDevicePermissionFlags, MutableAppIdDevicePermissionFlags> - typealias AppIdAppOpModes = IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>> typealias MutableAppIdAppOpModes = @@ -358,7 +346,6 @@ private typealias PackageAppOpModesReference = sealed class UserState( internal val packageVersionsReference: PackageVersionsReference, internal val appIdPermissionFlagsReference: AppIdPermissionFlagsReference, - internal val appIdDevicePermissionFlagsReference: AppIdDevicePermissionFlagsReference, internal val appIdAppOpModesReference: AppIdAppOpModesReference, internal val packageAppOpModesReference: PackageAppOpModesReference, defaultPermissionGrantFingerprint: String?, @@ -370,9 +357,6 @@ sealed class UserState( val appIdPermissionFlags: AppIdPermissionFlags get() = appIdPermissionFlagsReference.get() - val appIdDevicePermissionFlags: AppIdDevicePermissionFlags - get() = appIdDevicePermissionFlagsReference.get() - val appIdAppOpModes: AppIdAppOpModes get() = appIdAppOpModesReference.get() @@ -391,7 +375,6 @@ sealed class UserState( class MutableUserState private constructor( packageVersionsReference: PackageVersionsReference, appIdPermissionFlagsReference: AppIdPermissionFlagsReference, - appIdDevicePermissionFlagsReference: AppIdDevicePermissionFlagsReference, appIdAppOpModesReference: AppIdAppOpModesReference, packageAppOpModesReference: PackageAppOpModesReference, defaultPermissionGrantFingerprint: String?, @@ -399,7 +382,6 @@ class MutableUserState private constructor( ) : UserState( packageVersionsReference, appIdPermissionFlagsReference, - appIdDevicePermissionFlagsReference, appIdAppOpModesReference, packageAppOpModesReference, defaultPermissionGrantFingerprint, @@ -408,7 +390,6 @@ class MutableUserState private constructor( constructor() : this( PackageVersionsReference(MutableIndexedMap<String, Int>()), AppIdPermissionFlagsReference(MutableAppIdPermissionFlags()), - AppIdDevicePermissionFlagsReference(MutableAppIdDevicePermissionFlags()), AppIdAppOpModesReference(MutableAppIdAppOpModes()), PackageAppOpModesReference(MutablePackageAppOpModes()), null, @@ -418,7 +399,6 @@ class MutableUserState private constructor( internal constructor(userState: UserState) : this( userState.packageVersionsReference.toImmutable(), userState.appIdPermissionFlagsReference.toImmutable(), - userState.appIdDevicePermissionFlagsReference.toImmutable(), userState.appIdAppOpModesReference.toImmutable(), userState.packageAppOpModesReference.toImmutable(), userState.defaultPermissionGrantFingerprint, @@ -430,9 +410,6 @@ class MutableUserState private constructor( fun mutateAppIdPermissionFlags(): MutableAppIdPermissionFlags = appIdPermissionFlagsReference.mutate() - fun mutateAppIdDevicePermissionFlags(): MutableAppIdDevicePermissionFlags = - appIdDevicePermissionFlagsReference.mutate() - fun mutateAppIdAppOpModes(): MutableAppIdAppOpModes = appIdAppOpModesReference.mutate() fun mutatePackageAppOpModes(): MutablePackageAppOpModes = packageAppOpModesReference.mutate() diff --git a/services/permission/java/com/android/server/permission/access/AccessUri.kt b/services/permission/java/com/android/server/permission/access/AccessUri.kt index 1d46ca71fe0f..d1abc0455245 100644 --- a/services/permission/java/com/android/server/permission/access/AccessUri.kt +++ b/services/permission/java/com/android/server/permission/access/AccessUri.kt @@ -65,17 +65,6 @@ data class PermissionUri( } } -data class DevicePermissionUri( - val permissionName: String, - val deviceId: Int -) : AccessUri(SCHEME) { - override fun toString(): String = "$scheme:///$permissionName/$deviceId" - - companion object { - const val SCHEME = "device-permission" - } -} - data class UidUri( val uid: Int ) : AccessUri(SCHEME) { diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt deleted file mode 100644 index 37a4a90f8f80..000000000000 --- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) 2023 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.permission.access.permission - -import android.util.Slog -import com.android.modules.utils.BinaryXmlPullParser -import com.android.modules.utils.BinaryXmlSerializer -import com.android.server.permission.access.AccessState -import com.android.server.permission.access.DevicePermissionFlags -import com.android.server.permission.access.MutableAccessState -import com.android.server.permission.access.MutableAppIdDevicePermissionFlags -import com.android.server.permission.access.MutableDevicePermissionFlags -import com.android.server.permission.access.WriteMode -import com.android.server.permission.access.immutable.IndexedMap -import com.android.server.permission.access.immutable.MutableIndexedMap -import com.android.server.permission.access.immutable.forEachIndexed -import com.android.server.permission.access.immutable.forEachReversedIndexed -import com.android.server.permission.access.immutable.set -import com.android.server.permission.access.util.andInv -import com.android.server.permission.access.util.attributeInt -import com.android.server.permission.access.util.attributeInterned -import com.android.server.permission.access.util.forEachTag -import com.android.server.permission.access.util.getAttributeIntOrThrow -import com.android.server.permission.access.util.getAttributeValueOrThrow -import com.android.server.permission.access.util.hasBits -import com.android.server.permission.access.util.tag -import com.android.server.permission.access.util.tagName - -class DevicePermissionPersistence { - fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) { - when (tagName) { - TAG_APP_ID_DEVICE_PERMISSIONS -> parseAppIdDevicePermissions(state, userId) - else -> {} - } - } - - private fun BinaryXmlPullParser.parseAppIdDevicePermissions( - state: MutableAccessState, - userId: Int - ) { - val userState = state.mutateUserState(userId, WriteMode.NONE)!! - val appIdDevicePermissionFlags = userState.mutateAppIdDevicePermissionFlags() - forEachTag { - when (tagName) { - TAG_APP_ID -> parseAppId(appIdDevicePermissionFlags) - else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state") - } - } - - appIdDevicePermissionFlags.forEachReversedIndexed { appIdIndex, appId, _ -> - if (appId !in state.externalState.appIdPackageNames) { - Slog.w(LOG_TAG, "Dropping unknown app ID $appId when parsing permission state") - appIdDevicePermissionFlags.removeAt(appIdIndex) - userState.requestWriteMode(WriteMode.ASYNCHRONOUS) - } - } - } - - private fun BinaryXmlPullParser.parseAppId( - appIdPermissionFlags: MutableAppIdDevicePermissionFlags - ) { - val appId = getAttributeIntOrThrow(ATTR_ID) - val devicePermissionFlags = MutableDevicePermissionFlags() - appIdPermissionFlags[appId] = devicePermissionFlags - forEachTag { - when (tagName) { - TAG_DEVICE -> parseDevice(devicePermissionFlags) - else -> { - Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state") - } - } - } - } - - private fun BinaryXmlPullParser.parseDevice( - deviceIdPermissionFlags: MutableDevicePermissionFlags - ) { - val deviceId = getAttributeValueOrThrow(ATTR_ID) - val permissionFlags = MutableIndexedMap<String, Int>() - deviceIdPermissionFlags.put(deviceId, permissionFlags) - forEachTag { - when (tagName) { - TAG_PERMISSION -> parsePermission(permissionFlags) - else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state") - } - } - } - - private fun BinaryXmlPullParser.parsePermission( - permissionFlags: MutableIndexedMap<String, Int> - ) { - val name = getAttributeValueOrThrow(ATTR_NAME).intern() - val flags = getAttributeIntOrThrow(ATTR_FLAGS) - permissionFlags[name] = flags - } - - fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int) { - val appIdDevicePermissionFlags = state.userStates[userId]!!.appIdDevicePermissionFlags - tag(TAG_APP_ID_DEVICE_PERMISSIONS) { - appIdDevicePermissionFlags.forEachIndexed { _, appId, devicePermissionFlags -> - serializeAppId(appId, devicePermissionFlags) - } - } - } - - private fun BinaryXmlSerializer.serializeAppId( - appId: Int, - devicePermissionFlags: DevicePermissionFlags - ) { - tag(TAG_APP_ID) { - attributeInt(ATTR_ID, appId) - devicePermissionFlags.forEachIndexed { _, deviceId, permissionFlags -> - serializeDevice(deviceId, permissionFlags) - } - } - } - - private fun BinaryXmlSerializer.serializeDevice( - deviceId: String, - permissionFlags: IndexedMap<String, Int> - ) { - tag(TAG_DEVICE) { - attributeInterned(ATTR_ID, deviceId) - permissionFlags.forEachIndexed { _, name, flags -> - serializePermission(name, flags) - } - } - } - - private fun BinaryXmlSerializer.serializePermission(name: String, flags: Int) { - tag(TAG_PERMISSION) { - attributeInterned(ATTR_NAME, name) - // Never serialize one-time permissions as granted. - val serializedFlags = if (flags.hasBits(PermissionFlags.ONE_TIME)) { - flags andInv PermissionFlags.RUNTIME_GRANTED - } else { - flags - } - attributeInt(ATTR_FLAGS, serializedFlags) - } - } - - companion object { - private val LOG_TAG = DevicePermissionPersistence::class.java.simpleName - - private const val TAG_APP_ID_DEVICE_PERMISSIONS = "app-id-device-permissions" - private const val TAG_APP_ID = "app-id" - private const val TAG_DEVICE = "device" - private const val TAG_PERMISSION = "permission" - - private const val ATTR_ID = "id" - private const val ATTR_NAME = "name" - private const val ATTR_FLAGS = "flags" - } -}
\ No newline at end of file diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt deleted file mode 100644 index c0d7546180bf..000000000000 --- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt +++ /dev/null @@ -1,276 +0,0 @@ -/* - * Copyright (C) 2023 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.permission.access.permission - -import android.util.Slog -import com.android.modules.utils.BinaryXmlPullParser -import com.android.modules.utils.BinaryXmlSerializer -import com.android.server.permission.access.AccessState -import com.android.server.permission.access.DevicePermissionUri -import com.android.server.permission.access.GetStateScope -import com.android.server.permission.access.MutableAccessState -import com.android.server.permission.access.MutateStateScope -import com.android.server.permission.access.SchemePolicy -import com.android.server.permission.access.UidUri -import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports -import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports -import com.android.server.permission.access.util.andInv -import com.android.server.pm.pkg.PackageState - -class DevicePermissionPolicy : SchemePolicy() { - private val persistence = DevicePermissionPersistence() - - @Volatile - private var listeners: IndexedListSet<OnDevicePermissionFlagsChangedListener> = - MutableIndexedListSet() - private val listenersLock = Any() - - override val subjectScheme: String - get() = UidUri.SCHEME - - override val objectScheme: String - get() = DevicePermissionUri.SCHEME - - override fun GetStateScope.onStateMutated() { - listeners.forEachIndexed { _, it -> it.onStateMutated() } - } - - override fun MutateStateScope.onAppIdRemoved(appId: Int) { - newState.userStates.forEachIndexed { userStateIndex, _, userState -> - if (appId in userState.appIdDevicePermissionFlags) { - newState.mutateUserStateAt(userStateIndex) - .mutateAppIdDevicePermissionFlags() -= appId - } - } - } - - override fun MutateStateScope.onStorageVolumeMounted( - volumeUuid: String?, - packageNames: List<String>, - isSystemUpdated: Boolean - ) { - packageNames.forEachIndexed { _, packageName -> - val packageState = newState.externalState.packageStates[packageName]!! - trimPermissionStates(packageState.appId) - } - } - - override fun MutateStateScope.onPackageAdded(packageState: PackageState) { - trimPermissionStates(packageState.appId) - } - - override fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) { - if (appId in newState.externalState.appIdPackageNames) { - trimPermissionStates(appId) - } - } - - override fun MutateStateScope.onPackageUninstalled( - packageName: String, - appId: Int, - userId: Int - ) { - resetPermissionStates(packageName, userId) - } - - private fun MutateStateScope.resetPermissionStates(packageName: String, userId: Int) { - // It's okay to skip resetting permissions for packages that are removed, - // because their states will be trimmed in onPackageRemoved()/onAppIdRemoved() - val packageState = newState.externalState.packageStates[packageName] ?: return - val androidPackage = packageState.androidPackage ?: return - val appId = packageState.appId - val appIdPermissionFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags - androidPackage.requestedPermissions.forEach { permissionName -> - val isRequestedByOtherPackages = anyPackageInAppId(appId) { - it.packageName != packageName && - permissionName in it.androidPackage!!.requestedPermissions - } - if (isRequestedByOtherPackages) { - return@forEach - } - appIdPermissionFlags[appId]?.forEachIndexed { _, deviceId, _ -> - setPermissionFlags(appId, deviceId, userId, permissionName, 0) - } - } - } - - private fun MutateStateScope.trimPermissionStates(appId: Int) { - val requestedPermissions = MutableIndexedSet<String>() - forEachPackageInAppId(appId) { - requestedPermissions += it.androidPackage!!.requestedPermissions - } - newState.userStates.forEachIndexed { _, userId, userState -> - userState.appIdDevicePermissionFlags[appId]?.forEachReversedIndexed { - _, deviceId, permissionFlags -> - permissionFlags.forEachReversedIndexed { _, permissionName, _ -> - if (permissionName !in requestedPermissions) { - setPermissionFlags(appId, deviceId, userId, permissionName, 0) - } - } - } - } - } - - private inline fun MutateStateScope.anyPackageInAppId( - appId: Int, - state: AccessState = newState, - predicate: (PackageState) -> Boolean - ): Boolean { - val packageNames = state.externalState.appIdPackageNames[appId]!! - return packageNames.anyIndexed { _, packageName -> - val packageState = state.externalState.packageStates[packageName]!! - packageState.androidPackage != null && predicate(packageState) - } - } - - private inline fun MutateStateScope.forEachPackageInAppId( - appId: Int, - state: AccessState = newState, - action: (PackageState) -> Unit - ) { - val packageNames = state.externalState.appIdPackageNames[appId]!! - packageNames.forEachIndexed { _, packageName -> - val packageState = state.externalState.packageStates[packageName]!! - if (packageState.androidPackage != null) { - action(packageState) - } - } - } - - override fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) { - with(persistence) { this@parseUserState.parseUserState(state, userId) } - } - - override fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int) { - with(persistence) { this@serializeUserState.serializeUserState(state, userId) } - } - - fun GetStateScope.getPermissionFlags( - appId: Int, - deviceId: String, - userId: Int, - permissionName: String - ): Int = - state.userStates[userId]?.appIdDevicePermissionFlags?.get(appId)?.get(deviceId) - ?.getWithDefault(permissionName, 0) ?: 0 - - fun MutateStateScope.setPermissionFlags( - appId: Int, - deviceId: String, - userId: Int, - permissionName: String, - flags: Int - ): Boolean = - updatePermissionFlags( - appId, deviceId, userId, permissionName, PermissionFlags.MASK_ALL, flags - ) - - private fun MutateStateScope.updatePermissionFlags( - appId: Int, - deviceId: String, - userId: Int, - permissionName: String, - flagMask: Int, - flagValues: Int - ): Boolean { - if (!isDeviceAwarePermission(permissionName)) { - Slog.w(LOG_TAG, "$permissionName is not a device aware permission.") - return false - } - val oldFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags[appId] - ?.get(deviceId).getWithDefault(permissionName, 0) - val newFlags = (oldFlags andInv flagMask) or (flagValues and flagMask) - if (oldFlags == newFlags) { - return false - } - val appIdDevicePermissionFlags = - newState.mutateUserState(userId)!!.mutateAppIdDevicePermissionFlags() - val devicePermissionFlags = appIdDevicePermissionFlags.mutateOrPut(appId) { - MutableIndexedReferenceMap() - } - val permissionFlags = devicePermissionFlags.mutateOrPut(deviceId) { MutableIndexedMap() } - permissionFlags.putWithDefault(permissionName, newFlags, 0) - if (permissionFlags.isEmpty()) { - devicePermissionFlags -= deviceId - if (devicePermissionFlags.isEmpty()) { - appIdDevicePermissionFlags -= appId - } - } - listeners.forEachIndexed { _, it -> - it.onDevicePermissionFlagsChanged( - appId, userId, deviceId, permissionName, oldFlags, newFlags - ) - } - return true - } - - fun addOnPermissionFlagsChangedListener(listener: OnDevicePermissionFlagsChangedListener) { - synchronized(listenersLock) { - listeners = listeners + listener - } - } - - fun removeOnPermissionFlagsChangedListener(listener: OnDevicePermissionFlagsChangedListener) { - synchronized(listenersLock) { - listeners = listeners - listener - } - } - - private fun isDeviceAwarePermission(permissionName: String): Boolean = - DEVICE_SUPPORTED_PERMISSIONS.contains(permissionName) - - companion object { - private val LOG_TAG = DevicePermissionPolicy::class.java.simpleName - - /** - * These permissions are supported for virtual devices. - */ - private val DEVICE_SUPPORTED_PERMISSIONS = indexedSetOf( - android.Manifest.permission.CAMERA, - android.Manifest.permission.RECORD_AUDIO - ) - } - - /** - * TODO: b/289355341 - implement listener for permission changes - * Listener for permission flags changes. - */ - abstract class OnDevicePermissionFlagsChangedListener { - /** - * Called when a permission flags change has been made to the upcoming new state. - * - * Implementations should keep this method fast to avoid stalling the locked state mutation, - * and only call external code after [onStateMutated] when the new state has actually become - * the current state visible to external code. - */ - abstract fun onDevicePermissionFlagsChanged( - appId: Int, - userId: Int, - deviceId: String, - permissionName: String, - oldFlags: Int, - newFlags: Int - ) - - /** - * Called when the upcoming new state has become the current state. - * - * Implementations should keep this method fast to avoid stalling the locked state mutation. - */ - abstract fun onStateMutated() - } -}
\ No newline at end of file diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt index d9f179a6c11b..edacf188b333 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt @@ -64,11 +64,9 @@ import com.android.server.LocalServices import com.android.server.PermissionThread import com.android.server.ServiceThread import com.android.server.SystemConfig -import com.android.server.companion.virtual.VirtualDeviceManagerInternal import com.android.server.permission.access.AccessCheckingService import com.android.server.permission.access.AccessState import com.android.server.permission.access.AppOpUri -import com.android.server.permission.access.DevicePermissionUri import com.android.server.permission.access.GetStateScope import com.android.server.permission.access.MutateStateScope import com.android.server.permission.access.PermissionUri @@ -112,9 +110,6 @@ class PermissionService( private val policy = service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy - private val devicePolicy = - service.getSchemePolicy(UidUri.SCHEME, DevicePermissionUri.SCHEME) as DevicePermissionPolicy - private val context = service.context private lateinit var metricsLogger: MetricsLogger private lateinit var packageManagerInternal: PackageManagerInternal @@ -137,8 +132,6 @@ class PermissionService( private lateinit var permissionControllerManager: PermissionControllerManager - private lateinit var virtualDeviceManagerInternal: VirtualDeviceManagerInternal - /** * A permission backup might contain apps that are not installed. In this case we delay the * restoration until the app is installed. @@ -159,8 +152,6 @@ class PermissionService( systemConfig = SystemConfig.getInstance() userManagerInternal = LocalServices.getService(UserManagerInternal::class.java) userManagerService = UserManagerService.getInstance() - virtualDeviceManagerInternal = - LocalServices.getService(VirtualDeviceManagerInternal::class.java) // The package info cache is the cache for package and permission information. // Disable the package info and package permission caches locally but leave the @@ -469,7 +460,7 @@ class PermissionService( return size } - override fun checkUidPermission(uid: Int, permissionName: String, deviceId: Int): Int { + override fun checkUidPermission(uid: Int, permissionName: String): Int { val userId = UserHandle.getUserId(uid) if (!userManagerInternal.exists(userId)) { return PackageManager.PERMISSION_DENIED @@ -491,7 +482,7 @@ class PermissionService( return PackageManager.PERMISSION_DENIED } val isPermissionGranted = service.getState { - isPermissionGranted(packageState, userId, permissionName, deviceId) + isPermissionGranted(packageState, userId, permissionName) } return if (isPermissionGranted) { PackageManager.PERMISSION_GRANTED @@ -524,12 +515,7 @@ class PermissionService( return false } - override fun checkPermission( - packageName: String, - permissionName: String, - deviceId: Int, - userId: Int - ): Int { + override fun checkPermission(packageName: String, permissionName: String, userId: Int): Int { if (!userManagerInternal.exists(userId)) { return PackageManager.PERMISSION_DENIED } @@ -538,7 +524,7 @@ class PermissionService( .use { it.getPackageState(packageName) } ?: return PackageManager.PERMISSION_DENIED val isPermissionGranted = service.getState { - isPermissionGranted(packageState, userId, permissionName, deviceId) + isPermissionGranted(packageState, userId, permissionName) } return if (isPermissionGranted) { PackageManager.PERMISSION_GRANTED @@ -556,21 +542,19 @@ class PermissionService( private fun GetStateScope.isPermissionGranted( packageState: PackageState, userId: Int, - permissionName: String, - deviceId: Int + permissionName: String ): Boolean { val appId = packageState.appId // Note that instant apps can't have shared UIDs, so we only need to check the current // package state. val isInstantApp = packageState.getUserStateOrDefault(userId).isInstantApp - if (isSinglePermissionGranted(appId, userId, isInstantApp, permissionName, deviceId)) { + if (isSinglePermissionGranted(appId, userId, isInstantApp, permissionName)) { return true } val fullerPermissionName = FULLER_PERMISSIONS[permissionName] if (fullerPermissionName != null && - isSinglePermissionGranted(appId, userId, isInstantApp, fullerPermissionName, deviceId) - ) { + isSinglePermissionGranted(appId, userId, isInstantApp, fullerPermissionName)) { return true } @@ -584,10 +568,9 @@ class PermissionService( appId: Int, userId: Int, isInstantApp: Boolean, - permissionName: String, - deviceId: Int, + permissionName: String ): Boolean { - val flags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId) + val flags = with(policy) { getPermissionFlags(appId, userId, permissionName) } if (!PermissionFlags.isPermissionGranted(flags)) { return false } @@ -618,8 +601,7 @@ class PermissionService( ?: return emptySet() return permissionFlags.mapNotNullIndexedTo(ArraySet()) { _, permissionName, _ -> - if (isPermissionGranted( - packageState, userId, permissionName, Context.DEVICE_ID_DEFAULT)) { + if (isPermissionGranted(packageState, userId, permissionName)) { permissionName } else { null @@ -658,26 +640,18 @@ class PermissionService( } } - override fun grantRuntimePermission( - packageName: String, - permissionName: String, - deviceId: Int, - userId: Int - ) { - setRuntimePermissionGranted( - packageName, userId, permissionName, deviceId, isGranted = true - ) + override fun grantRuntimePermission(packageName: String, permissionName: String, userId: Int) { + setRuntimePermissionGranted(packageName, userId, permissionName, isGranted = true) } override fun revokeRuntimePermission( packageName: String, permissionName: String, - deviceId: Int, userId: Int, reason: String? ) { setRuntimePermissionGranted( - packageName, userId, permissionName, deviceId, isGranted = false, revokeReason = reason + packageName, userId, permissionName, isGranted = false, revokeReason = reason ) } @@ -686,8 +660,8 @@ class PermissionService( userId: Int ) { setRuntimePermissionGranted( - packageName, userId, Manifest.permission.POST_NOTIFICATIONS, Context.DEVICE_ID_DEFAULT, - isGranted = false, skipKillUid = true + packageName, userId, Manifest.permission.POST_NOTIFICATIONS, isGranted = false, + skipKillUid = true ) } @@ -699,7 +673,6 @@ class PermissionService( packageName: String, userId: Int, permissionName: String, - deviceId: Int, isGranted: Boolean, skipKillUid: Boolean = false, revokeReason: String? = null @@ -775,7 +748,7 @@ class PermissionService( } setRuntimePermissionGranted( - packageState, userId, permissionName, deviceId, isGranted, canManageRolePermission, + packageState, userId, permissionName, isGranted, canManageRolePermission, overridePolicyFixed, reportError = true, methodName ) } @@ -809,16 +782,14 @@ class PermissionService( if (permissionState == PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED) { setRuntimePermissionGranted( - packageState, userId, permissionName, Context.DEVICE_ID_DEFAULT, - isGranted = true, canManageRolePermission = false, - overridePolicyFixed = false, reportError = false, - "setRequestedPermissionStates" + packageState, userId, permissionName, isGranted = true, + canManageRolePermission = false, overridePolicyFixed = false, + reportError = false, "setRequestedPermissionStates" ) updatePermissionFlags( packageState.appId, userId, permissionName, - Context.DEVICE_ID_DEFAULT, PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED or - PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, 0, + PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, 0, canUpdateSystemFlags = false, reportErrorForUnknownPermission = false, isPermissionRequested = true, "setRequestedPermissionStates", @@ -845,7 +816,6 @@ class PermissionService( packageState: PackageState, userId: Int, permissionName: String, - deviceId: Int, isGranted: Boolean, canManageRolePermission: Boolean, overridePolicyFixed: Boolean, @@ -901,7 +871,7 @@ class PermissionService( } val appId = packageState.appId - val oldFlags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId) + val oldFlags = with(policy) { getPermissionFlags(appId, userId, permissionName) } if (permissionName !in androidPackage.requestedPermissions && oldFlags == 0) { if (reportError) { @@ -964,7 +934,7 @@ class PermissionService( return } - setPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId, newFlags) + with(policy) { setPermissionFlags(appId, userId, permissionName, newFlags) } if (permission.isRuntime) { val action = if (isGranted) { @@ -993,12 +963,7 @@ class PermissionService( with(appOpPolicy) { setAppOpMode(packageState.appId, userId, appOpName, mode) } } - override fun getPermissionFlags( - packageName: String, - permissionName: String, - deviceId: Int, - userId: Int, - ): Int { + override fun getPermissionFlags(packageName: String, permissionName: String, userId: Int): Int { if (!userManagerInternal.exists(userId)) { Slog.w(LOG_TAG, "getPermissionFlags: Unknown user $userId") return 0 @@ -1029,8 +994,7 @@ class PermissionService( } val flags = - getPermissionFlagsWithPolicy(packageState.appId, userId, permissionName, deviceId) - + with(policy) { getPermissionFlags(packageState.appId, userId, permissionName) } return PermissionFlags.toApiFlags(flags) } } @@ -1038,7 +1002,6 @@ class PermissionService( override fun isPermissionRevokedByPolicy( packageName: String, permissionName: String, - deviceId: Int, userId: Int ): Boolean { if (!userManagerInternal.exists(userId)) { @@ -1055,13 +1018,13 @@ class PermissionService( .use { it.getPackageState(packageName) } ?: return false service.getState { - if (isPermissionGranted(packageState, userId, permissionName, deviceId)) { + if (isPermissionGranted(packageState, userId, permissionName)) { return false } - val flags = - getPermissionFlagsWithPolicy(packageState.appId, userId, permissionName, deviceId) - + val flags = with(policy) { + getPermissionFlags(packageState.appId, userId, permissionName) + } return flags.hasBits(PermissionFlags.POLICY_FIXED) } } @@ -1083,8 +1046,7 @@ class PermissionService( override fun shouldShowRequestPermissionRationale( packageName: String, permissionName: String, - deviceId: Int, - userId: Int, + userId: Int ): Boolean { if (!userManagerInternal.exists(userId)) { Slog.w(LOG_TAG, "shouldShowRequestPermissionRationale: Unknown user $userId") @@ -1106,11 +1068,11 @@ class PermissionService( val flags: Int service.getState { - if (isPermissionGranted(packageState, userId, permissionName, deviceId)) { + if (isPermissionGranted(packageState, userId, permissionName)) { return false } - flags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId) + flags = with(policy) { getPermissionFlags(appId, userId, permissionName) } } if (flags.hasAnyBit(UNREQUESTABLE_MASK)) { return false @@ -1142,7 +1104,6 @@ class PermissionService( flagMask: Int, flagValues: Int, enforceAdjustPolicyPermission: Boolean, - deviceId: Int, userId: Int ) { val callingUid = Binder.getCallingUid() @@ -1238,7 +1199,7 @@ class PermissionService( val appId = packageState.appId service.mutateState { updatePermissionFlags( - appId, userId, permissionName, deviceId, flagMask, flagValues, canUpdateSystemFlags, + appId, userId, permissionName, flagMask, flagValues, canUpdateSystemFlags, reportErrorForUnknownPermission = true, isPermissionRequested, "updatePermissionFlags", packageName ) @@ -1287,9 +1248,8 @@ class PermissionService( val androidPackage = packageState.androidPackage ?: return@forEach androidPackage.requestedPermissions.forEach { permissionName -> updatePermissionFlags( - packageState.appId, userId, permissionName, Context.DEVICE_ID_DEFAULT, - flagMask, flagValues, canUpdateSystemFlags, - reportErrorForUnknownPermission = false, + packageState.appId, userId, permissionName, flagMask, flagValues, + canUpdateSystemFlags, reportErrorForUnknownPermission = false, isPermissionRequested = true, "updatePermissionFlagsForAllApps", packageName ) } @@ -1304,7 +1264,6 @@ class PermissionService( appId: Int, userId: Int, permissionName: String, - deviceId: Int, flagMask: Int, flagValues: Int, canUpdateSystemFlags: Boolean, @@ -1339,7 +1298,7 @@ class PermissionService( return } - val oldFlags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId) + val oldFlags = with(policy) { getPermissionFlags(appId, userId, permissionName) } if (!isPermissionRequested && oldFlags == 0) { Slog.w( LOG_TAG, "$methodName: Permission $permissionName isn't requested by package" + @@ -1349,7 +1308,7 @@ class PermissionService( } val newFlags = PermissionFlags.updateFlags(permission, oldFlags, flagMask, flagValues) - setPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId, newFlags) + with(policy) { setPermissionFlags(appId, userId, permissionName, newFlags) } } override fun getAllowlistedRestrictedPermissions( @@ -1406,49 +1365,6 @@ class PermissionService( ) } - private fun GetStateScope.getPermissionFlagsWithPolicy( - appId: Int, - userId: Int, - permissionName: String, - deviceId: Int, - ): Int = - if (deviceId == Context.DEVICE_ID_DEFAULT) { - with(policy) { getPermissionFlags(appId, userId, permissionName) } - } else { - val persistentDeviceId = virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId) - if (persistentDeviceId != null) { - with(devicePolicy) { - getPermissionFlags(appId, persistentDeviceId, userId, permissionName) - } - } else { - Slog.e(LOG_TAG, "Invalid deviceId $deviceId, no persistent device ID found.") - 0 - } - } - - private fun MutateStateScope.setPermissionFlagsWithPolicy( - appId: Int, - userId: Int, - permissionName: String, - deviceId: Int, - flags: Int - ): Boolean = - if (deviceId == Context.DEVICE_ID_DEFAULT) { - with(policy) { - setPermissionFlags(appId, userId, permissionName, flags) - } - } else { - val persistentDeviceId = virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId) - if (persistentDeviceId != null) { - with(devicePolicy) { - setPermissionFlags(appId, persistentDeviceId, userId, permissionName, flags) - } - } else { - Slog.e(LOG_TAG, "Invalid deviceId $deviceId, no cdm association found.") - false - } - } - /** * This method does not enforce checks on the caller, should only be called after * required checks. @@ -1623,7 +1539,8 @@ class PermissionService( ) { service.mutateState { with(policy) { - val permissionsFlags = getUidPermissionFlags(appId, userId) ?: return@mutateState + val permissionsFlags = + getUidPermissionFlags(appId, userId) ?: return@mutateState val permissions = getPermissions() androidPackage.requestedPermissions.forEachIndexed { _, requestedPermission -> @@ -1744,6 +1661,8 @@ class PermissionService( ) } + + override fun getAppOpPermissionPackages(permissionName: String): Array<String> { requireNotNull(permissionName) { "permissionName cannot be null" } val packageNames = ArraySet<String>() @@ -1960,7 +1879,7 @@ class PermissionService( println("Permissions:") withIndent { userState.appIdPermissionFlags[appId]?.forEachIndexed { - _, permissionName, flags -> + _, permissionName, flags -> val isGranted = PermissionFlags.isPermissionGranted(flags) println( "$permissionName: granted=$isGranted, flags=" + @@ -1969,20 +1888,6 @@ class PermissionService( } } - userState.appIdDevicePermissionFlags[appId]?.forEachIndexed { - _, deviceId, devicePermissionFlags -> - println("Permissions (Device $deviceId):") - withIndent { - devicePermissionFlags.forEachIndexed { _, permissionName, flags -> - val isGranted = PermissionFlags.isPermissionGranted(flags) - println( - "$permissionName: granted=$isGranted, flags=" + - PermissionFlags.toString(flags) - ) - } - } - } - println("App ops:") withIndent { userState.appIdAppOpModes[appId]?.forEachIndexed {_, appOpName, appOpMode -> @@ -2507,7 +2412,7 @@ class PermissionService( } private fun isAppBackupAndRestoreRunning(uid: Int): Boolean { - if (checkUidPermission(uid, Manifest.permission.BACKUP, Context.DEVICE_ID_DEFAULT) != + if (checkUidPermission(uid, Manifest.permission.BACKUP) != PackageManager.PERMISSION_GRANTED) { return false } diff --git a/services/selectiontoolbar/Android.bp b/services/selectiontoolbar/Android.bp deleted file mode 100644 index cc6405f97bc3..000000000000 --- a/services/selectiontoolbar/Android.bp +++ /dev/null @@ -1,22 +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: "services.selectiontoolbar-sources", - srcs: ["java/**/*.java"], - path: "java", - visibility: ["//frameworks/base/services"], -} - -java_library_static { - name: "services.selectiontoolbar", - defaults: ["platform_service_defaults"], - srcs: [":services.selectiontoolbar-sources"], - libs: ["services.core"], -} diff --git a/services/selectiontoolbar/OWNERS b/services/selectiontoolbar/OWNERS deleted file mode 100644 index ed9425cc26c9..000000000000 --- a/services/selectiontoolbar/OWNERS +++ /dev/null @@ -1 +0,0 @@ -include /core/java/android/view/selectiontoolbar/OWNERS diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/RemoteSelectionToolbarRenderService.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/RemoteSelectionToolbarRenderService.java deleted file mode 100644 index ae4227bd4f23..000000000000 --- a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/RemoteSelectionToolbarRenderService.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2021 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.selectiontoolbar; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.os.IBinder; -import android.service.selectiontoolbar.ISelectionToolbarRenderService; -import android.service.selectiontoolbar.SelectionToolbarRenderService; -import android.util.Slog; -import android.view.selectiontoolbar.ISelectionToolbarCallback; -import android.view.selectiontoolbar.ShowInfo; - -import com.android.internal.infra.AbstractRemoteService; -import com.android.internal.infra.ServiceConnector; - -final class RemoteSelectionToolbarRenderService extends - ServiceConnector.Impl<ISelectionToolbarRenderService> { - private static final String TAG = "RemoteSelectionToolbarRenderService"; - - private static final long TIMEOUT_IDLE_UNBIND_MS = - AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS; - - private final ComponentName mComponentName; - private final IBinder mRemoteCallback; - - RemoteSelectionToolbarRenderService(Context context, ComponentName serviceName, int userId, - IBinder callback) { - super(context, new Intent(SelectionToolbarRenderService.SERVICE_INTERFACE).setComponent( - serviceName), 0, userId, ISelectionToolbarRenderService.Stub::asInterface); - mComponentName = serviceName; - mRemoteCallback = callback; - // Bind right away. - connect(); - } - - @Override // from AbstractRemoteService - protected long getAutoDisconnectTimeoutMs() { - return TIMEOUT_IDLE_UNBIND_MS; - } - - @Override // from ServiceConnector.Impl - protected void onServiceConnectionStatusChanged(ISelectionToolbarRenderService service, - boolean connected) { - try { - if (connected) { - service.onConnected(mRemoteCallback); - } - } catch (Exception e) { - Slog.w(TAG, "Exception calling onConnected().", e); - } - } - - public ComponentName getComponentName() { - return mComponentName; - } - - public void onShow(int callingUid, ShowInfo showInfo, ISelectionToolbarCallback callback) { - run((s) -> s.onShow(callingUid, showInfo, callback)); - } - - public void onHide(long widgetToken) { - run((s) -> s.onHide(widgetToken)); - } - - public void onDismiss(int callingUid, long widgetToken) { - run((s) -> s.onDismiss(callingUid, widgetToken)); - } -} diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerService.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerService.java deleted file mode 100644 index 3bdf55ccb5c8..000000000000 --- a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerService.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2021 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.selectiontoolbar; - -import android.content.Context; -import android.util.Slog; -import android.view.selectiontoolbar.ISelectionToolbarCallback; -import android.view.selectiontoolbar.ISelectionToolbarManager; -import android.view.selectiontoolbar.ShowInfo; - -import com.android.internal.util.DumpUtils; -import com.android.server.infra.AbstractMasterSystemService; - -import java.io.FileDescriptor; -import java.io.PrintWriter; - -/** - * Entry point service for selection toolbar management. - */ -public final class SelectionToolbarManagerService extends - AbstractMasterSystemService<SelectionToolbarManagerService, - SelectionToolbarManagerServiceImpl> { - - private static final String TAG = "SelectionToolbarManagerService"; - - @Override - public void onStart() { - publishBinderService(Context.SELECTION_TOOLBAR_SERVICE, - new SelectionToolbarManagerService.SelectionToolbarManagerServiceStub()); - } - - public SelectionToolbarManagerService(Context context) { - super(context, new SelectionToolbarServiceNameResolver(), /* disallowProperty= */ - null, PACKAGE_UPDATE_POLICY_REFRESH_EAGER); - } - - @Override - protected SelectionToolbarManagerServiceImpl newServiceLocked(int resolvedUserId, - boolean disabled) { - return new SelectionToolbarManagerServiceImpl(this, mLock, resolvedUserId); - } - - final class SelectionToolbarManagerServiceStub extends ISelectionToolbarManager.Stub { - - @Override - public void showToolbar(ShowInfo showInfo, ISelectionToolbarCallback callback, int userId) { - synchronized (mLock) { - SelectionToolbarManagerServiceImpl service = getServiceForUserLocked(userId); - if (service != null) { - service.showToolbar(showInfo, callback); - } else { - Slog.v(TAG, "showToolbar(): no service for " + userId); - } - } - } - - @Override - public void hideToolbar(long widgetToken, int userId) { - synchronized (mLock) { - SelectionToolbarManagerServiceImpl service = getServiceForUserLocked(userId); - if (service != null) { - service.hideToolbar(widgetToken); - } else { - Slog.v(TAG, "hideToolbar(): no service for " + userId); - } - } - } - - @Override - public void dismissToolbar(long widgetToken, int userId) { - synchronized (mLock) { - SelectionToolbarManagerServiceImpl service = getServiceForUserLocked(userId); - if (service != null) { - service.dismissToolbar(widgetToken); - } else { - Slog.v(TAG, "dismissToolbar(): no service for " + userId); - } - } - } - - @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return; - - synchronized (mLock) { - dumpLocked("", pw); - } - } - } -} diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java deleted file mode 100644 index c8d153ad37a5..000000000000 --- a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) 2021 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.selectiontoolbar; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.UserIdInt; -import android.app.AppGlobals; -import android.content.ComponentName; -import android.content.pm.PackageManager; -import android.content.pm.ServiceInfo; -import android.os.Binder; -import android.os.IBinder; -import android.os.RemoteException; -import android.service.selectiontoolbar.ISelectionToolbarRenderServiceCallback; -import android.util.Slog; -import android.view.selectiontoolbar.ISelectionToolbarCallback; -import android.view.selectiontoolbar.ShowInfo; - -import com.android.internal.annotations.GuardedBy; -import com.android.server.LocalServices; -import com.android.server.infra.AbstractPerUserSystemService; -import com.android.server.input.InputManagerInternal; - -final class SelectionToolbarManagerServiceImpl extends - AbstractPerUserSystemService<SelectionToolbarManagerServiceImpl, - SelectionToolbarManagerService> { - - private static final String TAG = "SelectionToolbarManagerServiceImpl"; - - @GuardedBy("mLock") - @Nullable - private RemoteSelectionToolbarRenderService mRemoteService; - - InputManagerInternal mInputManagerInternal; - private final SelectionToolbarRenderServiceRemoteCallback mRemoteServiceCallback = - new SelectionToolbarRenderServiceRemoteCallback(); - - protected SelectionToolbarManagerServiceImpl(@NonNull SelectionToolbarManagerService master, - @NonNull Object lock, int userId) { - super(master, lock, userId); - mInputManagerInternal = LocalServices.getService(InputManagerInternal.class); - updateRemoteServiceLocked(); - } - - @GuardedBy("mLock") - @Override // from PerUserSystemService - protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent) - throws PackageManager.NameNotFoundException { - return getServiceInfoOrThrow(serviceComponent, mUserId); - } - - @GuardedBy("mLock") - @Override // from PerUserSystemService - protected boolean updateLocked(boolean disabled) { - final boolean enabledChanged = super.updateLocked(disabled); - updateRemoteServiceLocked(); - return enabledChanged; - } - - /** - * Updates the reference to the remote service. - */ - @GuardedBy("mLock") - private void updateRemoteServiceLocked() { - if (mRemoteService != null) { - Slog.d(TAG, "updateRemoteService(): destroying old remote service"); - mRemoteService.unbind(); - mRemoteService = null; - } - } - - @GuardedBy("mLock") - void showToolbar(ShowInfo showInfo, ISelectionToolbarCallback callback) { - final RemoteSelectionToolbarRenderService remoteService = ensureRemoteServiceLocked(); - if (remoteService != null) { - remoteService.onShow(Binder.getCallingUid(), showInfo, callback); - } - } - - @GuardedBy("mLock") - void hideToolbar(long widgetToken) { - final RemoteSelectionToolbarRenderService remoteService = ensureRemoteServiceLocked(); - if (remoteService != null) { - remoteService.onHide(widgetToken); - } - } - - @GuardedBy("mLock") - void dismissToolbar(long widgetToken) { - final RemoteSelectionToolbarRenderService remoteService = ensureRemoteServiceLocked(); - if (remoteService != null) { - remoteService.onDismiss(Binder.getCallingUid(), widgetToken); - } - } - - @GuardedBy("mLock") - @Nullable - private RemoteSelectionToolbarRenderService ensureRemoteServiceLocked() { - if (mRemoteService == null) { - final String serviceName = getComponentNameLocked(); - final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName); - mRemoteService = new RemoteSelectionToolbarRenderService(getContext(), serviceComponent, - mUserId, mRemoteServiceCallback); - } - return mRemoteService; - } - - private static ServiceInfo getServiceInfoOrThrow(ComponentName comp, @UserIdInt int userId) - throws PackageManager.NameNotFoundException { - int flags = PackageManager.GET_META_DATA; - - ServiceInfo si = null; - try { - si = AppGlobals.getPackageManager().getServiceInfo(comp, flags, userId); - } catch (RemoteException e) { - } - if (si == null) { - throw new PackageManager.NameNotFoundException("Could not get serviceInfo for " - + comp.flattenToShortString()); - } - return si; - } - - private void transferTouchFocus(IBinder source, IBinder target) { - mInputManagerInternal.transferTouchFocus(source, target); - } - - private final class SelectionToolbarRenderServiceRemoteCallback extends - ISelectionToolbarRenderServiceCallback.Stub { - - @Override - public void transferTouch(IBinder source, IBinder target) { - transferTouchFocus(source, target); - } - } -} diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarServiceNameResolver.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarServiceNameResolver.java deleted file mode 100644 index 99b0f25c0a4e..000000000000 --- a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarServiceNameResolver.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2021 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.selectiontoolbar; - -import android.service.selectiontoolbar.DefaultSelectionToolbarRenderService; - -import com.android.server.infra.ServiceNameResolver; - -import java.io.PrintWriter; - -final class SelectionToolbarServiceNameResolver implements ServiceNameResolver { - - // TODO: move to SysUi or ExtServices - private static final String SELECTION_TOOLBAR_SERVICE_NAME = - "android/" + DefaultSelectionToolbarRenderService.class.getName(); - - @Override - public String getDefaultServiceName(int userId) { - return SELECTION_TOOLBAR_SERVICE_NAME; - } - - @Override - public void dumpShort(PrintWriter pw) { - pw.print("service="); pw.print(SELECTION_TOOLBAR_SERVICE_NAME); - } - - @Override - public void dumpShort(PrintWriter pw, int userId) { - pw.print("defaultService="); pw.print(getDefaultServiceName(userId)); - } -} diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java index dc92376263a6..74dc8532f163 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java @@ -966,9 +966,11 @@ public class PackageManagerTests extends AndroidTestCase { } private static void assertUninstalled(ApplicationInfo info) throws Exception { - File nativeLibraryFile = new File(info.nativeLibraryDir); - assertFalse("Native library directory " + info.nativeLibraryDir - + " should be erased", nativeLibraryFile.exists()); + if (info.nativeLibraryDir != null) { + File nativeLibraryFile = new File(info.nativeLibraryDir); + assertFalse("Native library directory " + info.nativeLibraryDir + + " should be erased", nativeLibraryFile.exists()); + } } public void deleteFromRawResource(int iFlags, int dFlags) throws Exception { @@ -2883,14 +2885,15 @@ public class PackageManagerTests extends AndroidTestCase { break; } } - assertNotNull("activities should not be null", packageInfo.activities); - assertNotNull("configPreferences should not be null", packageInfo.configPreferences); - assertNotNull("instrumentation should not be null", packageInfo.instrumentation); - assertNotNull("permissions should not be null", packageInfo.permissions); - assertNotNull("providers should not be null", packageInfo.providers); - assertNotNull("receivers should not be null", packageInfo.receivers); - assertNotNull("services should not be null", packageInfo.services); - assertNotNull("signatures should not be null", packageInfo.signatures); + assertNotNull("applicationInfo should not be null", packageInfo.applicationInfo); + assertNull("activities should be null", packageInfo.activities); + assertNull("configPreferences should be null", packageInfo.configPreferences); + assertNull("instrumentation should be null", packageInfo.instrumentation); + assertNull("permissions should be null", packageInfo.permissions); + assertNull("providers should be null", packageInfo.providers); + assertNull("receivers should be null", packageInfo.receivers); + assertNull("services should be null", packageInfo.services); + assertNotNull("signingInfo should not be null", packageInfo.signingInfo); } finally { cleanUpInstall(ip); } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt index 5a733c7ea117..d217d63c5b44 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt @@ -29,7 +29,6 @@ import com.android.server.pm.PackageSettingBuilder import com.android.server.pm.parsing.pkg.PackageImpl import com.android.server.pm.pkg.AndroidPackage import com.android.server.pm.pkg.PackageState -import com.android.server.pm.pkg.PackageStateImpl import com.android.server.pm.pkg.PackageUserState import com.android.server.pm.pkg.PackageUserStateImpl import com.android.server.pm.pkg.component.ParsedActivity @@ -125,7 +124,7 @@ class PackageStateTest { fillMissingData(packageState, pkg as PackageImpl) - visitType(seenTypes, emptyList(), PackageStateImpl.copy(packageState), + visitType(seenTypes, emptyList(), PackageSetting(packageState, true), PackageState::class.starProjectedType) visitType(seenTypes, emptyList(), pkg, AndroidPackage::class.starProjectedType) visitType(seenTypes, emptyList(), packageState.getUserStateOrDefault(0), diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java index da7a6a10895a..d9338a9b12bf 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -22,8 +22,10 @@ import static com.android.server.display.utils.DeviceConfigParsingUtils.displayB import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; @@ -32,6 +34,7 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; +import android.hardware.display.DisplayManagerInternal; import android.os.Temperature; import android.util.SparseArray; import android.util.Spline; @@ -74,8 +77,7 @@ public final class DisplayDeviceConfigTest { private static final int[] HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{-1, 30000}; private static final float[] NITS = {2, 500, 800}; private static final float[] BRIGHTNESS = {0, 0.62f, 1}; - private static final Spline NITS_TO_BRIGHTNESS_SPLINE = - Spline.createSpline(NITS, BRIGHTNESS); + private static final Spline NITS_TO_BRIGHTNESS_SPLINE = Spline.createSpline(NITS, BRIGHTNESS); private DisplayDeviceConfig mDisplayDeviceConfig; private static final float ZERO_DELTA = 0.0f; @@ -178,40 +180,174 @@ public final class DisplayDeviceConfigTest { assertEquals(82, mDisplayDeviceConfig.getDefaultRefreshRateInHbmHdr()); assertEquals(83, mDisplayDeviceConfig.getDefaultRefreshRateInHbmSunlight()); - assertEquals("sensor_12345", - mDisplayDeviceConfig.getScreenOffBrightnessSensor().type); - assertEquals("Sensor 12345", - mDisplayDeviceConfig.getScreenOffBrightnessSensor().name); + assertNotNull(mDisplayDeviceConfig.getHostUsiVersion()); + assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMajorVersion(), 2); + assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMinorVersion(), 0); + } - assertArrayEquals(new int[]{-1, 10, 20, 30, 40}, - mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux()); + @Test + public void testConfigValuesFromConfigResource() { + setupDisplayDeviceConfigFromConfigResourceFile(); + verifyConfigValuesFromConfigResource(); + } + + @Test + public void testThermalRefreshRateThrottlingFromDisplayConfig() throws IOException { + setupDisplayDeviceConfigFromDisplayConfigFile(); + + SparseArray<SurfaceControl.RefreshRateRange> defaultMap = + mDisplayDeviceConfig.getThermalRefreshRateThrottlingData(null); + assertNotNull(defaultMap); + assertEquals(2, defaultMap.size()); + assertEquals(30, defaultMap.get(Temperature.THROTTLING_CRITICAL).min, SMALL_DELTA); + assertEquals(60, defaultMap.get(Temperature.THROTTLING_CRITICAL).max, SMALL_DELTA); + assertEquals(0, defaultMap.get(Temperature.THROTTLING_SHUTDOWN).min, SMALL_DELTA); + assertEquals(30, defaultMap.get(Temperature.THROTTLING_SHUTDOWN).max, SMALL_DELTA); + + SparseArray<SurfaceControl.RefreshRateRange> testMap = + mDisplayDeviceConfig.getThermalRefreshRateThrottlingData("test"); + assertNotNull(testMap); + assertEquals(1, testMap.size()); + assertEquals(60, testMap.get(Temperature.THROTTLING_EMERGENCY).min, SMALL_DELTA); + assertEquals(90, testMap.get(Temperature.THROTTLING_EMERGENCY).max, SMALL_DELTA); + } + + @Test + public void testValidLuxThrottling() throws Exception { + setupDisplayDeviceConfigFromDisplayConfigFile(); + + Map<DisplayDeviceConfig.BrightnessLimitMapType, Map<Float, Float>> luxThrottlingData = + mDisplayDeviceConfig.getLuxThrottlingData(); + assertEquals(2, luxThrottlingData.size()); + + Map<Float, Float> adaptiveOnBrightnessPoints = luxThrottlingData.get( + DisplayDeviceConfig.BrightnessLimitMapType.ADAPTIVE); + assertEquals(2, adaptiveOnBrightnessPoints.size()); + assertEquals(0.3f, adaptiveOnBrightnessPoints.get(1000f), SMALL_DELTA); + assertEquals(0.5f, adaptiveOnBrightnessPoints.get(5000f), SMALL_DELTA); + + Map<Float, Float> adaptiveOffBrightnessPoints = luxThrottlingData.get( + DisplayDeviceConfig.BrightnessLimitMapType.DEFAULT); + assertEquals(2, adaptiveOffBrightnessPoints.size()); + assertEquals(0.35f, adaptiveOffBrightnessPoints.get(1500f), SMALL_DELTA); + assertEquals(0.55f, adaptiveOffBrightnessPoints.get(5500f), SMALL_DELTA); + } + + @Test + public void testInvalidLuxThrottling() throws Exception { + setupDisplayDeviceConfigFromDisplayConfigFile( + getContent(getInvalidLuxThrottling(), getValidProxSensor())); + + Map<DisplayDeviceConfig.BrightnessLimitMapType, Map<Float, Float>> luxThrottlingData = + mDisplayDeviceConfig.getLuxThrottlingData(); + assertEquals(1, luxThrottlingData.size()); + + Map<Float, Float> adaptiveOnBrightnessPoints = luxThrottlingData.get( + DisplayDeviceConfig.BrightnessLimitMapType.ADAPTIVE); + assertEquals(1, adaptiveOnBrightnessPoints.size()); + assertEquals(0.3f, adaptiveOnBrightnessPoints.get(1000f), SMALL_DELTA); + } + + @Test + public void testFallbackToConfigResource() throws IOException { + setupDisplayDeviceConfigFromConfigResourceFile(); + + // Empty display config file + setupDisplayDeviceConfigFromDisplayConfigFile( + "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" + + "<displayConfiguration />\n"); + + // We should fall back to the config resource + verifyConfigValuesFromConfigResource(); + } + + @Test + public void testDensityMappingFromDisplayConfig() throws IOException { + setupDisplayDeviceConfigFromDisplayConfigFile(); + + assertEquals(120, mDisplayDeviceConfig.getDensityMapping() + .getDensityForResolution(720, 480)); + assertEquals(213, mDisplayDeviceConfig.getDensityMapping() + .getDensityForResolution(1280, 720)); + assertEquals(320, mDisplayDeviceConfig.getDensityMapping() + .getDensityForResolution(1920, 1080)); + assertEquals(640, mDisplayDeviceConfig.getDensityMapping() + .getDensityForResolution(3840, 2160)); + } + + @Test + public void testHighBrightnessModeDataFromDisplayConfig() throws IOException { + setupDisplayDeviceConfigFromDisplayConfigFile(); + + DisplayDeviceConfig.HighBrightnessModeData hbmData = + mDisplayDeviceConfig.getHighBrightnessModeData(); + assertNotNull(hbmData); + assertEquals(BRIGHTNESS[1], hbmData.transitionPoint, ZERO_DELTA); + assertEquals(10000, hbmData.minimumLux, ZERO_DELTA); + assertEquals(1800 * 1000, hbmData.timeWindowMillis); + assertEquals(300 * 1000, hbmData.timeMaxMillis); + assertEquals(60 * 1000, hbmData.timeMinMillis); + assertFalse(hbmData.allowInLowPowerMode); + assertEquals(0.6f, hbmData.minimumHdrPercentOfScreen, ZERO_DELTA); + + List<DisplayManagerInternal.RefreshRateLimitation> refreshRateLimitations = + mDisplayDeviceConfig.getRefreshRateLimitations(); + assertEquals(1, refreshRateLimitations.size()); + assertEquals(DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE, + refreshRateLimitations.get(0).type); + assertEquals(120, refreshRateLimitations.get(0).range.min, ZERO_DELTA); + assertEquals(120, refreshRateLimitations.get(0).range.max, ZERO_DELTA); + + // Max desired Hdr/SDR ratio upper-bounds the HDR brightness. + assertTrue(mDisplayDeviceConfig.hasSdrToHdrRatioSpline()); + assertEquals(NITS_TO_BRIGHTNESS_SPLINE.interpolate(500 * 1.6f), + mDisplayDeviceConfig.getHdrBrightnessFromSdr( + NITS_TO_BRIGHTNESS_SPLINE.interpolate(500), Float.POSITIVE_INFINITY), + ZERO_DELTA); + assertEquals(NITS_TO_BRIGHTNESS_SPLINE.interpolate(500), + mDisplayDeviceConfig.getHdrBrightnessFromSdr( + NITS_TO_BRIGHTNESS_SPLINE.interpolate(500), 1.0f), + ZERO_DELTA); + assertEquals(NITS_TO_BRIGHTNESS_SPLINE.interpolate(500 * 1.25f), + mDisplayDeviceConfig.getHdrBrightnessFromSdr( + NITS_TO_BRIGHTNESS_SPLINE.interpolate(500), 1.25f), + SMALL_DELTA); + assertEquals(NITS_TO_BRIGHTNESS_SPLINE.interpolate(2 * 4), + mDisplayDeviceConfig.getHdrBrightnessFromSdr( + NITS_TO_BRIGHTNESS_SPLINE.interpolate(2), Float.POSITIVE_INFINITY), + SMALL_DELTA); + } + + @Test + public void testThermalBrightnessThrottlingDataFromDisplayConfig() throws IOException { + setupDisplayDeviceConfigFromDisplayConfigFile(); List<DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel> defaultThrottlingLevels = new ArrayList<>(); defaultThrottlingLevels.add( new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( - DisplayDeviceConfig.convertThermalStatus(ThermalStatus.light), 0.4f - )); + DisplayDeviceConfig.convertThermalStatus(ThermalStatus.light), 0.4f + )); defaultThrottlingLevels.add( new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( - DisplayDeviceConfig.convertThermalStatus(ThermalStatus.moderate), 0.3f - )); + DisplayDeviceConfig.convertThermalStatus(ThermalStatus.moderate), 0.3f + )); defaultThrottlingLevels.add( new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( - DisplayDeviceConfig.convertThermalStatus(ThermalStatus.severe), 0.2f - )); + DisplayDeviceConfig.convertThermalStatus(ThermalStatus.severe), 0.2f + )); defaultThrottlingLevels.add( new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( - DisplayDeviceConfig.convertThermalStatus(ThermalStatus.critical), 0.1f - )); + DisplayDeviceConfig.convertThermalStatus(ThermalStatus.critical), 0.1f + )); defaultThrottlingLevels.add( new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( - DisplayDeviceConfig.convertThermalStatus(ThermalStatus.emergency), 0.05f - )); + DisplayDeviceConfig.convertThermalStatus(ThermalStatus.emergency), 0.05f + )); defaultThrottlingLevels.add( new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( - DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.025f - )); + DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.025f + )); DisplayDeviceConfig.ThermalBrightnessThrottlingData defaultThrottlingData = new DisplayDeviceConfig.ThermalBrightnessThrottlingData(defaultThrottlingLevels); @@ -220,28 +356,28 @@ public final class DisplayDeviceConfigTest { concurrentThrottlingLevels = new ArrayList<>(); concurrentThrottlingLevels.add( new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( - DisplayDeviceConfig.convertThermalStatus(ThermalStatus.light), 0.2f - )); + DisplayDeviceConfig.convertThermalStatus(ThermalStatus.light), 0.2f + )); concurrentThrottlingLevels.add( new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( - DisplayDeviceConfig.convertThermalStatus(ThermalStatus.moderate), 0.15f - )); + DisplayDeviceConfig.convertThermalStatus(ThermalStatus.moderate), 0.15f + )); concurrentThrottlingLevels.add( new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( - DisplayDeviceConfig.convertThermalStatus(ThermalStatus.severe), 0.1f - )); + DisplayDeviceConfig.convertThermalStatus(ThermalStatus.severe), 0.1f + )); concurrentThrottlingLevels.add( new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( - DisplayDeviceConfig.convertThermalStatus(ThermalStatus.critical), 0.05f - )); + DisplayDeviceConfig.convertThermalStatus(ThermalStatus.critical), 0.05f + )); concurrentThrottlingLevels.add( new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( - DisplayDeviceConfig.convertThermalStatus(ThermalStatus.emergency), 0.025f - )); + DisplayDeviceConfig.convertThermalStatus(ThermalStatus.emergency), 0.025f + )); concurrentThrottlingLevels.add( new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( - DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.0125f - )); + DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.0125f + )); DisplayDeviceConfig.ThermalBrightnessThrottlingData concurrentThrottlingData = new DisplayDeviceConfig.ThermalBrightnessThrottlingData(concurrentThrottlingLevels); @@ -252,29 +388,87 @@ public final class DisplayDeviceConfigTest { assertEquals(throttlingDataMap, mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId()); + } - assertNotNull(mDisplayDeviceConfig.getHostUsiVersion()); - assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMajorVersion(), 2); - assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMinorVersion(), 0); + @Test + public void testAmbientLightSensorFromDisplayConfig() throws IOException { + setupDisplayDeviceConfigFromDisplayConfigFile(); - // Max desired Hdr/SDR ratio upper-bounds the HDR brightness. - assertEquals(1.0f, - mDisplayDeviceConfig.getHdrBrightnessFromSdr(0.62f, Float.POSITIVE_INFINITY), - ZERO_DELTA); - assertEquals(0.62f, - mDisplayDeviceConfig.getHdrBrightnessFromSdr(0.62f, 1.0f), - ZERO_DELTA); - assertEquals(0.77787f, - mDisplayDeviceConfig.getHdrBrightnessFromSdr(0.62f, 1.25f), - SMALL_DELTA); + assertEquals("test_light_sensor", + mDisplayDeviceConfig.getAmbientLightSensor().type); + assertEquals("Test Ambient Light Sensor", + mDisplayDeviceConfig.getAmbientLightSensor().name); + assertEquals(60, mDisplayDeviceConfig.getAmbientLightSensor().minRefreshRate, ZERO_DELTA); + assertEquals(120, mDisplayDeviceConfig.getAmbientLightSensor().maxRefreshRate, ZERO_DELTA); + } + + @Test + public void testScreenOffBrightnessSensorFromDisplayConfig() throws IOException { + setupDisplayDeviceConfigFromDisplayConfigFile(); + + assertEquals("test_binned_brightness_sensor", + mDisplayDeviceConfig.getScreenOffBrightnessSensor().type); + assertEquals("Test Binned Brightness Sensor", + mDisplayDeviceConfig.getScreenOffBrightnessSensor().name); - // Todo: Add asserts for DensityMapping, - // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor. + assertArrayEquals(new int[]{ -1, 10, 20, 30, 40 }, + mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux()); } @Test - public void testConfigValuesFromConfigResource() { + public void testProximitySensorFromDisplayConfig() throws IOException { + setupDisplayDeviceConfigFromDisplayConfigFile(); + + assertEquals("test_proximity_sensor", + mDisplayDeviceConfig.getProximitySensor().type); + assertEquals("Test Proximity Sensor", + mDisplayDeviceConfig.getProximitySensor().name); + } + + @Test + public void testProximitySensorWithEmptyValuesFromDisplayConfig() throws IOException { + setupDisplayDeviceConfigFromDisplayConfigFile( + getContent(getValidLuxThrottling(), getProxSensorWithEmptyValues())); + assertNull(mDisplayDeviceConfig.getProximitySensor()); + } + + @Test + public void testBlockingZoneThresholdsFromDisplayConfig() throws IOException { + setupDisplayDeviceConfigFromDisplayConfigFile(); + + assertArrayEquals(new float[]{ NITS_TO_BRIGHTNESS_SPLINE.interpolate(50), + NITS_TO_BRIGHTNESS_SPLINE.interpolate(300), + NITS_TO_BRIGHTNESS_SPLINE.interpolate(300), -1}, + mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(), SMALL_DELTA); + assertArrayEquals(new float[]{50, 60, -1, 60}, + mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(), ZERO_DELTA); + assertArrayEquals(new float[]{ NITS_TO_BRIGHTNESS_SPLINE.interpolate(80), + NITS_TO_BRIGHTNESS_SPLINE.interpolate(100), + NITS_TO_BRIGHTNESS_SPLINE.interpolate(100), -1}, + mDisplayDeviceConfig.getHighDisplayBrightnessThresholds(), SMALL_DELTA); + assertArrayEquals(new float[]{70, 80, -1, 80}, + mDisplayDeviceConfig.getHighAmbientBrightnessThresholds(), ZERO_DELTA); + } + + @Test + public void testBlockingZoneThresholdsFromConfigResource() { setupDisplayDeviceConfigFromConfigResourceFile(); + + assertArrayEquals(displayBrightnessThresholdsIntToFloat( + LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE), + mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(), SMALL_DELTA); + assertArrayEquals(ambientBrightnessThresholdsIntToFloat( + LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE), + mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(), ZERO_DELTA); + assertArrayEquals(displayBrightnessThresholdsIntToFloat( + HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE), + mDisplayDeviceConfig.getHighDisplayBrightnessThresholds(), SMALL_DELTA); + assertArrayEquals(ambientBrightnessThresholdsIntToFloat( + HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE), + mDisplayDeviceConfig.getHighAmbientBrightnessThresholds(), ZERO_DELTA); + } + + private void verifyConfigValuesFromConfigResource() { assertNull(mDisplayDeviceConfig.getName()); assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new float[]{2.0f, 200.0f, 600.0f}, ZERO_DELTA); @@ -342,100 +536,8 @@ public final class DisplayDeviceConfigTest { assertEquals(mDisplayDeviceConfig.getDefaultRefreshRateInHbmHdr(), DEFAULT_REFRESH_RATE_IN_HBM_HDR); - // Todo: Add asserts for ThermalBrightnessThrottlingData, DensityMapping, - // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor. - } - - @Test - public void testThermalRefreshRateThrottlingFromDisplayConfig() throws IOException { - setupDisplayDeviceConfigFromDisplayConfigFile(); - - SparseArray<SurfaceControl.RefreshRateRange> defaultMap = - mDisplayDeviceConfig.getThermalRefreshRateThrottlingData(null); - assertNotNull(defaultMap); - assertEquals(2, defaultMap.size()); - assertEquals(30, defaultMap.get(Temperature.THROTTLING_CRITICAL).min, SMALL_DELTA); - assertEquals(60, defaultMap.get(Temperature.THROTTLING_CRITICAL).max, SMALL_DELTA); - assertEquals(0, defaultMap.get(Temperature.THROTTLING_SHUTDOWN).min, SMALL_DELTA); - assertEquals(30, defaultMap.get(Temperature.THROTTLING_SHUTDOWN).max, SMALL_DELTA); - - SparseArray<SurfaceControl.RefreshRateRange> testMap = - mDisplayDeviceConfig.getThermalRefreshRateThrottlingData("test"); - assertNotNull(testMap); - assertEquals(1, testMap.size()); - assertEquals(60, testMap.get(Temperature.THROTTLING_EMERGENCY).min, SMALL_DELTA); - assertEquals(90, testMap.get(Temperature.THROTTLING_EMERGENCY).max, SMALL_DELTA); - } - - @Test - public void testValidLuxThrottling() throws Exception { - setupDisplayDeviceConfigFromDisplayConfigFile(); - - Map<DisplayDeviceConfig.BrightnessLimitMapType, Map<Float, Float>> luxThrottlingData = - mDisplayDeviceConfig.getLuxThrottlingData(); - assertEquals(2, luxThrottlingData.size()); - - Map<Float, Float> adaptiveOnBrightnessPoints = luxThrottlingData.get( - DisplayDeviceConfig.BrightnessLimitMapType.ADAPTIVE); - assertEquals(2, adaptiveOnBrightnessPoints.size()); - assertEquals(0.3f, adaptiveOnBrightnessPoints.get(1000f), SMALL_DELTA); - assertEquals(0.5f, adaptiveOnBrightnessPoints.get(5000f), SMALL_DELTA); - - Map<Float, Float> adaptiveOffBrightnessPoints = luxThrottlingData.get( - DisplayDeviceConfig.BrightnessLimitMapType.DEFAULT); - assertEquals(2, adaptiveOffBrightnessPoints.size()); - assertEquals(0.35f, adaptiveOffBrightnessPoints.get(1500f), SMALL_DELTA); - assertEquals(0.55f, adaptiveOffBrightnessPoints.get(5500f), SMALL_DELTA); - } - - @Test - public void testInvalidLuxThrottling() throws Exception { - setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getInvalidLuxThrottling())); - - Map<DisplayDeviceConfig.BrightnessLimitMapType, Map<Float, Float>> luxThrottlingData = - mDisplayDeviceConfig.getLuxThrottlingData(); - assertEquals(1, luxThrottlingData.size()); - - Map<Float, Float> adaptiveOnBrightnessPoints = luxThrottlingData.get( - DisplayDeviceConfig.BrightnessLimitMapType.ADAPTIVE); - assertEquals(1, adaptiveOnBrightnessPoints.size()); - assertEquals(0.3f, adaptiveOnBrightnessPoints.get(1000f), SMALL_DELTA); - } - - @Test - public void testBlockingZoneThresholdsFromDisplayConfig() throws IOException { - setupDisplayDeviceConfigFromDisplayConfigFile(); - - assertArrayEquals(new float[]{ NITS_TO_BRIGHTNESS_SPLINE.interpolate(50), - NITS_TO_BRIGHTNESS_SPLINE.interpolate(300), - NITS_TO_BRIGHTNESS_SPLINE.interpolate(300), -1}, - mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(), SMALL_DELTA); - assertArrayEquals(new float[]{50, 60, -1, 60}, - mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(), ZERO_DELTA); - assertArrayEquals(new float[]{ NITS_TO_BRIGHTNESS_SPLINE.interpolate(80), - NITS_TO_BRIGHTNESS_SPLINE.interpolate(100), - NITS_TO_BRIGHTNESS_SPLINE.interpolate(100), -1}, - mDisplayDeviceConfig.getHighDisplayBrightnessThresholds(), SMALL_DELTA); - assertArrayEquals(new float[]{70, 80, -1, 80}, - mDisplayDeviceConfig.getHighAmbientBrightnessThresholds(), ZERO_DELTA); - } - - @Test - public void testBlockingZoneThresholdsFromConfigResource() { - setupDisplayDeviceConfigFromConfigResourceFile(); - - assertArrayEquals(displayBrightnessThresholdsIntToFloat( - LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE), - mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(), SMALL_DELTA); - assertArrayEquals(ambientBrightnessThresholdsIntToFloat( - LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE), - mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(), ZERO_DELTA); - assertArrayEquals(displayBrightnessThresholdsIntToFloat( - HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE), - mDisplayDeviceConfig.getHighDisplayBrightnessThresholds(), SMALL_DELTA); - assertArrayEquals(ambientBrightnessThresholdsIntToFloat( - HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE), - mDisplayDeviceConfig.getHighAmbientBrightnessThresholds(), ZERO_DELTA); + assertEquals("test_light_sensor", mDisplayDeviceConfig.getAmbientLightSensor().type); + assertEquals("", mDisplayDeviceConfig.getAmbientLightSensor().name); } private String getValidLuxThrottling() { @@ -541,14 +643,50 @@ public final class DisplayDeviceConfigTest { + "</refreshRateThrottlingMap>\n"; } + private String getValidProxSensor() { + return "<proxSensor>\n" + + "<type>test_proximity_sensor</type>\n" + + "<name>Test Proximity Sensor</name>\n" + + "</proxSensor>\n"; + } + + private String getProxSensorWithEmptyValues() { + return "<proxSensor>\n" + + "<type></type>\n" + + "<name></name>\n" + + "</proxSensor>\n"; + } + private String getContent() { - return getContent(getValidLuxThrottling()); + return getContent(getValidLuxThrottling(), getValidProxSensor()); } - private String getContent(String brightnessCapConfig) { + private String getContent(String brightnessCapConfig, String proxSensor) { return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" + "<displayConfiguration>\n" - + "<name>Example Display</name>" + + "<name>Example Display</name>\n" + + "<densityMapping>\n" + + "<density>\n" + + "<height>480</height>\n" + + "<width>720</width>\n" + + "<density>120</density>\n" + + "</density>\n" + + "<density>\n" + + "<height>720</height>\n" + + "<width>1280</width>\n" + + "<density>213</density>\n" + + "</density>\n" + + "<density>\n" + + "<height>1080</height>\n" + + "<width>1920</width>\n" + + "<density>320</density>\n" + + "</density>\n" + + "<density>\n" + + "<height>2160</height>\n" + + "<width>3840</width>\n" + + "<density>640</density>\n" + + "</density>\n" + + "</densityMapping>\n" + "<screenBrightnessMap>\n" + "<point>\n" + "<value>" + BRIGHTNESS[0] + "</value>\n" @@ -578,7 +716,7 @@ public final class DisplayDeviceConfigTest { + "</displayBrightnessMapping>\n" + "</autoBrightness>\n" + "<highBrightnessMode enabled=\"true\">\n" - + "<transitionPoint>0.62</transitionPoint>\n" + + "<transitionPoint>" + BRIGHTNESS[1] + "</transitionPoint>\n" + "<minimumLux>10000</minimumLux>\n" + "<timing>\n" + "<!-- allow for 5 minutes out of every 30 minutes -->\n" @@ -590,8 +728,8 @@ public final class DisplayDeviceConfigTest { + "<minimum>120</minimum>\n" + "<maximum>120</maximum>\n" + "</refreshRate>\n" - + "<thermalStatusLimit>light</thermalStatusLimit>\n" + "<allowInLowPowerMode>false</allowInLowPowerMode>\n" + + "<minimumHdrPercentOfScreen>0.6</minimumHdrPercentOfScreen>\n" + "<sdrHdrRatioMap>\n" + "<point>\n" + "<sdrNits>2.000</sdrNits>\n" @@ -604,10 +742,19 @@ public final class DisplayDeviceConfigTest { + "</sdrHdrRatioMap>\n" + "</highBrightnessMode>\n" + brightnessCapConfig + + "<lightSensor>\n" + + "<type>test_light_sensor</type>\n" + + "<name>Test Ambient Light Sensor</name>\n" + + "<refreshRate>\n" + + "<minimum>60</minimum>\n" + + "<maximum>120</maximum>\n" + + "</refreshRate>\n" + + "</lightSensor>\n" + "<screenOffBrightnessSensor>\n" - + "<type>sensor_12345</type>\n" - + "<name>Sensor 12345</name>\n" + + "<type>test_binned_brightness_sensor</type>\n" + + "<name>Test Binned Brightness Sensor</name>\n" + "</screenOffBrightnessSensor>\n" + + proxSensor + "<ambientBrightnessChangeThresholds>\n" + "<brighteningThresholds>\n" + "<minimum>10</minimum>\n" @@ -946,9 +1093,9 @@ public final class DisplayDeviceConfigTest { when(mResources.getInteger(R.integer.config_defaultRefreshRate)) .thenReturn(DEFAULT_REFRESH_RATE); when(mResources.getInteger(R.integer.config_fixedRefreshRateInHighZone)) - .thenReturn(DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE); + .thenReturn(DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE); when(mResources.getInteger(R.integer.config_defaultRefreshRateInZone)) - .thenReturn(DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE); + .thenReturn(DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE); when(mResources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate)) .thenReturn(LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE); when(mResources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate)) @@ -960,11 +1107,14 @@ public final class DisplayDeviceConfigTest { R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate)) .thenReturn(HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE); when(mResources.getInteger( - R.integer.config_defaultRefreshRateInHbmHdr)) - .thenReturn(DEFAULT_REFRESH_RATE_IN_HBM_HDR); + R.integer.config_defaultRefreshRateInHbmHdr)) + .thenReturn(DEFAULT_REFRESH_RATE_IN_HBM_HDR); when(mResources.getInteger( - R.integer.config_defaultRefreshRateInHbmSunlight)) - .thenReturn(DEFAULT_REFRESH_RATE_IN_HBM_SUNLIGHT); + R.integer.config_defaultRefreshRateInHbmSunlight)) + .thenReturn(DEFAULT_REFRESH_RATE_IN_HBM_SUNLIGHT); + + when(mResources.getString(com.android.internal.R.string.config_displayLightSensorType)) + .thenReturn("test_light_sensor"); mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index bf2311761891..979676e5f1be 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -479,6 +479,41 @@ public class DisplayManagerServiceTest { } @Test + public void testCreateVirtualRotatesWithContent() throws RemoteException { + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mBasicInjector); + registerDefaultDisplays(displayManager); + + // This is effectively the DisplayManager service published to ServiceManager. + DisplayManagerService.BinderService bs = displayManager.new BinderService(); + + String uniqueId = "uniqueId --- Rotates with Content Test"; + int width = 600; + int height = 800; + int dpi = 320; + int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT; + + when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder( + VIRTUAL_DISPLAY_NAME, width, height, dpi); + builder.setFlags(flags); + builder.setUniqueId(uniqueId); + int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken, + /* projection= */ null, PACKAGE_NAME); + verify(mMockProjectionService, never()).setContentRecordingSession(any(), + nullable(IMediaProjection.class)); + + displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class)); + + // flush the handler + displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0); + + DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId); + assertNotNull(ddi); + assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0); + } + + @Test public void testCreateVirtualDisplayOwnFocus() throws RemoteException { DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java index 20654797a5d2..c0128ae38a28 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java @@ -69,7 +69,6 @@ public class LogicalDisplayTest { mDisplayDeviceInfo.copyFrom(new DisplayDeviceInfo()); mDisplayDeviceInfo.width = DISPLAY_WIDTH; mDisplayDeviceInfo.height = DISPLAY_HEIGHT; - mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT; mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL; mDisplayDeviceInfo.modeId = MODE_ID; mDisplayDeviceInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID, @@ -112,8 +111,18 @@ public class LogicalDisplayTest { mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition()); - expectedPosition.set(40, -20); DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.logicalWidth = DISPLAY_WIDTH; + displayInfo.logicalHeight = DISPLAY_HEIGHT; + // Rotation doesn't matter when the FLAG_ROTATES_WITH_CONTENT is absent. + displayInfo.rotation = Surface.ROTATION_90; + mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo); + mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition()); + + expectedPosition.set(40, -20); + mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT; + mLogicalDisplay.updateLocked(mDeviceRepo); displayInfo.logicalWidth = DISPLAY_HEIGHT; displayInfo.logicalHeight = DISPLAY_WIDTH; displayInfo.rotation = Surface.ROTATION_90; diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java index 88f3b2eb12f8..525bfd75269b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java @@ -48,7 +48,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; @@ -83,6 +82,7 @@ import android.os.PowerManager; import android.os.PowerManagerInternal; import android.os.PowerSaveState; import android.os.SystemClock; +import android.os.WearModeManagerInternal; import android.provider.DeviceConfig; import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; @@ -147,6 +147,8 @@ public class DeviceIdleControllerTest { private TelephonyManager mTelephonyManager; @Mock private Sensor mOffBodySensor; + @Mock + private WearModeManagerInternal mWearModeManagerInternal; class InjectorForTest extends DeviceIdleController.Injector { ConnectivityManager connectivityManager; @@ -348,6 +350,9 @@ public class DeviceIdleControllerTest { mAnyMotionDetector = new AnyMotionDetectorForTest(); mInjector = new InjectorForTest(getContext()); + doReturn(mWearModeManagerInternal) + .when(() -> LocalServices.getService(WearModeManagerInternal.class)); + setupDeviceIdleController(); } @@ -2413,22 +2418,20 @@ public class DeviceIdleControllerTest { } @Test - public void testLowLatencyBodyDetection_NoBodySensor() { - mConstants.USE_BODY_SENSOR = true; - doReturn(null).when(mSensorManager).getDefaultSensor( - eq(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT), anyBoolean()); + public void testModeManager_NoModeManagerLocalService_AddListenerNotCalled() { + mConstants.USE_MODE_MANAGER = true; + doReturn(null) + .when(() -> LocalServices.getService(WearModeManagerInternal.class)); cleanupDeviceIdleController(); setupDeviceIdleController(); - verify(mSensorManager, never()) - .registerListener(any(), any(), anyInt()); + verify(mWearModeManagerInternal, never()).addActiveStateChangeListener( + eq(WearModeManagerInternal.QUICK_DOZE_REQUEST_IDENTIFIER), any(), + eq(mDeviceIdleController.mModeManagerQuickDozeRequestConsumer)); } @Test - public void testLowLatencyBodyDetection_NoBatterySaver_QuickDoze() { - mConstants.USE_BODY_SENSOR = true; - doReturn(mOffBodySensor) - .when(mSensorManager) - .getDefaultSensor(eq(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT), anyBoolean()); + public void testModeManager_NoBatterySaver_QuickDoze() { + mConstants.USE_MODE_MANAGER = true; PowerSaveState powerSaveState = new PowerSaveState.Builder().setBatterySaverEnabled( false).build(); when(mPowerManagerInternal.getLowPowerState(anyInt())) @@ -2436,32 +2439,19 @@ public class DeviceIdleControllerTest { cleanupDeviceIdleController(); setupDeviceIdleController(); - ArgumentCaptor<SensorEventListener> listenerCaptor = - ArgumentCaptor.forClass(SensorEventListener.class); - verify(mSensorManager) - .registerListener(listenerCaptor.capture(), eq(mOffBodySensor), - eq(SensorManager.SENSOR_DELAY_NORMAL)); - final SensorEventListener listener = listenerCaptor.getValue(); - // Set the device as off body - float[] valsZero = {0.0f}; - SensorEvent offbodyEvent = new SensorEvent(mOffBodySensor, 1, 1L, valsZero); - listener.onSensorChanged(offbodyEvent); + // Mode manager quick doze request: true. + mDeviceIdleController.mModeManagerQuickDozeRequestConsumer.accept(true); assertTrue(mDeviceIdleController.isQuickDozeEnabled()); - // Set the device as on body - float[] valsNonZero = {1.0f}; - SensorEvent onbodyEvent = new SensorEvent(mOffBodySensor, 1, 1L, valsNonZero); - listener.onSensorChanged(onbodyEvent); + // Mode manager quick doze request: false. + mDeviceIdleController.mModeManagerQuickDozeRequestConsumer.accept(false); assertFalse(mDeviceIdleController.isQuickDozeEnabled()); verifyStateConditions(STATE_ACTIVE); } @Test - public void testLowLatencyBodyDetection_WithBatterySaver_QuickDoze() { - mConstants.USE_BODY_SENSOR = true; - doReturn(mOffBodySensor) - .when(mSensorManager) - .getDefaultSensor(eq(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT), anyBoolean()); + public void testModeManager_WithBatterySaver_QuickDoze() { + mConstants.USE_MODE_MANAGER = true; PowerSaveState powerSaveState = new PowerSaveState.Builder().setBatterySaverEnabled( true).build(); when(mPowerManagerInternal.getLowPowerState(anyInt())) @@ -2469,22 +2459,13 @@ public class DeviceIdleControllerTest { cleanupDeviceIdleController(); setupDeviceIdleController(); - ArgumentCaptor<SensorEventListener> listenerCaptor = - ArgumentCaptor.forClass(SensorEventListener.class); - verify(mSensorManager) - .registerListener(listenerCaptor.capture(), eq(mOffBodySensor), - eq(SensorManager.SENSOR_DELAY_NORMAL)); - final SensorEventListener listener = listenerCaptor.getValue(); - // Set the device as off body - float[] valsZero = {0.0f}; - SensorEvent offbodyEvent = new SensorEvent(mOffBodySensor, 1, 1L, valsZero); - listener.onSensorChanged(offbodyEvent); + // Mode manager quick doze request: true. + mDeviceIdleController.mModeManagerQuickDozeRequestConsumer.accept(true); assertTrue(mDeviceIdleController.isQuickDozeEnabled()); - // Set the device as on body. Quick doze should remain enabled because battery saver is on. - float[] valsNonZero = {1.0f}; - SensorEvent onbodyEvent = new SensorEvent(mOffBodySensor, 1, 1L, valsNonZero); - listener.onSensorChanged(onbodyEvent); + // Mode manager quick doze request: false. + // Quick doze should remain enabled because battery saver is on. + mDeviceIdleController.mModeManagerQuickDozeRequestConsumer.accept(false); assertTrue(mDeviceIdleController.isQuickDozeEnabled()); } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index 1f4563fb2682..b4a66bd75a6e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -105,6 +105,7 @@ import com.android.server.wm.ActivityServiceConnectionsHolder; import com.android.server.wm.ActivityTaskManagerService; import com.android.server.wm.WindowProcessController; +import org.junit.After; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -206,8 +207,10 @@ public class MockingOomAdjusterTests { setFieldValue(AppProfiler.class, profiler, "mProfilerLock", new Object()); doReturn(new ActivityManagerService.ProcessChangeItem()).when(pr) .enqueueProcessChangeItemLocked(anyInt(), anyInt()); - sService.mOomAdjuster = new OomAdjuster(sService, sService.mProcessList, - new ActiveUids(sService, false)); + sService.mOomAdjuster = sService.mConstants.ENABLE_NEW_OOMADJ + ? new OomAdjusterModernImpl(sService, sService.mProcessList, + new ActiveUids(sService, false)) + : new OomAdjuster(sService, sService.mProcessList, new ActiveUids(sService, false)); sService.mOomAdjuster.mAdjSeq = 10000; sService.mWakefulness = new AtomicInteger(PowerManagerInternal.WAKEFULNESS_AWAKE); if (sService.mConstants.USE_TIERED_CACHED_ADJ) { @@ -220,6 +223,11 @@ public class MockingOomAdjusterTests { LocalServices.removeServiceForTest(PackageManagerInternal.class); } + @After + public void tearDown() { + sService.mOomAdjuster.resetInternal(); + } + private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) { try { Field field = clazz.getDeclaredField(fieldName); @@ -245,10 +253,14 @@ public class MockingOomAdjusterTests { * Replace the process LRU with the given processes. * @param apps */ + @SuppressWarnings("GuardedBy") private void setProcessesToLru(ProcessRecord... apps) { ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP(); lru.clear(); Collections.addAll(lru, apps); + for (ProcessRecord app : apps) { + sService.mOomAdjuster.onProcessBeginLocked(app); + } } /** @@ -259,6 +271,7 @@ public class MockingOomAdjusterTests { @SuppressWarnings("GuardedBy") private void updateOomAdj(ProcessRecord... apps) { if (apps.length == 1) { + sService.mOomAdjuster.onProcessBeginLocked(apps[0]); sService.mOomAdjuster.updateOomAdjLocked(apps[0], OOM_ADJ_REASON_NONE); } else { setProcessesToLru(apps); @@ -600,10 +613,13 @@ public class MockingOomAdjusterTests { s.lastTopAlmostPerceptibleBindRequestUptimeMs = nowUptime; s.getConnections().clear(); app.mServices.updateHasTopStartedAlmostPerceptibleServices(); + sService.mOomAdjuster.onProcessBeginLocked(system); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(app); assertEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj()); + + sService.mOomAdjuster.resetInternal(); } // Out of grace period but valid binding allows the adjustment. @@ -620,10 +636,13 @@ public class MockingOomAdjusterTests { s.lastTopAlmostPerceptibleBindRequestUptimeMs = nowUptime - 2 * sService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs; app.mServices.updateHasTopStartedAlmostPerceptibleServices(); + sService.mOomAdjuster.onProcessBeginLocked(system); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(app); assertEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj()); + + sService.mOomAdjuster.resetInternal(); } // Out of grace period and no valid binding so no adjustment. @@ -641,10 +660,13 @@ public class MockingOomAdjusterTests { nowUptime - 2 * sService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs; s.getConnections().clear(); app.mServices.updateHasTopStartedAlmostPerceptibleServices(); + sService.mOomAdjuster.onProcessBeginLocked(system); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(app); assertNotEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj()); + + sService.mOomAdjuster.resetInternal(); } } @@ -657,11 +679,12 @@ public class MockingOomAdjusterTests { MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, true)); system.mState.setMaxAdj(PERSISTENT_PROC_ADJ); system.mState.setHasTopUi(true); + sService.mOomAdjuster.onProcessBeginLocked(system); // Simulate the system starting and binding to a service in the app. ServiceRecord s = bindService(app, system, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class)); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); + updateOomAdj(system, app); assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND, PERCEPTIBLE_APP_ADJ + 1, SCHED_GROUP_DEFAULT); @@ -850,6 +873,7 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); + client.mServices.setTreatLikeActivity(true); bindService(app, client, null, Context.BIND_WAIVE_PRIORITY | Context.BIND_TREAT_LIKE_ACTIVITY, mock(IBinder.class)); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); @@ -1006,7 +1030,7 @@ public class MockingOomAdjusterTests { bindService(app, client, null, Context.BIND_NOT_FOREGROUND, mock(IBinder.class)); client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - updateOomAdj(app); + updateOomAdj(client, app); assertEquals(PROCESS_STATE_TRANSIENT_BACKGROUND, app.mState.getSetProcState()); assertNoBfsl(app); @@ -1132,7 +1156,7 @@ public class MockingOomAdjusterTests { assertNoBfsl(app); client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); - updateOomAdj(app); + updateOomAdj(client, app); assertEquals(PERSISTENT_SERVICE_ADJ, app.mState.getSetAdj()); assertBfsl(app); @@ -1148,7 +1172,7 @@ public class MockingOomAdjusterTests { bindService(app, client, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class)); client.mState.setRunningRemoteAnimation(true); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - updateOomAdj(app); + updateOomAdj(client, app); assertEquals(PERCEPTIBLE_LOW_APP_ADJ, app.mState.getSetAdj()); } @@ -1199,6 +1223,8 @@ public class MockingOomAdjusterTests { updateOomAdj(client, app); assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj()); + + sService.mOomAdjuster.resetInternal(); } { @@ -1217,6 +1243,8 @@ public class MockingOomAdjusterTests { doReturn(false).when(wpc).isHeavyWeightProcess(); assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj()); + + sService.mOomAdjuster.resetInternal(); } { @@ -1229,9 +1257,11 @@ public class MockingOomAdjusterTests { mock(IBinder.class)); client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); + updateOomAdj(client, app); assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj()); + + sService.mOomAdjuster.resetInternal(); } { @@ -1246,10 +1276,12 @@ public class MockingOomAdjusterTests { mock(IBinder.class)); client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); + updateOomAdj(client, app); doReturn(false).when(wpc).isHeavyWeightProcess(); assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj()); + + sService.mOomAdjuster.resetInternal(); } } @@ -1849,7 +1881,7 @@ public class MockingOomAdjusterTests { bindService(app1, client1, null, Context.BIND_SCHEDULE_LIKE_TOP_APP, mock(IBinder.class)); bindService(app2, client2, null, Context.BIND_SCHEDULE_LIKE_TOP_APP, mock(IBinder.class)); - updateOomAdj(app1, app2); + updateOomAdj(client1, client2, app1, app2); assertProcStates(app1, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, VISIBLE_APP_ADJ, SCHED_GROUP_TOP_APP); @@ -1899,6 +1931,8 @@ public class MockingOomAdjusterTests { s1.getConnections().clear(); s2.getConnections().clear(); + client1.mServices.removeAllConnections(); + client2.mServices.removeAllConnections(); client1.mState.setMaxAdj(UNKNOWN_ADJ); client2.mState.setMaxAdj(UNKNOWN_ADJ); client1.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true); @@ -1909,7 +1943,7 @@ public class MockingOomAdjusterTests { bindService(app2, client2, s2, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE, mock(IBinder.class)); - updateOomAdj(app1, app2); + updateOomAdj(client1, client2, app1, app2); // VISIBLE_APP_ADJ is the max oom-adj for BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE. assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, VISIBLE_APP_ADJ, @@ -1922,7 +1956,7 @@ public class MockingOomAdjusterTests { doReturn(client2).when(sService).getTopApp(); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(app2, OOM_ADJ_REASON_NONE); + updateOomAdj(client2, app2); assertProcStates(app2, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_DEFAULT); } @@ -1977,6 +2011,7 @@ public class MockingOomAdjusterTests { app.setPendingFinishAttach(true); app.mState.setHasForegroundActivities(false); + sService.mOomAdjuster.onProcessBeginLocked(app); sService.mOomAdjuster.setAttachingProcessStatesLSP(app); updateOomAdj(app); @@ -1991,7 +2026,9 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); app.setPendingFinishAttach(true); app.mState.setHasForegroundActivities(true); + doReturn(app).when(sService).getTopApp(); + sService.mOomAdjuster.onProcessBeginLocked(app); sService.mOomAdjuster.setAttachingProcessStatesLSP(app); updateOomAdj(app); @@ -2088,7 +2125,7 @@ public class MockingOomAdjusterTests { anyInt(), anyBoolean(), anyBoolean(), anyBoolean()); doNothing().when(sService.mServices) .scheduleServiceTimeoutLocked(any(ProcessRecord.class)); - sService.mOomAdjuster.updateOomAdjLocked(client1, OOM_ADJ_REASON_NONE); + updateOomAdj(client1, client2, app1, app2, app3); assertEquals(PROCESS_STATE_CACHED_EMPTY, client1.mState.getSetProcState()); assertEquals(PROCESS_STATE_SERVICE, app1.mState.getSetProcState()); @@ -2426,6 +2463,8 @@ public class MockingOomAdjusterTests { lru.clear(); lru.add(app2); lru.add(app); + sService.mOomAdjuster.onProcessBeginLocked(app2); + sService.mOomAdjuster.onProcessBeginLocked(app); final ComponentName cn = ComponentName.unflattenFromString( MOCKAPP_PACKAGENAME + "/.TestService"); @@ -2528,7 +2567,7 @@ public class MockingOomAdjusterTests { doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState(); doReturn(app).when(sService).getTopApp(); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); + updateOomAdj(app); assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj()); diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java index 2d962acfe665..1a8b12a00714 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java @@ -686,9 +686,6 @@ public final class FakeGnssHal extends GnssNative.GnssHal { } @Override - protected void sendNiResponse(int notificationId, int userResponse) {} - - @Override protected void requestPowerStats() { Objects.requireNonNull(mGnssNative).reportGnssPowerStats(mState.mPowerStats); } diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java index 0b1e3386cbac..d6d5264257e6 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java @@ -141,7 +141,7 @@ import java.util.concurrent.atomic.AtomicReference; * Tests for {@link com.android.server.power.PowerManagerService}. * * Build/Install/Run: - * atest FrameworksServicesTests:PowerManagerServiceTest + * atest PowerServiceTests:PowerManagerServiceTest */ @SuppressWarnings("GuardedBy") @RunWith(TestParameterInjector.class) @@ -240,6 +240,10 @@ public class PowerManagerServiceTest { cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); when(mContextSpy.getContentResolver()).thenReturn(cr); + when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_dreamsSupported)) + .thenReturn(true); + when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_dreamsEnabledByDefault)) + .thenReturn(true); Settings.Global.putInt(mContextSpy.getContentResolver(), Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0); Settings.Secure.putInt(mContextSpy.getContentResolver(), @@ -1084,13 +1088,9 @@ public class PowerManagerServiceTest { @SuppressWarnings("GuardedBy") @Test public void testScreensaverActivateOnSleepEnabled_powered_afterTimeout_goesToDreaming() { - when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_dreamsSupported)) - .thenReturn(true); when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(true); Settings.Secure.putInt(mContextSpy.getContentResolver(), Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1); - Settings.Secure.putInt(mContextSpy.getContentResolver(), - Settings.Secure.SCREENSAVER_ENABLED, 1); doAnswer(inv -> { when(mDreamManagerInternalMock.isDreaming()).thenReturn(true); @@ -1112,8 +1112,6 @@ public class PowerManagerServiceTest { public void testAmbientSuppression_disablesDreamingAndWakesDevice() { Settings.Secure.putInt(mContextSpy.getContentResolver(), Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1); - Settings.Secure.putInt(mContextSpy.getContentResolver(), - Settings.Secure.SCREENSAVER_ENABLED, 1); setDreamsDisabledByAmbientModeSuppressionConfig(true); setMinimumScreenOffTimeoutConfig(10000); @@ -1141,8 +1139,6 @@ public class PowerManagerServiceTest { public void testAmbientSuppressionDisabled_shouldNotWakeDevice() { Settings.Secure.putInt(mContextSpy.getContentResolver(), Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1); - Settings.Secure.putInt(mContextSpy.getContentResolver(), - Settings.Secure.SCREENSAVER_ENABLED, 1); setDreamsDisabledByAmbientModeSuppressionConfig(false); setMinimumScreenOffTimeoutConfig(10000); @@ -1169,8 +1165,6 @@ public class PowerManagerServiceTest { public void testAmbientSuppression_doesNotAffectDreamForcing() { Settings.Secure.putInt(mContextSpy.getContentResolver(), Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1); - Settings.Secure.putInt(mContextSpy.getContentResolver(), - Settings.Secure.SCREENSAVER_ENABLED, 1); setDreamsDisabledByAmbientModeSuppressionConfig(true); setMinimumScreenOffTimeoutConfig(10000); @@ -1196,8 +1190,6 @@ public class PowerManagerServiceTest { public void testBatteryDrainDuringDream() { Settings.Secure.putInt(mContextSpy.getContentResolver(), Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1); - Settings.Secure.putInt(mContextSpy.getContentResolver(), - Settings.Secure.SCREENSAVER_ENABLED, 1); setMinimumScreenOffTimeoutConfig(100); setDreamsBatteryLevelDrainConfig(5); diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp new file mode 100644 index 000000000000..05acd9b8eeb1 --- /dev/null +++ b/services/tests/powerstatstests/Android.bp @@ -0,0 +1,52 @@ +package { + // See: http://go/android-license-faq + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "PowerStatsTests", + + // Include all test java files. + srcs: [ + "src/**/*.java", + ], + + static_libs: [ + "services.core", + "coretests-aidl", + "platformprotosnano", + "junit", + "truth-prebuilt", + "androidx.test.runner", + "androidx.test.ext.junit", + "androidx.test.ext.truth", + "androidx.test.uiautomator_uiautomator", + "mockito-target-minus-junit4", + "servicestests-utils", + ], + + libs: [ + "android.test.base", + ], + + resource_dirs: ["res/"], + + data: [ + ":BstatsTestApp", + ], + + test_suites: [ + "automotive-tests", + "device-tests", + ], + + platform_apis: true, + + certificate: "platform", + + dxflags: ["--multi-dex"], + + optimize: { + enabled: false, + }, +} diff --git a/services/tests/powerstatstests/AndroidManifest.xml b/services/tests/powerstatstests/AndroidManifest.xml new file mode 100644 index 000000000000..d3a88d2bc38c --- /dev/null +++ b/services/tests/powerstatstests/AndroidManifest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.powerstatstests"> + + <uses-permission android:name="android.permission.BATTERY_STATS" /> + <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/> + <uses-permission android:name="android.permission.MANAGE_USERS"/> + + <queries> + <package android:name="com.android.coretests.apps.bstatstestapp" /> + </queries> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.frameworks.powerstatstests" + android:label="BatteryStats and PowerStats Services Tests"/> +</manifest> diff --git a/services/tests/powerstatstests/AndroidTest.xml b/services/tests/powerstatstests/AndroidTest.xml new file mode 100644 index 000000000000..79b07e812b28 --- /dev/null +++ b/services/tests/powerstatstests/AndroidTest.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2023 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 Power Stats Tests."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-instrumentation" /> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="install-arg" value="-t" /> + <option name="test-file-name" value="PowerStatsTests.apk" /> + <option name="test-file-name" value="BstatsTestApp.apk" /> + </target_preparer> + + <option name="test-tag" value="PowerStatsTests" /> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.frameworks.powerstatstests" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + <option name="exclude-annotation" value="androidx.test.filters.FlakyTest" /> + </test> +</configuration> diff --git a/core/tests/coretests/BstatsTestApp/Android.bp b/services/tests/powerstatstests/BstatsTestApp/Android.bp index c82da9e7b449..c82da9e7b449 100644 --- a/core/tests/coretests/BstatsTestApp/Android.bp +++ b/services/tests/powerstatstests/BstatsTestApp/Android.bp diff --git a/core/tests/coretests/BstatsTestApp/AndroidManifest.xml b/services/tests/powerstatstests/BstatsTestApp/AndroidManifest.xml index fcb1e71cc3f5..fcb1e71cc3f5 100644 --- a/core/tests/coretests/BstatsTestApp/AndroidManifest.xml +++ b/services/tests/powerstatstests/BstatsTestApp/AndroidManifest.xml diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.java b/services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.java index 2601f3571b98..2601f3571b98 100644 --- a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.java +++ b/services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.java diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.java b/services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.java index d192fbd66c89..c731e536d032 100644 --- a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.java +++ b/services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.java @@ -15,8 +15,6 @@ */ package com.android.coretests.apps.bstatstestapp; -import com.android.frameworks.coretests.aidl.ICmdCallback; - import android.content.Intent; import android.os.Bundle; import android.os.IBinder; @@ -24,6 +22,8 @@ import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; +import com.android.frameworks.coretests.aidl.ICmdCallback; + public class Common { private static final String EXTRA_KEY_CMD_RECEIVER = "cmd_receiver"; diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java b/services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java index 892f60e8530f..892f60e8530f 100644 --- a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java +++ b/services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java b/services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java index 5c551d54b4d5..5c551d54b4d5 100644 --- a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java +++ b/services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java b/services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java index 0cd0643ee8c0..0cd0643ee8c0 100644 --- a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java +++ b/services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java diff --git a/services/tests/servicestests/src/com/android/server/powerstats/OWNERS b/services/tests/powerstatstests/OWNERS index 12f13ea63db0..9ed0e051738d 100644 --- a/services/tests/servicestests/src/com/android/server/powerstats/OWNERS +++ b/services/tests/powerstatstests/OWNERS @@ -1 +1,4 @@ +# Bug component: 987260 + +include /BATTERY_STATS_OWNERS include /services/core/java/com/android/server/powerstats/OWNERS diff --git a/services/tests/powerstatstests/TEST_MAPPING b/services/tests/powerstatstests/TEST_MAPPING new file mode 100644 index 000000000000..e1eb1e4fc0db --- /dev/null +++ b/services/tests/powerstatstests/TEST_MAPPING @@ -0,0 +1,22 @@ +{ + "presubmit": [ + { + "name": "PowerStatsTests", + "options": [ + {"include-filter": "com.android.server.power.stats"}, + {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, + {"exclude-annotation": "androidx.test.filters.FlakyTest"}, + {"exclude-annotation": "org.junit.Ignore"} + ] + } + ], + "postsubmit": [ + { + "name": "PowerStatsTests", + "options": [ + {"include-filter": "com.android.server.power.stats"}, + {"exclude-annotation": "org.junit.Ignore"} + ] + } + ] +} diff --git a/services/tests/servicestests/res/xml/irq_device_map_1.xml b/services/tests/powerstatstests/res/xml/irq_device_map_1.xml index 1f1a77b437ab..1f1a77b437ab 100644 --- a/services/tests/servicestests/res/xml/irq_device_map_1.xml +++ b/services/tests/powerstatstests/res/xml/irq_device_map_1.xml diff --git a/services/tests/servicestests/res/xml/irq_device_map_2.xml b/services/tests/powerstatstests/res/xml/irq_device_map_2.xml index 508c98d871da..508c98d871da 100644 --- a/services/tests/servicestests/res/xml/irq_device_map_2.xml +++ b/services/tests/powerstatstests/res/xml/irq_device_map_2.xml diff --git a/services/tests/servicestests/res/xml/irq_device_map_3.xml b/services/tests/powerstatstests/res/xml/irq_device_map_3.xml index fd55428c48df..fd55428c48df 100644 --- a/services/tests/servicestests/res/xml/irq_device_map_3.xml +++ b/services/tests/powerstatstests/res/xml/irq_device_map_3.xml diff --git a/services/tests/servicestests/res/xml/power_profile_test_legacy_modem.xml b/services/tests/powerstatstests/res/xml/power_profile_test_legacy_modem.xml index 5335f9640738..5335f9640738 100644 --- a/services/tests/servicestests/res/xml/power_profile_test_legacy_modem.xml +++ b/services/tests/powerstatstests/res/xml/power_profile_test_legacy_modem.xml diff --git a/services/tests/servicestests/res/xml/power_profile_test_modem_calculator.xml b/services/tests/powerstatstests/res/xml/power_profile_test_modem_calculator.xml index f57bc0f70b5d..f57bc0f70b5d 100644 --- a/services/tests/servicestests/res/xml/power_profile_test_modem_calculator.xml +++ b/services/tests/powerstatstests/res/xml/power_profile_test_modem_calculator.xml diff --git a/services/tests/servicestests/res/xml/power_profile_test_modem_calculator_multiactive.xml b/services/tests/powerstatstests/res/xml/power_profile_test_modem_calculator_multiactive.xml index 4f5e674c60f5..4f5e674c60f5 100644 --- a/services/tests/servicestests/res/xml/power_profile_test_modem_calculator_multiactive.xml +++ b/services/tests/powerstatstests/res/xml/power_profile_test_modem_calculator_multiactive.xml diff --git a/services/tests/servicestests/res/xml/power_profile_test_modem_default.xml b/services/tests/powerstatstests/res/xml/power_profile_test_modem_default.xml index ab016fbb1f5d..ab016fbb1f5d 100644 --- a/services/tests/servicestests/res/xml/power_profile_test_modem_default.xml +++ b/services/tests/powerstatstests/res/xml/power_profile_test_modem_default.xml diff --git a/services/tests/servicestests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java index 319a280d10cc..319a280d10cc 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/AudioPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AudioPowerCalculatorTest.java index fb367b24168e..fb367b24168e 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/AudioPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AudioPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java index 4ea0805ffaa7..4ea0805ffaa7 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java index 5a2d2e3d33fd..5a2d2e3d33fd 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsBackgroundStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBackgroundStatsTest.java index 4d3fcb611f24..4d3fcb611f24 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsBackgroundStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBackgroundStatsTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsBinderCallStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBinderCallStatsTest.java index 3f101a96d36c..3f101a96d36c 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsBinderCallStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBinderCallStatsTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsCounterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCounterTest.java index 326639c54495..326639c54495 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsCounterTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCounterTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java index 55ffa1a15a6b..55ffa1a15a6b 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsDualTimerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsDualTimerTest.java index d6acf8da7431..d6acf8da7431 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsDualTimerTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsDualTimerTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsDurationTimerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsDurationTimerTest.java index 99520cd6fbad..99520cd6fbad 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsDurationTimerTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsDurationTimerTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java index 4fde73bd8408..4fde73bd8408 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java index eb3bd0ebb536..48ba765c3968 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java @@ -30,6 +30,7 @@ import android.os.BatteryStats.CpuUsageDetails; import android.os.BatteryStats.EnergyConsumerDetails; import android.os.BatteryStats.HistoryItem; import android.os.Parcel; +import android.telephony.NetworkRegistrationInfo; import android.util.Log; import androidx.test.InstrumentationRegistry; @@ -66,6 +67,7 @@ public class BatteryStatsHistoryTest { private File mHistoryDir; private final Clock mClock = new MockClock(); private BatteryStatsHistory mHistory; + private BatteryStats.HistoryPrinter mHistoryPrinter; @Mock private BatteryStatsHistory.TraceDelegate mTracer; @Mock @@ -89,6 +91,9 @@ public class BatteryStatsHistoryTest { when(mStepDetailsCalculator.getHistoryStepDetails()) .thenReturn(new BatteryStats.HistoryStepDetails()); + + + mHistoryPrinter = new BatteryStats.HistoryPrinter(); } @Test @@ -379,11 +384,112 @@ public class BatteryStatsHistoryTest { assertThat(checkin).contains("XC,10321,400,500,600"); } + @Test + public void testNrState_dump() { + mHistory.forceRecordAllHistory(); + mHistory.startRecordingHistory(0, 0, /* reset */ true); + mHistory.setBatteryState(true /* charging */, BatteryManager.BATTERY_STATUS_CHARGING, 80, + 1234); + + mHistory.recordNrStateChangeEvent(200, 200, + NetworkRegistrationInfo.NR_STATE_RESTRICTED); + mHistory.recordNrStateChangeEvent(300, 300, + NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED); + mHistory.recordNrStateChangeEvent(400, 400, + NetworkRegistrationInfo.NR_STATE_CONNECTED); + mHistory.recordNrStateChangeEvent(500, 500, + NetworkRegistrationInfo.NR_STATE_NONE); + + BatteryStatsHistoryIterator iterator = mHistory.iterate(); + BatteryStats.HistoryItem item = new BatteryStats.HistoryItem(); + assertThat(item = iterator.next()).isNotNull(); // First item contains current time only + + assertThat(item = iterator.next()).isNotNull(); + String dump = toString(item, /* checkin */ false); + assertThat(dump).contains("+200ms"); + assertThat(dump).contains("nr_state=restricted"); + + assertThat(item = iterator.next()).isNotNull(); + dump = toString(item, /* checkin */ false); + assertThat(dump).contains("+300ms"); + assertThat(dump).contains("nr_state=not_restricted"); + + assertThat(item = iterator.next()).isNotNull(); + dump = toString(item, /* checkin */ false); + assertThat(dump).contains("+400ms"); + assertThat(dump).contains("nr_state=connected"); + + assertThat(item = iterator.next()).isNotNull(); + dump = toString(item, /* checkin */ false); + assertThat(dump).contains("+500ms"); + assertThat(dump).contains("nr_state=none"); + } + + @Test + public void testNrState_checkin() { + mHistory.forceRecordAllHistory(); + mHistory.startRecordingHistory(0, 0, /* reset */ true); + mHistory.setBatteryState(true /* charging */, BatteryManager.BATTERY_STATUS_CHARGING, 80, + 1234); + + mHistory.recordNrStateChangeEvent(200, 200, + NetworkRegistrationInfo.NR_STATE_RESTRICTED); + mHistory.recordNrStateChangeEvent(300, 300, + NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED); + mHistory.recordNrStateChangeEvent(400, 400, + NetworkRegistrationInfo.NR_STATE_CONNECTED); + mHistory.recordNrStateChangeEvent(500, 500, + NetworkRegistrationInfo.NR_STATE_NONE); + + BatteryStatsHistoryIterator iterator = mHistory.iterate(); + BatteryStats.HistoryItem item = new BatteryStats.HistoryItem(); + assertThat(item = iterator.next()).isNotNull(); // First item contains current time only + + assertThat(item = iterator.next()).isNotNull(); + String dump = toString(item, /* checkin */ true); + assertThat(dump).contains("nrs=1"); + + assertThat(item = iterator.next()).isNotNull(); + dump = toString(item, /* checkin */ true); + assertThat(dump).contains("nrs=2"); + + assertThat(item = iterator.next()).isNotNull(); + dump = toString(item, /* checkin */ true); + assertThat(dump).contains("nrs=3"); + + assertThat(item = iterator.next()).isNotNull(); + dump = toString(item, /* checkin */ true); + assertThat(dump).contains("nrs=0"); + } + + @Test + public void testNrState_aTrace() { + InOrder inOrder = Mockito.inOrder(mTracer); + Mockito.when(mTracer.tracingEnabled()).thenReturn(true); + + mHistory.recordNrStateChangeEvent(mClock.elapsedRealtime(), mClock.uptimeMillis(), + NetworkRegistrationInfo.NR_STATE_RESTRICTED); + mHistory.recordNrStateChangeEvent(mClock.elapsedRealtime(), mClock.uptimeMillis(), + NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED); + mHistory.recordNrStateChangeEvent(mClock.elapsedRealtime(), mClock.uptimeMillis(), + NetworkRegistrationInfo.NR_STATE_CONNECTED); + mHistory.recordNrStateChangeEvent(mClock.elapsedRealtime(), mClock.uptimeMillis(), + NetworkRegistrationInfo.NR_STATE_NONE); + + inOrder.verify(mTracer).traceCounter("battery_stats.nr_state", + NetworkRegistrationInfo.NR_STATE_RESTRICTED); + inOrder.verify(mTracer).traceCounter("battery_stats.nr_state", + NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED); + inOrder.verify(mTracer).traceCounter("battery_stats.nr_state", + NetworkRegistrationInfo.NR_STATE_CONNECTED); + inOrder.verify(mTracer).traceCounter("battery_stats.nr_state", + NetworkRegistrationInfo.NR_STATE_NONE); + } + private String toString(BatteryStats.HistoryItem item, boolean checkin) { - BatteryStats.HistoryPrinter printer = new BatteryStats.HistoryPrinter(); StringWriter writer = new StringWriter(); PrintWriter pw = new PrintWriter(writer); - printer.printNextItem(pw, item, 0, checkin, /* verbose */ true); + mHistoryPrinter.printNextItem(pw, item, 0, checkin, /* verbose */ true); pw.flush(); return writer.toString(); } diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java index f20f061230e2..5ebc6ca3f558 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java @@ -267,7 +267,7 @@ public class BatteryStatsImplTest { final long[][] delta3 = { {98545, 95768795, 76586, 548945, 57846}, {788876, 586, 578459, 8776984, 9578923}, - {3049509483598l, 4597834, 377654, 94589035, 7854}, + {3049509483598L, 4597834, 377654, 94589035, 7854}, {9493, 784, 99895, 8974893, 9879843} }; diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsManagerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java index 7ae111711b6b..7ae111711b6b 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsManagerTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java index 090c8c8c2c3c..88b9522d4cb1 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java @@ -44,6 +44,7 @@ import android.telephony.Annotation; import android.telephony.CellSignalStrength; import android.telephony.DataConnectionRealTimeInfo; import android.telephony.ModemActivityInfo; +import android.telephony.NetworkRegistrationInfo; import android.telephony.ServiceState; import android.telephony.TelephonyManager; import android.util.Log; @@ -2461,14 +2462,26 @@ public class BatteryStatsNoteTest extends TestCase { @BatteryStats.RadioAccessTechnology int rat) { currentNetworkDataType = dataType; currentRat = rat; + final int nrState; + if (currentNetworkDataType == TelephonyManager.NETWORK_TYPE_NR) { + nrState = NetworkRegistrationInfo.NR_STATE_CONNECTED; + } else { + nrState = NetworkRegistrationInfo.NR_STATE_NONE; + } mBsi.notePhoneDataConnectionStateLocked(dataType, true, ServiceState.STATE_IN_SERVICE, - currentFrequencyRange); + nrState, currentFrequencyRange); } void setFrequencyRange(@ServiceState.FrequencyRange int frequency) { currentFrequencyRange = frequency; + final int nrState; + if (currentNetworkDataType == TelephonyManager.NETWORK_TYPE_NR) { + nrState = NetworkRegistrationInfo.NR_STATE_CONNECTED; + } else { + nrState = NetworkRegistrationInfo.NR_STATE_NONE; + } mBsi.notePhoneDataConnectionStateLocked(currentNetworkDataType, true, - ServiceState.STATE_IN_SERVICE, frequency); + ServiceState.STATE_IN_SERVICE, nrState, frequency); } void setSignalStrength(@BatteryStats.RadioAccessTechnology int rat, int strength) { diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsResetTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java index a0fb631812f4..a0fb631812f4 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsResetTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsSamplingTimerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsSamplingTimerTest.java index 784d673ed3f0..ee68bf8ae53c 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsSamplingTimerTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsSamplingTimerTest.java @@ -1,17 +1,17 @@ /* * 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 + * 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 + * 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. + * 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.power.stats; diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsSensorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsSensorTest.java index b8f0ce3456f8..9c70f376ca14 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsSensorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsSensorTest.java @@ -1,17 +1,17 @@ /* * 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 + * 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 + * 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. + * 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.power.stats; diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsServTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsServTest.java index 200eb1d0ad15..200eb1d0ad15 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsServTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsServTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsStopwatchTimerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsStopwatchTimerTest.java index fcae42a76f1b..18a366c178fd 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsStopwatchTimerTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsStopwatchTimerTest.java @@ -1,17 +1,17 @@ /* * 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 + * 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 + * 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. + * 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.power.stats; diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTimeBaseTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsTimeBaseTest.java index 5b47423f0d59..5b47423f0d59 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTimeBaseTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsTimeBaseTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTimerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsTimerTest.java index 14c5c5db5c1c..14c5c5db5c1c 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTimerTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsTimerTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java index face849620d7..face849620d7 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java index 5df0acb65249..5df0acb65249 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java index 534aa89e1699..534aa89e1699 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java index b846e3a36656..b846e3a36656 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java index 266a22632a6d..266a22632a6d 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java index 4d4337c16757..4d4337c16757 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java index ccace40bb056..ccace40bb056 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java index 5fce32f0598a..5fce32f0598a 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java index 888bc623f669..888bc623f669 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CustomEnergyConsumerPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerCalculatorTest.java index 245faaf15cc8..245faaf15cc8 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/CustomEnergyConsumerPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java index 28f4799656b7..28f4799656b7 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/FlashlightPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/FlashlightPowerCalculatorTest.java index 0f85fdc375fb..0f85fdc375fb 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/FlashlightPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/FlashlightPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java index 3f2a6d04c1e6..3f2a6d04c1e6 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/IdlePowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/IdlePowerCalculatorTest.java index 3d150af711f1..3d150af711f1 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/IdlePowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/IdlePowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/KernelWakelockReaderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/KernelWakelockReaderTest.java index c0f3c775ffe5..2edfc8e1e408 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/KernelWakelockReaderTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/KernelWakelockReaderTest.java @@ -1,17 +1,17 @@ /* * 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 + * 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 + * 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. + * 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.power.stats; diff --git a/services/tests/servicestests/src/com/android/server/power/stats/LongSamplingCounterArrayTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/LongSamplingCounterArrayTest.java index 2e962c364ed2..2e962c364ed2 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/LongSamplingCounterArrayTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/LongSamplingCounterArrayTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/LongSamplingCounterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/LongSamplingCounterTest.java index 0eac625051fc..0eac625051fc 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/LongSamplingCounterTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/LongSamplingCounterTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MemoryPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MemoryPowerCalculatorTest.java index 2cce449c6c05..2cce449c6c05 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/MemoryPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MemoryPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java index d3ec0d7e3f6e..888a1688c2a1 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java @@ -46,7 +46,7 @@ import android.telephony.TelephonyManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.frameworks.servicestests.R; +import com.android.frameworks.powerstatstests.R; import com.google.common.collect.Range; diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java index 6d3f1f27b572..6d3f1f27b572 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MockClock.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockClock.java index 5e57cc36797b..5e57cc36797b 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/MockClock.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockClock.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/OWNERS b/services/tests/powerstatstests/src/com/android/server/power/stats/OWNERS index 9a7db1cb4fe4..9a7db1cb4fe4 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/OWNERS +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/OWNERS diff --git a/services/tests/servicestests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java index 372307985f04..372307985f04 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/SensorPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/SensorPowerCalculatorTest.java index 474527040839..474527040839 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/SensorPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/SensorPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/SystemServerCpuThreadReaderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServerCpuThreadReaderTest.java index 80cbe0da402e..80cbe0da402e 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/SystemServerCpuThreadReaderTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServerCpuThreadReaderTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java index 4dae2d548057..4dae2d548057 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/UserPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/UserPowerCalculatorTest.java index f14745ef2daa..f14745ef2daa 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/UserPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/UserPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/VideoPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/VideoPowerCalculatorTest.java index f578aa3b46be..f578aa3b46be 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/VideoPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/VideoPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/WakelockPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerCalculatorTest.java index f1961855f12f..f1961855f12f 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/WakelockPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java index 113be8b19518..113be8b19518 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java index b81b776019f9..0dc836ba0400 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java @@ -34,7 +34,7 @@ import android.util.SparseIntArray; import androidx.test.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; -import com.android.frameworks.servicestests.R; +import com.android.frameworks.powerstatstests.R; import com.android.server.power.stats.wakeups.CpuWakeupStats.Wakeup; import org.junit.Test; diff --git a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/IrqDeviceMapTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/IrqDeviceMapTest.java index 47a8f49e7025..9af288496bb9 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/IrqDeviceMapTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/IrqDeviceMapTest.java @@ -23,7 +23,7 @@ import android.content.Context; import androidx.test.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; -import com.android.frameworks.servicestests.R; +import com.android.frameworks.powerstatstests.R; import com.android.internal.util.CollectionUtils; import org.junit.Test; diff --git a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java index 99bc25abc4a1..99bc25abc4a1 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java diff --git a/services/tests/servicestests/src/com/android/server/powerstats/IntervalRandomNoiseGeneratorTest.java b/services/tests/powerstatstests/src/com/android/server/powerstats/IntervalRandomNoiseGeneratorTest.java index 99621460f360..99621460f360 100644 --- a/services/tests/servicestests/src/com/android/server/powerstats/IntervalRandomNoiseGeneratorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/powerstats/IntervalRandomNoiseGeneratorTest.java diff --git a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java index 2ffe4aacda73..2ffe4aacda73 100644 --- a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java +++ b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 173c5f5dd40f..92ff7ab86247 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -112,7 +112,6 @@ android_test { }, data: [ - ":BstatsTestApp", ":JobTestApp", ":SimpleServiceTestApp1", ":SimpleServiceTestApp2", @@ -134,7 +133,6 @@ java_library { name: "servicestests-core-utils", srcs: [ "src/com/android/server/am/DeviceConfigSession.java", - "src/com/android/server/display/TestUtils.java", "src/com/android/server/pm/PackageSettingBuilder.java", "src/com/android/server/pm/parsing/TestPackageParser2.kt", ], diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml index fbb0ca108ecd..b1d50399416a 100644 --- a/services/tests/servicestests/AndroidTest.xml +++ b/services/tests/servicestests/AndroidTest.xml @@ -28,7 +28,6 @@ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true" /> <option name="install-arg" value="-t" /> - <option name="test-file-name" value="BstatsTestApp.apk" /> <option name="test-file-name" value="FrameworksServicesTests.apk" /> <option name="test-file-name" value="JobTestApp.apk" /> <option name="test-file-name" value="SuspendTestApp.apk" /> diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java index fc62e75b7d59..e79ac0986dc8 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -16,6 +16,7 @@ package com.android.server.biometrics; +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_CREDENTIAL; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.biometrics.BiometricManager.Authenticators; @@ -116,9 +117,9 @@ public class BiometricServiceTest { private static final String ERROR_LOCKOUT = "error_lockout"; private static final String FACE_SUBTITLE = "face_subtitle"; private static final String FINGERPRINT_SUBTITLE = "fingerprint_subtitle"; + private static final String CREDENTIAL_SUBTITLE = "credential_subtitle"; private static final String DEFAULT_SUBTITLE = "default_subtitle"; - private static final String FINGERPRINT_ACQUIRED_SENSOR_DIRTY = "sensor_dirty"; private static final int SENSOR_ID_FINGERPRINT = 0; @@ -143,6 +144,8 @@ public class BiometricServiceTest { @Mock IBiometricAuthenticator mFaceAuthenticator; @Mock + IBiometricAuthenticator mCredentialAuthenticator; + @Mock ITrustManager mTrustManager; @Mock DevicePolicyManager mDevicePolicyManager; @@ -196,10 +199,12 @@ public class BiometricServiceTest { .thenReturn(ERROR_NOT_RECOGNIZED); when(mResources.getString(R.string.biometric_error_user_canceled)) .thenReturn(ERROR_USER_CANCELED); - when(mContext.getString(R.string.biometric_dialog_face_subtitle)) + when(mContext.getString(R.string.face_dialog_default_subtitle)) .thenReturn(FACE_SUBTITLE); - when(mContext.getString(R.string.biometric_dialog_fingerprint_subtitle)) + when(mContext.getString(R.string.fingerprint_dialog_default_subtitle)) .thenReturn(FINGERPRINT_SUBTITLE); + when(mContext.getString(R.string.screen_lock_dialog_default_subtitle)) + .thenReturn(CREDENTIAL_SUBTITLE); when(mContext.getString(R.string.biometric_dialog_default_subtitle)) .thenReturn(DEFAULT_SUBTITLE); @@ -292,7 +297,8 @@ public class BiometricServiceTest { mBiometricService.onStart(); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, - Authenticators.DEVICE_CREDENTIAL); + Authenticators.DEVICE_CREDENTIAL, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); verify(mReceiver1).onError( eq(BiometricAuthenticator.TYPE_CREDENTIAL), @@ -312,7 +318,8 @@ public class BiometricServiceTest { mBiometricService.onStart(); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, - Authenticators.DEVICE_CREDENTIAL); + Authenticators.DEVICE_CREDENTIAL, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); assertNotNull(mBiometricService.mAuthSession); @@ -338,7 +345,8 @@ public class BiometricServiceTest { mBiometricService.onStart(); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, - null /* authenticators */); + null /* authenticators */, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); verify(mReceiver1).onError( eq(BiometricAuthenticator.TYPE_NONE), @@ -357,7 +365,8 @@ public class BiometricServiceTest { mFingerprintAuthenticator); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, - null /* authenticators */); + null /* authenticators */, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); verify(mReceiver1).onError( eq(TYPE_FINGERPRINT), @@ -370,7 +379,8 @@ public class BiometricServiceTest { setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, - Authenticators.BIOMETRIC_STRONG); + Authenticators.BIOMETRIC_STRONG, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); verify(mReceiver1).onError( eq(BiometricAuthenticator.TYPE_NONE), @@ -429,7 +439,8 @@ public class BiometricServiceTest { mFingerprintAuthenticator); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, - null /* authenticators */); + null /* authenticators */, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); verify(mReceiver1).onError( eq(TYPE_FINGERPRINT), @@ -441,9 +452,9 @@ public class BiometricServiceTest { public void testAuthenticateFace_shouldShowSubtitleForFace() throws Exception { setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG); - invokeAuthenticate(mBiometricService.mImpl, mReceiver1, - false /* requireConfirmation */, - null); + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, + null /* authenticators */, true /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); assertEquals(FACE_SUBTITLE, mBiometricService.mAuthSession.mPromptInfo.getSubtitle()); @@ -453,9 +464,9 @@ public class BiometricServiceTest { public void testAuthenticateFingerprint_shouldShowSubtitleForFingerprint() throws Exception { setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); - invokeAuthenticate(mBiometricService.mImpl, mReceiver1, - false /* requireConfirmation */, - null); + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, + null /* authenticators */, true /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); assertEquals(FINGERPRINT_SUBTITLE, @@ -463,6 +474,19 @@ public class BiometricServiceTest { } @Test + public void testAuthenticateFingerprint_shouldShowSubtitleForCredential() throws Exception { + setupAuthForOnly(TYPE_CREDENTIAL, Authenticators.DEVICE_CREDENTIAL); + + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, + null /* authenticators */, true /* useDefaultSubtitle */, + true /* deviceCredentialAllowed */); + waitForIdle(); + + assertEquals(CREDENTIAL_SUBTITLE, + mBiometricService.mAuthSession.mPromptInfo.getSubtitle()); + } + + @Test public void testAuthenticateBothFpAndFace_shouldShowDefaultSubtitle() throws Exception { final int[] modalities = new int[] { TYPE_FINGERPRINT, @@ -476,9 +500,9 @@ public class BiometricServiceTest { setupAuthForMultiple(modalities, strengths); - invokeAuthenticate(mBiometricService.mImpl, mReceiver1, - false /* requireConfirmation */, - null); + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, + null /* authenticators */, true /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); assertEquals(DEFAULT_SUBTITLE, mBiometricService.mAuthSession.mPromptInfo.getSubtitle()); @@ -492,7 +516,8 @@ public class BiometricServiceTest { // Disabled in user settings receives onError when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, - null /* authenticators */); + null /* authenticators */, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); verify(mReceiver1).onError( eq(BiometricAuthenticator.TYPE_NONE), @@ -506,7 +531,8 @@ public class BiometricServiceTest { anyInt() /* modality */, anyInt() /* userId */)) .thenReturn(true); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, - null /* authenticators */); + null /* authenticators */, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt()); final byte[] HAT = generateRandomHAT(); @@ -524,7 +550,8 @@ public class BiometricServiceTest { anyInt() /* modality */, anyInt() /* userId */)) .thenReturn(false); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, - null /* authenticators */); + null /* authenticators */, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationSucceeded( SENSOR_ID_FACE, @@ -552,7 +579,8 @@ public class BiometricServiceTest { throws Exception { // Start testing the happy path invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, - null /* authenticators */); + null /* authenticators */, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); // Creates a pending auth session with the correct initial states @@ -632,7 +660,8 @@ public class BiometricServiceTest { .thenReturn(true); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, true /* requireConfirmation */, - Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK); + Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK, + false /* useDefaultSubtitle*/, false /* deviceCredentialAllowed */); waitForIdle(); assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL, @@ -702,7 +731,8 @@ public class BiometricServiceTest { invokeAuthenticate(mBiometricService.mImpl, mReceiver1, true /* requireConfirmation */, - Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG); + Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG, + false /* useDefaultSubtitle */, false /* deviceCredentialAllowed */); waitForIdle(); verify(mReceiver1).onError(anyInt() /* modality */, @@ -754,7 +784,8 @@ public class BiometricServiceTest { false /* requireConfirmation */, null /* authenticators */); invokeAuthenticate(mBiometricService.mImpl, mReceiver2, false /* requireConfirmation */, - null /* authenticators */); + null /* authenticators */, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); verify(mReceiver1).onError( @@ -887,7 +918,8 @@ public class BiometricServiceTest { setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, - Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK); + Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK, + false /* useDefaultSubtitle */, false /* deviceCredentialAllowed */); waitForIdle(); assertEquals(STATE_AUTH_CALLED, mBiometricService.mAuthSession.getState()); @@ -920,8 +952,9 @@ public class BiometricServiceTest { public void testErrorFromHal_whilePreparingAuthentication_credentialNotAllowed() throws Exception { setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); - invokeAuthenticate(mBiometricService.mImpl, mReceiver1, - false /* requireConfirmation */, null /* authenticators */); + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, + null /* authenticators */, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); mBiometricService.mAuthSession.mSensorReceiver.onError( @@ -957,8 +990,9 @@ public class BiometricServiceTest { setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt())) .thenReturn(lockoutMode); - invokeAuthenticate(mBiometricService.mImpl, mReceiver1, - false /* requireConfirmation */, null /* authenticators */); + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, + null /* authenticators */, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); // Modality and error are sent @@ -996,8 +1030,9 @@ public class BiometricServiceTest { when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt())) .thenReturn(lockoutMode); when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false); - invokeAuthenticate(mBiometricService.mImpl, mReceiver1, - false /* requireConfirmation */, null /* authenticators */); + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, + null /* authenticators */, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); // The lockout error should be sent, instead of ERROR_NONE_ENROLLED. See b/286923477. @@ -1014,7 +1049,8 @@ public class BiometricServiceTest { .thenReturn(LockoutTracker.LOCKOUT_PERMANENT); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, - Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG); + Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG, + false /* useDefaultSubtitle */, false /* deviceCredentialAllowed */); waitForIdle(); verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt()); @@ -1503,7 +1539,8 @@ public class BiometricServiceTest { assertEquals(BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED, invokeCanAuthenticate(mBiometricService, authenticators)); long requestId = invokeAuthenticate(mBiometricService.mImpl, mReceiver1, - false /* requireConfirmation */, authenticators); + false /* requireConfirmation */, authenticators, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); verify(mReceiver1).onError( eq(TYPE_FINGERPRINT), @@ -1539,7 +1576,8 @@ public class BiometricServiceTest { invokeCanAuthenticate(mBiometricService, authenticators)); requestId = invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, - authenticators); + authenticators, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */); waitForIdle(); assertTrue(Utils.isCredentialRequested(mBiometricService.mAuthSession.mPromptInfo)); verify(mBiometricService.mStatusBarService).showAuthenticationDialog( @@ -1749,6 +1787,11 @@ public class BiometricServiceTest { mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FACE, modality, strength, mFaceAuthenticator); } + + if ((modality & TYPE_CREDENTIAL) != 0) { + when(mTrustManager.isDeviceSecure(anyInt(), anyInt())) + .thenReturn(true); + } } // TODO: Reduce duplicated code, currently we cannot start the BiometricService in setUp() for @@ -1799,7 +1842,8 @@ public class BiometricServiceTest { Integer authenticators) throws Exception { // Request auth, creates a pending session final long requestId = invokeAuthenticate( - service, receiver, requireConfirmation, authenticators); + service, receiver, requireConfirmation, authenticators, + false /* useDefaultSubtitle */, false /* deviceCredentialAllowed */); waitForIdle(); startPendingAuthSession(mBiometricService); @@ -1827,7 +1871,8 @@ public class BiometricServiceTest { private static long invokeAuthenticate(IBiometricService.Stub service, IBiometricServiceReceiver receiver, boolean requireConfirmation, - Integer authenticators) throws Exception { + Integer authenticators, boolean useDefaultSubtitle, + boolean deviceCredentialAllowed) throws Exception { return service.authenticate( new Binder() /* token */, 0 /* operationId */, @@ -1835,7 +1880,8 @@ public class BiometricServiceTest { receiver, TEST_PACKAGE_NAME /* packageName */, createTestPromptInfo(requireConfirmation, authenticators, - false /* checkDevicePolicy */)); + false /* checkDevicePolicy */, useDefaultSubtitle, + deviceCredentialAllowed)); } private static long invokeAuthenticateForWorkApp(IBiometricService.Stub service, @@ -1847,16 +1893,19 @@ public class BiometricServiceTest { receiver, TEST_PACKAGE_NAME /* packageName */, createTestPromptInfo(false /* requireConfirmation */, authenticators, - true /* checkDevicePolicy */)); + true /* checkDevicePolicy */, false /* useDefaultSubtitle */, + false /* deviceCredentialAllowed */)); } private static PromptInfo createTestPromptInfo( boolean requireConfirmation, Integer authenticators, - boolean checkDevicePolicy) { + boolean checkDevicePolicy, + boolean useDefaultSubtitle, + boolean deviceCredentialAllowed) { final PromptInfo promptInfo = new PromptInfo(); promptInfo.setConfirmationRequested(requireConfirmation); - promptInfo.setUseDefaultSubtitle(true); + promptInfo.setUseDefaultSubtitle(useDefaultSubtitle); if (authenticators != null) { promptInfo.setAuthenticators(authenticators); @@ -1864,6 +1913,7 @@ public class BiometricServiceTest { if (checkDevicePolicy) { promptInfo.setDisallowBiometricsIfPolicyExists(checkDevicePolicy); } + promptInfo.setDeviceCredentialAllowed(deviceCredentialAllowed); return promptInfo; } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java index a4423038a072..437510595ecb 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java @@ -252,6 +252,14 @@ public class BiometricContextProviderTest { } @Test + public void testSubscribesWithDifferentState() throws RemoteException { + final Consumer<OperationContext> nonEmptyConsumer = mock(Consumer.class); + mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_AOD); + mProvider.subscribe(mOpContext, nonEmptyConsumer); + verify(nonEmptyConsumer).accept(same(mOpContext.toAidlContext())); + } + + @Test public void testUnsubscribes() throws RemoteException { final Consumer<OperationContext> emptyConsumer = mock(Consumer.class); mProvider.subscribe(mOpContext, emptyConsumer); @@ -259,6 +267,9 @@ public class BiometricContextProviderTest { mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_AOD); + //reset to unknown to avoid trigger accept when subscribe + mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_UNKNOWN); + final Consumer<OperationContext> nonEmptyConsumer = mock(Consumer.class); mProvider.subscribe(mOpContext, nonEmptyConsumer); mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN); diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 4512cc025bf0..bcbbcd4456ee 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -363,7 +363,8 @@ public class VirtualDeviceManagerServiceTest { new CameraAccessController(mContext, mLocalService, mCameraAccessBlockedCallback); mAssociationInfo = new AssociationInfo(/* associationId= */ 1, 0, null, - MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, 0, 0, -1); + null, MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, + 0, 0, -1); mVdms = new VirtualDeviceManagerService(mContext); mLocalService = mVdms.getLocalServiceInstance(); diff --git a/services/tests/servicestests/src/com/android/server/display/TestUtils.java b/services/tests/servicestests/src/com/android/server/display/TestUtils.java deleted file mode 100644 index 8b45145b160f..000000000000 --- a/services/tests/servicestests/src/com/android/server/display/TestUtils.java +++ /dev/null @@ -1,94 +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 com.android.server.display; - -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.input.InputSensorInfo; -import android.os.Parcel; -import android.os.SystemClock; -import android.view.DisplayAddress; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -public final class TestUtils { - - public static SensorEvent createSensorEvent(Sensor sensor, int value) throws Exception { - final Constructor<SensorEvent> constructor = - SensorEvent.class.getDeclaredConstructor(int.class); - constructor.setAccessible(true); - final SensorEvent event = constructor.newInstance(1); - event.sensor = sensor; - event.values[0] = value; - event.timestamp = SystemClock.elapsedRealtimeNanos(); - return event; - } - - - public static void setSensorType(Sensor sensor, int type, String strType) throws Exception { - Method setter = Sensor.class.getDeclaredMethod("setType", Integer.TYPE); - setter.setAccessible(true); - setter.invoke(sensor, type); - if (strType != null) { - Field f = sensor.getClass().getDeclaredField("mStringType"); - f.setAccessible(true); - f.set(sensor, strType); - } - } - - public static void setMaximumRange(Sensor sensor, float maximumRange) throws Exception { - Method setter = Sensor.class.getDeclaredMethod("setRange", Float.TYPE, Float.TYPE); - setter.setAccessible(true); - setter.invoke(sensor, maximumRange, 1); - } - - public static Sensor createSensor(int type, String strType) throws Exception { - Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor(); - constr.setAccessible(true); - Sensor sensor = constr.newInstance(); - setSensorType(sensor, type, strType); - return sensor; - } - - public static Sensor createSensor(int type, String strType, float maximumRange) - throws Exception { - Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor(); - constr.setAccessible(true); - Sensor sensor = constr.newInstance(); - setSensorType(sensor, type, strType); - setMaximumRange(sensor, maximumRange); - return sensor; - } - - public static Sensor createSensor(String type, String name) { - return new Sensor(new InputSensorInfo( - name, "vendor", 0, 0, 0, 1f, 1f, 1, 1, 1, 1, - type, "", 0, 0, 0)); - } - - /** - * Create a custom {@link DisplayAddress} to ensure we're not relying on any specific - * display-address implementation in our code. Intentionally uses default object (reference) - * equality rules. - */ - public static class TestDisplayAddress extends DisplayAddress { - @Override - public void writeToParcel(Parcel out, int flags) { } - } -} diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt index f834cb24f245..560a91974692 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt @@ -138,6 +138,17 @@ class AndroidPackageParsingValidationTest { R.styleable.AndroidManifestApplication_taskAffinity, 1024 ) + validateTagAttr( + tag, + "zygotePreloadName", + R.styleable.AndroidManifestApplication_zygotePreloadName, + 1024 + ) + validateTagAttrComponentName( + tag, + "zygotePreloadName", + R.styleable.AndroidManifestApplication_zygotePreloadName + ) validateTagCount("profileable", 100, tag) validateTagCount("uses-native-library", 100, tag) validateTagCount("receiver", 1000, tag) @@ -507,8 +518,26 @@ class AndroidPackageParsingValidationTest { } } - val failNames = arrayOf("com.android.TestClass:", "-TestClass", "TestClass.", ".", "..") - for (name in failNames) { + val badNames = arrayOf( + ";", + ",", + "[", + "]", + "(", + ")", + "{", + "}", + ":", + "?", + "-", + "%", + "^", + "*", + "|", + "/", + "\\" + ) + for (name in badNames) { val xml = "<$tag $attr=\"$name\" />" pullParser.setInput(ByteArrayInputStream(xml.toByteArray()), null) val validator = Validator() diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTests.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTests.java deleted file mode 100644 index 48290e5ca1ef..000000000000 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTests.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.power.stats; - -import org.junit.runner.RunWith; -import org.junit.runners.Suite; - -@RunWith(Suite.class) -@Suite.SuiteClasses({ - AmbientDisplayPowerCalculatorTest.class, - AudioPowerCalculatorTest.class, - BatteryChargeCalculatorTest.class, - BatteryExternalStatsWorkerTest.class, - BatteryStatsCpuTimesTest.class, - BatteryStatsBackgroundStatsTest.class, - BatteryStatsBinderCallStatsTest.class, - BatteryStatsCounterTest.class, - BatteryStatsDualTimerTest.class, - BatteryStatsDurationTimerTest.class, - BatteryStatsHistoryIteratorTest.class, - BatteryStatsHistoryTest.class, - BatteryStatsImplTest.class, - BatteryStatsManagerTest.class, - BatteryStatsNoteTest.class, - BatteryStatsSamplingTimerTest.class, - BatteryStatsSensorTest.class, - BatteryStatsServTest.class, - BatteryStatsStopwatchTimerTest.class, - BatteryStatsTimeBaseTest.class, - BatteryStatsTimerTest.class, - BatteryUsageStatsProviderTest.class, - BatteryUsageStatsTest.class, - BatteryUsageStatsStoreTest.class, - BatteryStatsUserLifecycleTests.class, - BluetoothPowerCalculatorTest.class, - BstatsCpuTimesValidationTest.class, - CameraPowerCalculatorTest.class, - CpuPowerCalculatorTest.class, - CustomEnergyConsumerPowerCalculatorTest.class, - FlashlightPowerCalculatorTest.class, - GnssPowerCalculatorTest.class, - IdlePowerCalculatorTest.class, - KernelWakelockReaderTest.class, - LongSamplingCounterTest.class, - LongSamplingCounterArrayTest.class, - EnergyConsumerSnapshotTest.class, - MobileRadioPowerCalculatorTest.class, - ScreenPowerCalculatorTest.class, - SensorPowerCalculatorTest.class, - SystemServerCpuThreadReaderTest.class, - SystemServicePowerCalculatorTest.class, - UserPowerCalculatorTest.class, - VideoPowerCalculatorTest.class, - WakelockPowerCalculatorTest.class, - WifiPowerCalculatorTest.class, -}) -public class BatteryStatsTests { -} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java index 95fae0707304..22b112746b50 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java @@ -15,8 +15,6 @@ */ package com.android.server.notification; -import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.NO_SORT_BY_INTERRUPTIVENESS; - import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertFalse; @@ -47,6 +45,8 @@ import android.service.notification.StatusBarNotification; import android.telecom.TelecomManager; import android.test.suitebuilder.annotation.SmallTest; +import androidx.test.runner.AndroidJUnit4; + import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; import com.android.server.UiServiceTestCase; @@ -54,7 +54,6 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -63,7 +62,7 @@ import java.util.Collections; import java.util.List; @SmallTest -@RunWith(Parameterized.class) +@RunWith(AndroidJUnit4.class) public class NotificationComparatorTest extends UiServiceTestCase { @Mock Context mMockContext; @Mock TelecomManager mTm; @@ -97,24 +96,9 @@ public class NotificationComparatorTest extends UiServiceTestCase { private NotificationRecord mRecordColorized; private NotificationRecord mRecordColorizedCall; - @Parameterized.Parameters(name = "sortByInterruptiveness={0}") - public static Boolean[] getSortByInterruptiveness() { - return new Boolean[] { true, false }; - } - - @Parameterized.Parameter - public boolean mSortByInterruptiveness; - @Before public void setUp() { MockitoAnnotations.initMocks(this); - SystemUiSystemPropertiesFlags.TEST_RESOLVER = flag -> { - if (flag.mSysPropKey.equals(NO_SORT_BY_INTERRUPTIVENESS.mSysPropKey)) { - return !mSortByInterruptiveness; - } - return new SystemUiSystemPropertiesFlags.DebugResolver().isEnabled(flag); - }; - int userId = UserHandle.myUserId(); final Resources res = mContext.getResources(); @@ -309,13 +293,8 @@ public class NotificationComparatorTest extends UiServiceTestCase { expected.add(mNoMediaSessionMedia); expected.add(mRecordCheater); expected.add(mRecordCheaterColorized); - if (mSortByInterruptiveness) { - expected.add(mRecordMinCall); - expected.add(mRecordMinCallNonInterruptive); - } else { - expected.add(mRecordMinCallNonInterruptive); - expected.add(mRecordMinCall); - } + expected.add(mRecordMinCallNonInterruptive); + expected.add(mRecordMinCall); List<NotificationRecord> actual = new ArrayList<>(); actual.addAll(expected); @@ -330,11 +309,7 @@ public class NotificationComparatorTest extends UiServiceTestCase { public void testRankingScoreOverrides() { NotificationComparator comp = new NotificationComparator(mMockContext); NotificationRecord recordMinCallNonInterruptive = spy(mRecordMinCallNonInterruptive); - if (mSortByInterruptiveness) { - assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) < 0); - } else { - assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) > 0); - } + assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) > 0); when(recordMinCallNonInterruptive.getRankingScore()).thenReturn(1f); assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) > 0); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java index 939ff97ab02e..c4302db16b98 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java @@ -1194,6 +1194,15 @@ public class DisplayRotationTests { } @Test + public void testIsFixedToUserRotation_displayContentOrientationFixed() throws Exception { + mBuilder.build(); + when(mMockDisplayContent.isDisplayOrientationFixed()).thenReturn(true); + + assertFalse("Display rotation should respect app requested orientation if" + + " the display has fixed orientation.", mTarget.isFixedToUserRotation()); + } + + @Test public void testIsFixedToUserRotation_FixedToUserRotationIfNoAutoRotation() throws Exception { mBuilder.build(); mTarget.setFixedToUserRotation(FIXED_TO_USER_ROTATION_IF_NO_AUTO_ROTATION); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java index 57a397facc61..d85d9b5729a0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java @@ -34,25 +34,30 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.MockitoAnnotations.initMocks; -import android.app.IWindowToken; +import android.app.ClientTransactionHandler; +import android.app.servertransaction.ClientTransactionItem; +import android.app.servertransaction.WindowContextInfoChangeItem; import android.content.res.Configuration; import android.graphics.Rect; -import android.os.Binder; import android.os.Bundle; -import android.os.IBinder; import android.platform.test.annotations.Presubmit; import android.view.Display; import android.view.DisplayInfo; +import android.window.WindowContextInfo; +import android.window.WindowTokenClient; import androidx.test.filters.SmallTest; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.Mockito; /** @@ -68,20 +73,47 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { private static final int TEST_UID = 12345; private static final int ANOTHER_UID = 1000; + @Mock + private ClientTransactionHandler mHandler; + @Mock + private WindowTokenClient mClientToken; + private WindowProcessController mWpc; - private final IBinder mClientToken = new Binder(); private WindowContainer<?> mContainer; @Before public void setUp() { + initMocks(this); mController = new WindowContextListenerController(); mContainer = createTestWindowToken(TYPE_APPLICATION_OVERLAY, mDisplayContent); // Make display on to verify configuration propagation. mDefaultDisplay.getDisplayInfo().state = STATE_ON; mDisplayContent.getDisplayInfo().state = STATE_ON; + mWpc = mSystemServicesTestRule.addProcess( DEFAULT_COMPONENT_PACKAGE_NAME, DEFAULT_COMPONENT_PACKAGE_NAME, 0 /* pid */, TEST_UID); + // Mock the behaviors on ClientTransaction + spyOn(mWpc); + doAnswer(invocation -> { + // Mock ActivityThread + final Object[] args = invocation.getArguments(); + final WindowTokenClient clientToken = (WindowTokenClient) args[0]; + final WindowContextInfo info = (WindowContextInfo) args[1]; + clientToken.onConfigurationChanged(info.getConfiguration(), info.getDisplayId()); + return null; + }).when(mHandler).handleWindowContextInfoChanged(any(), any()); + doAnswer(invocation -> { + // Mock WindowProcessController + final Object[] args = invocation.getArguments(); + final ClientTransactionItem item = (ClientTransactionItem) args[0]; + if (!(item instanceof WindowContextInfoChangeItem)) { + return null; + } + final WindowContextInfoChangeItem infoChangeItem = (WindowContextInfoChangeItem) item; + infoChangeItem.execute(mHandler, null, null); + return null; + }).when(mWpc).scheduleClientTransactionItem(any()); } @Test @@ -91,7 +123,7 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { assertEquals(1, mController.mListeners.size()); - final IBinder clientToken = mock(IBinder.class); + final WindowTokenClient clientToken = mock(WindowTokenClient.class); mController.registerWindowContainerListener(mWpc, clientToken, mContainer, TYPE_APPLICATION_OVERLAY, null /* options */); @@ -304,7 +336,7 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { assertThat(clientToken.mDisplayId).isEqualTo(mDisplayContent.mDisplayId); } - private static class TestWindowTokenClient extends IWindowToken.Stub { + private static class TestWindowTokenClient extends WindowTokenClient { private Configuration mConfiguration; private int mDisplayId; private boolean mRemoved; diff --git a/services/usage/java/com/android/server/usage/TEST_MAPPING b/services/usage/java/com/android/server/usage/TEST_MAPPING index a3fe6f2876d7..6e845433492b 100644 --- a/services/usage/java/com/android/server/usage/TEST_MAPPING +++ b/services/usage/java/com/android/server/usage/TEST_MAPPING @@ -20,22 +20,13 @@ ] }, { - "name": "CtsUsageStatsTestCases", + "name": "CtsBRSTestCases", "options": [ { - "include-filter": "android.app.usage.cts.BroadcastResponseStatsTest" - }, - { "exclude-annotation": "androidx.test.filters.FlakyTest" }, { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" - }, - { - "exclude-annotation": "androidx.test.filters.MediumTest" - }, - { - "exclude-annotation": "androidx.test.filters.LargeTest" + "exclude-annotation": "org.junit.Ignore" } ] } diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index 78b86d398718..5bdcdf4d63ef 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -2661,7 +2661,9 @@ public final class Call { // remove ourselves from the Phone. Note that we do this after completing all state updates // so a client can cleanly transition all their UI to the state appropriate for a // DISCONNECTED Call while still relying on the existence of that Call in the Phone's list. - if (mState == STATE_DISCONNECTED) { + // Check if the original state is already disconnected, otherwise onCallRemoved will be + // triggered before onCallAdded. + if (mState == STATE_DISCONNECTED && stateChanged) { fireCallDestroyed(); } } diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java index 95a8e16ace3d..61e829e75930 100644 --- a/telecomm/java/android/telecom/Phone.java +++ b/telecomm/java/android/telecom/Phone.java @@ -174,6 +174,9 @@ public final class Phone { checkCallTree(parcelableCall); call.internalUpdate(parcelableCall, mCallByTelecomCallId); fireCallAdded(call); + if (call.getState() == Call.STATE_DISCONNECTED) { + internalRemoveCall(call); + } } else { Log.w(this, "Call %s added, but it was already present", call.internalGetCallId()); checkCallTree(parcelableCall); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 314150b3f58c..4907134ffe1f 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -9418,6 +9418,7 @@ public class CarrierConfigManager { * <li>3 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_SMS}</li> * <li>4 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VIDEO}</li> * <li>5 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_EMERGENCY}</li> + * <li>6 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_MMS}</li> * </ul> * <p> * An example config for two PLMNs "123411" and "123412": diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java index 6258b9c74282..631013fc485e 100644 --- a/telephony/java/android/telephony/NetworkRegistrationInfo.java +++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java @@ -170,7 +170,7 @@ public final class NetworkRegistrationInfo implements Parcelable { @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = "SERVICE_TYPE_", value = {SERVICE_TYPE_UNKNOWN, SERVICE_TYPE_VOICE, SERVICE_TYPE_DATA, SERVICE_TYPE_SMS, - SERVICE_TYPE_VIDEO, SERVICE_TYPE_EMERGENCY}) + SERVICE_TYPE_VIDEO, SERVICE_TYPE_EMERGENCY, SERVICE_TYPE_MMS}) public @interface ServiceType {} /** @@ -203,11 +203,16 @@ public final class NetworkRegistrationInfo implements Parcelable { */ public static final int SERVICE_TYPE_EMERGENCY = 5; + /** + * MMS service + */ + public static final int SERVICE_TYPE_MMS = 6; + /** @hide */ public static final int FIRST_SERVICE_TYPE = SERVICE_TYPE_VOICE; /** @hide */ - public static final int LAST_SERVICE_TYPE = SERVICE_TYPE_EMERGENCY; + public static final int LAST_SERVICE_TYPE = SERVICE_TYPE_MMS; @Domain private final int mDomain; @@ -739,6 +744,7 @@ public final class NetworkRegistrationInfo implements Parcelable { case SERVICE_TYPE_SMS: return "SMS"; case SERVICE_TYPE_VIDEO: return "VIDEO"; case SERVICE_TYPE_EMERGENCY: return "EMERGENCY"; + case SERVICE_TYPE_MMS: return "MMS"; } return "Unknown service type " + serviceType; } diff --git a/tests/CompanionDeviceMultiDeviceTests/host/Android.bp b/tests/CompanionDeviceMultiDeviceTests/host/Android.bp index 1167a3e30ed4..03335c7cd576 100644 --- a/tests/CompanionDeviceMultiDeviceTests/host/Android.bp +++ b/tests/CompanionDeviceMultiDeviceTests/host/Android.bp @@ -37,7 +37,6 @@ python_test_host { }, data: [ ":cdm_snippet", - "requirements.txt", ], version: { py2: { diff --git a/tests/CompanionDeviceMultiDeviceTests/host/cdm_transport_test.py b/tests/CompanionDeviceMultiDeviceTests/host/cdm_transport_test.py index 9cb2d10fd2ee..5516c0f8e09f 100644 --- a/tests/CompanionDeviceMultiDeviceTests/host/cdm_transport_test.py +++ b/tests/CompanionDeviceMultiDeviceTests/host/cdm_transport_test.py @@ -25,12 +25,4 @@ class TransportTestClass(cdm_base_test.BaseTestClass): if __name__ == '__main__': - try: - # Take test args and remove standalone '--' from the list - index = sys.argv.index('--') - sys.argv = sys.argv[:1] + sys.argv[index + 1:] - except ValueError: - # Ignore if '--' is not in args - pass - test_runner.main()
\ No newline at end of file diff --git a/tests/CompanionDeviceMultiDeviceTests/host/requirements.txt b/tests/CompanionDeviceMultiDeviceTests/host/requirements.txt deleted file mode 100644 index 86a11aa0abf1..000000000000 --- a/tests/CompanionDeviceMultiDeviceTests/host/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -mobly==1.12.1 diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TransferSplashscreenAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TransferSplashscreenAppHelper.kt new file mode 100644 index 000000000000..2a2b70e6a20f --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TransferSplashscreenAppHelper.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.helpers + +import android.app.Instrumentation +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.traces.parsers.toFlickerComponent +import com.android.server.wm.flicker.testapp.ActivityOptions + +class TransferSplashscreenAppHelper +@JvmOverloads +constructor( + instr: Instrumentation, + launcherName: String = ActivityOptions.TransferSplashscreenActivity.LABEL, + component: ComponentNameMatcher = + ActivityOptions.TransferSplashscreenActivity.COMPONENT.toFlickerComponent() +) : StandardAppHelper(instr, launcherName, component)
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt index 3a784ff30e91..ec792ca27685 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt @@ -53,7 +53,8 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class OpenAppFromIconColdTest(flicker: LegacyFlickerTest) : OpenAppFromLauncherTransition(flicker) { +open class OpenAppFromIconColdTest(flicker: LegacyFlickerTest) : + OpenAppFromLauncherTransition(flicker) { /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit get() = { @@ -87,6 +88,7 @@ class OpenAppFromIconColdTest(flicker: LegacyFlickerTest) : OpenAppFromLauncherT override fun appWindowReplacesLauncherAsTopWindow() { super.appWindowReplacesLauncherAsTopWindow() } + @FlakyTest(bugId = 240916028) @Test override fun appWindowAsTopWindowAtEnd() { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt new file mode 100644 index 000000000000..1fdef3c53224 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.launch + +import android.platform.test.annotations.Presubmit +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.helpers.TransferSplashscreenAppHelper +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test cold launching an app from launcher + * + * To run this test: `atest FlickerTests:OpenTransferSplashscreenAppFromLauncherTransition` + * + * Actions: + * ``` + * Inherit from OpenAppFromIconColdTest, Launch an app [testApp] with an animated splash screen + * by clicking it's icon on all apps, and wait for transfer splash screen complete + * ``` + * + * Notes: + * ``` + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [OpenAppTransition] + * 2. Verify no flickering when transfer splash screen to app window. + * ``` + */ + +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class OpenTransferSplashscreenAppFromLauncherTransition(flicker: LegacyFlickerTest) : + OpenAppFromIconColdTest(flicker) { + override val testApp = TransferSplashscreenAppHelper(instrumentation) + + /** + * Checks that [ComponentNameMatcher.LAUNCHER] window is the top window at the start of the + * transition, and is replaced by [ComponentNameMatcher.SPLASH_SCREEN], then [testApp] remains + * visible until the end + */ + @Presubmit + @Test + fun appWindowAfterSplash() { + flicker.assertWm { + this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) + .then() + .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN) + .then() + .isAppWindowOnTop(testApp) + .isAppWindowInvisible(ComponentNameMatcher.SPLASH_SCREEN) + } + } + + companion object { + /** + * Creates the test configurations. + * + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape.kt index b34da72ea5d6..4adcc8bf5f0b 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape.kt @@ -25,10 +25,12 @@ import android.tools.common.flicker.config.FlickerConfig import android.tools.common.flicker.config.FlickerServiceConfig import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith @RunWith(FlickerServiceJUnit4ClassRunner::class) +@Ignore("b/294418322: no notification launch animation exists when keyguard is occluded") class OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape : OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) { @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"]) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait.kt index b1638979cd0d..f7211e7287cf 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait.kt @@ -25,10 +25,12 @@ import android.tools.common.flicker.config.FlickerConfig import android.tools.common.flicker.config.FlickerServiceConfig import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith @RunWith(FlickerServiceJUnit4ClassRunner::class) +@Ignore("b/294418322: no notification launch animation exists when keyguard is occluded") class OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait : OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) { @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"]) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape.kt index 19b533ed57af..1ade9560d90f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape.kt @@ -25,10 +25,12 @@ import android.tools.common.flicker.config.FlickerConfig import android.tools.common.flicker.config.FlickerServiceConfig import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith @RunWith(FlickerServiceJUnit4ClassRunner::class) +@Ignore("b/294418322: no notification launch animation exists when keyguard is occluded") class OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape : OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) { @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"]) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait.kt index c9ed4f42af06..ea26f0867c7f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait.kt @@ -25,10 +25,12 @@ import android.tools.common.flicker.config.FlickerConfig import android.tools.common.flicker.config.FlickerServiceConfig import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith @RunWith(FlickerServiceJUnit4ClassRunner::class) +@Ignore("b/294418322: no notification launch animation exists when keyguard is occluded") class OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait : OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) { @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"]) diff --git a/tests/FlickerTests/test-apps/flickerapp/Android.bp b/tests/FlickerTests/test-apps/flickerapp/Android.bp index 75e35ee9c765..e3b23b986c83 100644 --- a/tests/FlickerTests/test-apps/flickerapp/Android.bp +++ b/tests/FlickerTests/test-apps/flickerapp/Android.bp @@ -24,6 +24,9 @@ package { android_test { name: "FlickerTestApp", srcs: ["**/*.java"], + resource_dirs: [ + "res", + ], sdk_version: "current", test_suites: ["device-tests"], static_libs: [ diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index ff9799a1c710..704798e11016 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -347,6 +347,17 @@ android:exported="false" android:theme="@style/CutoutShortEdges" android:resizeableActivity="true"/> + <activity + android:name=".TransferSplashscreenActivity" + android:taskAffinity="com.android.server.wm.flicker.testapp.TransferSplashscreenActivity" + android:label="TransferSplashscreenActivity" + android:theme="@style/SplashscreenAppTheme" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> <service android:name=".AssistantInteractionSessionService" android:exported="true" diff --git a/tests/FlickerTests/test-apps/flickerapp/res/drawable/avd_anim.xml b/tests/FlickerTests/test-apps/flickerapp/res/drawable/avd_anim.xml new file mode 100644 index 000000000000..19205d4d1c14 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/drawable/avd_anim.xml @@ -0,0 +1,94 @@ +<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="432dp" android:width="432dp" android:viewportHeight="432" android:viewportWidth="432"> + <group android:name="_R_G"> + <group android:name="_R_G_L_5_G" android:translateX="216" android:translateY="216" android:scaleX="1.5" android:scaleY="1.5"> + <path android:name="_R_G_L_5_G_D_0_P_0" android:fillColor="#555555" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M53.82 -56.7 C53.82,-56.7 43.64,-49.06 43.64,-49.06 C43.64,-49.06 0,-16.34 0,-16.34 C0,-16.34 -43.64,-49.06 -43.64,-49.06 C-43.64,-49.06 -53.82,-56.7 -53.82,-56.7 C-64.6,-64.79 -80,-57.09 -80,-43.61 C-80,-43.61 -80,-29.07 -80,-29.07 C-80,-29.07 -80,49.09 -80,49.09 C-80,55.12 -75.12,60 -69.09,60 C-69.09,60 -43.64,60 -43.64,60 C-43.64,60 -43.64,-1.8 -43.64,-1.8 C-43.64,-1.8 0,30.92 0,30.92 C0,30.92 43.64,-1.8 43.64,-1.8 C43.64,-1.8 43.64,60 43.64,60 C43.64,60 69.09,60 69.09,60 C75.12,60 80,55.12 80,49.09 C80,49.09 80,-29.07 80,-29.07 C80,-29.07 80,-43.61 80,-43.61 C80,-57.09 64.61,-64.79 53.82,-56.7c "/> + </group> + <group android:name="_R_G_L_4_G" android:translateX="216" android:translateY="216" android:scaleX="1.5" android:scaleY="1.5"> + <clip-path android:name="mask_x" android:pathData="M53.82 -56.7 C53.82,-56.7 43.64,-49.06 43.64,-49.06 C43.64,-49.06 0,-16.34 0,-16.34 C0,-16.34 -43.64,-49.06 -43.64,-49.06 C-43.64,-49.06 -53.82,-56.7 -53.82,-56.7 C-64.6,-64.79 -80,-57.09 -80,-43.61 C-80,-43.61 -80,-29.07 -80,-29.07 C-80,-29.07 -80,49.09 -80,49.09 C-80,55.12 -75.12,60 -69.09,60 C-69.09,60 -43.64,60 -43.64,60 C-43.64,60 -43.64,-1.8 -43.64,-1.8 C-43.64,-1.8 0,30.92 0,30.92 C0,30.92 43.64,-1.8 43.64,-1.8 C43.64,-1.8 43.64,60 43.64,60 C43.64,60 69.09,60 69.09,60 C75.12,60 80,55.12 80,49.09 C80,49.09 80,-29.07 80,-29.07 C80,-29.07 80,-43.61 80,-43.61 C80,-57.09 64.61,-64.79 53.82,-56.7c"/> + <path android:name="_R_G_L_4_G_D_0_P_0" android:fillColor="#2684fc" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-80.39 60 C-80.39,60 -105.84,60 -105.84,60 C-111.87,60 -116.75,55.12 -116.75,49.09 C-116.75,49.09 -116.75,-60 -116.75,-60 C-116.75,-60 -80.39,-60 -80.39,-60 C-80.39,-60 -80.39,60 -80.39,60c "/> + </group> + <group android:name="_R_G_L_3_G" android:translateX="216" android:translateY="216" android:scaleX="1.5" android:scaleY="1.5"> + <clip-path android:name="mask_x" android:pathData="M53.82 -56.7 C53.82,-56.7 43.64,-49.06 43.64,-49.06 C43.64,-49.06 0,-16.34 0,-16.34 C0,-16.34 -43.64,-49.06 -43.64,-49.06 C-43.64,-49.06 -53.82,-56.7 -53.82,-56.7 C-64.6,-64.79 -80,-57.09 -80,-43.61 C-80,-43.61 -80,-29.07 -80,-29.07 C-80,-29.07 -80,49.09 -80,49.09 C-80,55.12 -75.12,60 -69.09,60 C-69.09,60 -43.64,60 -43.64,60 C-43.64,60 -43.64,-1.8 -43.64,-1.8 C-43.64,-1.8 0,30.92 0,30.92 C0,30.92 43.64,-1.8 43.64,-1.8 C43.64,-1.8 43.64,60 43.64,60 C43.64,60 69.09,60 69.09,60 C75.12,60 80,55.12 80,49.09 C80,49.09 80,-29.07 80,-29.07 C80,-29.07 80,-43.61 80,-43.61 C80,-57.09 64.61,-64.79 53.82,-56.7c"/> + <path android:name="_R_G_L_3_G_D_0_P_0" android:fillColor="#00ac47" android:fillAlpha="1" android:fillType="nonZero" android:pathData="M80.64 60 C80.64,60 106.09,60 106.09,60 C112.12,60 117,55.12 117,49.09 C117,49.09 117,-60 117,-60 C117,-60 80.64,-60 80.64,-60 C80.64,-60 80.64,60 80.64,60c "/> + </group> + <group android:name="_R_G_L_2_G" android:translateX="216" android:translateY="216" android:scaleX="1.5" android:scaleY="1.5"> + <clip-path android:name="mask_x" android:pathData="M53.82 -56.7 C53.82,-56.7 43.64,-49.06 43.64,-49.06 C43.64,-49.06 0,-16.34 0,-16.34 C0,-16.34 -43.64,-49.06 -43.64,-49.06 C-43.64,-49.06 -53.82,-56.7 -53.82,-56.7 C-64.6,-64.79 -80,-57.09 -80,-43.61 C-80,-43.61 -80,-29.07 -80,-29.07 C-80,-29.07 -80,49.09 -80,49.09 C-80,55.12 -75.12,60 -69.09,60 C-69.09,60 -43.64,60 -43.64,60 C-43.64,60 -43.64,-1.8 -43.64,-1.8 C-43.64,-1.8 0,30.92 0,30.92 C0,30.92 43.64,-1.8 43.64,-1.8 C43.64,-1.8 43.64,60 43.64,60 C43.64,60 69.09,60 69.09,60 C75.12,60 80,55.12 80,49.09 C80,49.09 80,-29.07 80,-29.07 C80,-29.07 80,-43.61 80,-43.61 C80,-57.09 64.61,-64.79 53.82,-56.7c"/> + <path android:name="_R_G_L_2_G_D_0_P_0" android:fillColor="#fe2c25" android:fillAlpha="1" android:fillType="nonZero" android:pathData="M53.82 -104.7 C53.82,-104.7 0,-64.34 0,-64.34 C0,-64.34 -53.82,-104.7 -53.82,-104.7 C-64.6,-112.79 -80,-105.09 -80,-91.61 C-80,-91.61 -80,-77.07 -80,-77.07 C-80,-77.07 0,-17.08 0,-17.08 C0,-17.08 80,-77.07 80,-77.07 C80,-77.07 80,-91.61 80,-91.61 C80,-105.09 64.61,-112.79 53.82,-104.7c "/> + </group> + <group android:name="_R_G_L_1_G" android:translateX="216" android:translateY="216" android:scaleX="1.5" android:scaleY="1.5"> + <clip-path android:name="mask_x" android:pathData="M43.64 -1.8 C43.64,-1.8 43.64,-49.06 43.64,-49.06 C43.64,-49.06 53.82,-56.7 53.82,-56.7 C64.61,-64.79 80,-57.09 80,-43.61 C80,-43.61 80,-1.8 80,-1.8 C80,-1.8 43.64,-1.8 43.64,-1.8c"/> + <path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffba00" android:fillAlpha="1" android:fillType="nonZero" android:pathData="M80.64 -135 C80.64,-135 117,-135 117,-135 C117,-135 117,-104.07 117,-104.07 C117,-104.07 80.64,-76.8 80.64,-76.8 C80.64,-76.8 80.64,-135 80.64,-135c "/> + </group> + <group android:name="_R_G_L_0_G" android:translateX="216" android:translateY="216" android:scaleX="1.5" android:scaleY="1.5"> + <clip-path android:name="mask_x" android:pathData="M-43.64 -1.8 C-43.64,-1.8 -80,-1.8 -80,-1.8 C-80,-1.8 -80,-43.61 -80,-43.61 C-80,-57.09 -64.6,-64.79 -53.82,-56.7 C-53.82,-56.7 -43.64,-49.06 -43.64,-49.06 C-43.64,-49.06 -43.64,-1.8 -43.64,-1.8c"/> + <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#d70007" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-117 -104.07 C-117,-104.07 -117,-135 -117,-135 C-117,-135 -80.64,-135 -80.64,-135 C-80.64,-135 -80.64,-76.8 -80.64,-76.8 C-80.64,-76.8 -117,-104.07 -117,-104.07c "/> + </group> + </group> + <group android:name="time_group"/> + </vector> + </aapt:attr> + <target android:name="_R_G_L_4_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" android:duration="1567" android:startOffset="233" android:valueFrom="M-80.39 60 C-80.39,60 -105.84,60 -105.84,60 C-111.87,60 -116.75,55.12 -116.75,49.09 C-116.75,49.09 -116.75,-60 -116.75,-60 C-116.75,-60 -80.39,-60 -80.39,-60 C-80.39,-60 -80.39,60 -80.39,60c " android:valueTo=" M-43.64 60 C-43.64,60 -69.09,60 -69.09,60 C-75.12,60 -80,55.12 -80,49.09 C-80,49.09 -80,-60 -80,-60 C-80,-60 -43.64,-60 -43.64,-60 C-43.64,-60 -43.64,60 -43.64,60c " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_3_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" android:duration="1567" android:startOffset="233" android:valueFrom=" M80.64 60 C80.64,60 106.09,60 106.09,60 C112.12,60 117,55.12 117,49.09 C117,49.09 117,-60 117,-60 C117,-60 80.64,-60 80.64,-60 C80.64,-60 80.64,60 80.64,60c " android:valueTo=" M43.64 60 C43.64,60 69.09,60 69.09,60 C75.12,60 80,55.12 80,49.09 C80,49.09 80,-60 80,-60 C80,-60 43.64,-60 43.64,-60 C43.64,-60 43.64,60 43.64,60c " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0,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="pathData" android:duration="1567" android:startOffset="233" android:valueFrom="M53.82 -104.7 C53.82,-104.7 0,-64.34 0,-64.34 C0,-64.34 -53.82,-104.7 -53.82,-104.7 C-64.6,-112.79 -80,-105.09 -80,-91.61 C-80,-91.61 -80,-77.07 -80,-77.07 C-80,-77.07 0,-17.08 0,-17.08 C0,-17.08 80,-77.07 80,-77.07 C80,-77.07 80,-91.61 80,-91.61 C80,-105.09 64.61,-112.79 53.82,-104.7c" android:valueTo="M53.82 -56.7 C53.82,-56.7 0,-16.34 0,-16.34 C0,-16.34 -53.82,-56.7 -53.82,-56.7 C-64.6,-64.79 -80,-57.09 -80,-43.61 C-80,-43.61 -80,-29.07 -80,-29.07 C-80,-29.07 0,30.92 0,30.92 C0,30.92 80,-29.07 80,-29.07 C80,-29.07 80,-43.61 80,-43.61 C80,-57.09 64.61,-64.79 53.82,-56.7c " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <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="1567" android:startOffset="233" android:valueFrom=" M80.64 -135 C80.64,-135 117,-135 117,-135 C117,-135 117,-104.07 117,-104.07 C117,-104.07 80.64,-76.8 80.64,-76.8 C80.64,-76.8 80.64,-135 80.64,-135c " android:valueTo=" M43.64 -60 C43.64,-60 80,-60 80,-60 C80,-60 80,-29.07 80,-29.07 C80,-29.07 43.64,-1.8 43.64,-1.8 C43.64,-1.8 43.64,-60 43.64,-60c " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0,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="pathData" android:duration="1567" android:startOffset="233" android:valueFrom=" M-117 -104.07 C-117,-104.07 -117,-135 -117,-135 C-117,-135 -80.64,-135 -80.64,-135 C-80.64,-135 -80.64,-76.8 -80.64,-76.8 C-80.64,-76.8 -117,-104.07 -117,-104.07c " android:valueTo=" M-80 -29.07 C-80,-29.07 -80,-60 -80,-60 C-80,-60 -43.64,-60 -43.64,-60 C-43.64,-60 -43.64,-1.8 -43.64,-1.8 C-43.64,-1.8 -80,-29.07 -80,-29.07c " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 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="2017" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/> + </set> + </aapt:attr> + </target> +</animated-vector> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml index e51ed29adebf..9b742d96e35b 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml @@ -53,4 +53,11 @@ <style name="no_starting_window" parent="@android:style/Theme.DeviceDefault"> <item name="android:windowDisablePreview">true</item> </style> + + <style name="SplashscreenAppTheme" parent="@android:style/Theme.DeviceDefault"> + <!-- Splashscreen Attributes --> + <item name="android:windowSplashScreenAnimatedIcon">@drawable/avd_anim</item> + <!-- Here we want to match the duration of our AVD --> + <item name="android:windowSplashScreenAnimationDuration">900</item> + </style> </resources> diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java index 2795a6c43015..7c5e9a3f86b5 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java @@ -178,6 +178,12 @@ public class ActivityOptions { FLICKER_APP_PACKAGE + ".LaunchNewActivity"); } + public static class TransferSplashscreenActivity { + public static final String LABEL = "TransferSplashscreenActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".TransferSplashscreenActivity"); + } + public static class Pip { // Test App > Pip Activity public static final String LABEL = "PipActivity"; diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransferSplashscreenActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransferSplashscreenActivity.java new file mode 100644 index 000000000000..0323286a0e21 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransferSplashscreenActivity.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.testapp; + +import android.app.Activity; +import android.os.Bundle; +import android.os.SystemClock; +import android.view.View; +import android.view.ViewTreeObserver; +import android.window.SplashScreen; +import android.window.SplashScreenView; + +public class TransferSplashscreenActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final SplashScreen splashScreen = getSplashScreen(); + // Register setOnExitAnimationListener to transfer the splash screen window to client. + splashScreen.setOnExitAnimationListener(this::onSplashScreenExit); + final View content = findViewById(android.R.id.content); + // By register preDrawListener to defer app window draw signal about 500ms, which to ensure + // the splash screen must show when cold launch. + content.getViewTreeObserver().addOnPreDrawListener( + new ViewTreeObserver.OnPreDrawListener() { + final long mCreateTime = SystemClock.uptimeMillis(); + @Override + public boolean onPreDraw() { + return SystemClock.uptimeMillis() - mCreateTime > 500; + } + } + ); + } + + private void onSplashScreenExit(SplashScreenView view) { + view.remove(); + } +} diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java index e2d17cdbe9e6..1f4c6c53b260 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java @@ -17,6 +17,7 @@ package com.android.test.hwui; import android.app.Activity; +import android.content.pm.ActivityInfo; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.ColorSpace; @@ -72,6 +73,10 @@ public class ColorBitmapActivity extends Activity implements SurfaceHolder.Callb private int[] mGradientEndColors = {0xFFFFFFFF, 0xFFFF0000, 0xFF00FF00, 0xFF0000FF}; private String[] mGradientColorNames = {"Grayscale", "Red", "Green", "Blue"}; + private int mColorMode = ActivityInfo.COLOR_MODE_DEFAULT; + private int[] mColorModes = {ActivityInfo.COLOR_MODE_DEFAULT, ActivityInfo.COLOR_MODE_HDR}; + private String[] mColorModeNames = {"DEFAULT", "HDR"}; + private final ExecutorService mBufferFenceExecutor = Executors.newFixedThreadPool(1); private final ExecutorService mBufferExecutor = Executors.newFixedThreadPool(1); @@ -139,6 +144,15 @@ public class ColorBitmapActivity extends Activity implements SurfaceHolder.Callb gradientColorSpinner .setOnItemSelectedListener(new GradientColorOnItemSelectedListener()); + ArrayAdapter<String> colorModeAdapter = new ArrayAdapter<>( + this, android.R.layout.simple_spinner_item, mColorModeNames); + + colorModeAdapter + .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + Spinner colorModeSpinner = new Spinner(this); + colorModeSpinner.setAdapter(colorModeAdapter); + colorModeSpinner.setOnItemSelectedListener(new ColorModeOnItemSelectedListener()); + mGradientBuffer = getGradientBuffer().get(); LinearLayout linearLayout = new LinearLayout(this); @@ -169,6 +183,10 @@ public class ColorBitmapActivity extends Activity implements SurfaceHolder.Callb LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)); + spinnerLayout.addView(colorModeSpinner, new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + linearLayout.addView(spinnerLayout, new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)); @@ -187,6 +205,8 @@ public class ColorBitmapActivity extends Activity implements SurfaceHolder.Callb linearLayout.addView(mSurfaceView, new LinearLayout.LayoutParams(WIDTH, HEIGHT)); setContentView(linearLayout); + + getWindow().setColorMode(mColorMode); } catch (Exception e) { throw new RuntimeException(e); } @@ -312,4 +332,22 @@ public class ColorBitmapActivity extends Activity implements SurfaceHolder.Callb } } + + private final class ColorModeOnItemSelectedListener + implements AdapterView.OnItemSelectedListener { + + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + ColorBitmapActivity.this.mColorMode = ColorBitmapActivity.this.mColorModes[position]; + ColorBitmapActivity.this.getMainExecutor() + .execute(() -> { + ColorBitmapActivity.this.getWindow().setColorMode(mColorMode); + }); + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + + } + } } diff --git a/tests/testables/src/android/testing/TestableResources.java b/tests/testables/src/android/testing/TestableResources.java index 27d5b66b355e..0ec106e329f6 100644 --- a/tests/testables/src/android/testing/TestableResources.java +++ b/tests/testables/src/android/testing/TestableResources.java @@ -15,9 +15,11 @@ package android.testing; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import static org.mockito.Mockito.withSettings; import android.content.Context; +import android.content.res.Configuration; import android.content.res.Resources; import android.util.Log; import android.util.SparseArray; @@ -54,6 +56,16 @@ public class TestableResources { } /** + * Sets a configuration for {@link #getResources()} to return to allow custom configs to + * be set and tested. + * + * @param configuration the configuration to return from resources. + */ + public void overrideConfiguration(Configuration configuration) { + when(mResources.getConfiguration()).thenReturn(configuration); + } + + /** * Sets the return value for the specified resource id. * <p> * Since resource ids are unique there is a single addOverride that will override the value |