Merge "Add verbose log for device config updates" into main
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java
index e805ab9..abb0fa7 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java
@@ -42,22 +42,6 @@
}
@Test
- public void testCreateRenderNodeNoName() {
- final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- while (state.keepRunning()) {
- RenderNode.create(null, null);
- }
- }
-
- @Test
- public void testCreateRenderNode() {
- final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- while (state.keepRunning()) {
- RenderNode.create("LinearLayout", null);
- }
- }
-
- @Test
public void testIsValid() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
RenderNode node = RenderNode.create("LinearLayout", null);
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index 3b5f11b..29afb27 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -40,10 +40,26 @@
* <p>This service executes each incoming job on a {@link android.os.Handler} running on your
* application's main thread. This means that you <b>must</b> offload your execution logic to
* another thread/handler/{@link android.os.AsyncTask} of your choosing. Not doing so will result
- * in blocking any future callbacks from the JobManager - specifically
+ * in blocking any future callbacks from the JobScheduler - specifically
* {@link #onStopJob(android.app.job.JobParameters)}, which is meant to inform you that the
* scheduling requirements are no longer being met.</p>
*
+ * <p class="note">
+ * Since the introduction of JobScheduler, if an app did not return from
+ * {@link #onStartJob(JobParameters)} within several seconds, JobScheduler would consider the app
+ * unresponsive and clean up job execution. In such cases, the app was no longer considered
+ * to be running a job and therefore did not have any of the job lifecycle guarantees outlined
+ * in {@link JobScheduler}. However, prior to Android version
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the failure and cleanup were silent
+ * and apps had no indication that they no longer had job lifecycle guarantees.
+ * Starting with Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * JobScheduler will explicitly trigger an ANR in such cases so that apps and developers
+ * can be aware of the issue.
+ * Similar behavior applies to the return time from {@link #onStopJob(JobParameters)} as well.
+ * <br /> <br />
+ * If you see ANRs, then the app may be doing too much work on the UI thread. Ensure that
+ * potentially long operations are moved to a worker thread.
+ *
* <p>As a subclass of {@link Service}, there will only be one active instance of any JobService
* subclasses, regardless of job ID. This means that if you schedule multiple jobs with different
* job IDs but using the same JobService class, that JobService may receive multiple calls to
@@ -240,7 +256,7 @@
* @param params The parameters identifying this job, similar to what was supplied to the job in
* the {@link #onStartJob(JobParameters)} callback, but with the stop reason
* included.
- * @return {@code true} to indicate to the JobManager whether you'd like to reschedule
+ * @return {@code true} to indicate to the JobScheduler whether you'd like to reschedule
* this job based on the retry criteria provided at job creation-time; or {@code false}
* to end the job entirely (or, for a periodic job, to reschedule it according to its
* requested periodic criteria). Regardless of the value returned, your job must stop executing.
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 76d1935..1be07fd 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.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.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
@@ -373,10 +375,9 @@
@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 @@
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 @@
}
}
- /**
- * 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 @@
*/
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 @@
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 @@
/**
* 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
/** 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 @@
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 @@
+ "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 @@
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 @@
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 @@
Binder.restoreCallingIdentity(token);
}
}
- } else if ("force-offbody".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 = true;
- 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 if ("force-onbody".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);
- }
+ } else {
+ pw.println("Provide true or false argument after force-modemanager-quickdoze");
+ return -1;
}
} else {
return shell.handleDefaultCommands(cmd);
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index b94b3b4..d76ca5b 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -265,7 +265,8 @@
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 3ad6d58..8ebd454 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 @@
@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 a29fa8f..1e7d4c2 100644
--- a/cmds/idmap2/include/idmap2/FabricatedOverlay.h
+++ b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
@@ -49,6 +49,8 @@
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 @@
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 @@
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 @@
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 c2b0abe..d4490ef4 100644
--- a/cmds/idmap2/include/idmap2/ResourceUtils.h
+++ b/cmds/idmap2/include/idmap2/ResourceUtils.h
@@ -43,6 +43,8 @@
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 dd5be21c..47daf23 100644
--- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -55,7 +55,7 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
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 a8aa033..c7f5cf3 100644
--- a/cmds/idmap2/self_targeting/SelfTargeting.cpp
+++ b/cmds/idmap2/self_targeting/SelfTargeting.cpp
@@ -52,6 +52,7 @@
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 e13a0eb..b460bb3 100644
--- a/cmds/idmap2/tests/FabricatedOverlayTests.cpp
+++ b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
@@ -59,7 +59,7 @@
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 f6e48ba..a3448fd 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -269,7 +269,7 @@
.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 380e462..40f98c2 100644
--- a/cmds/idmap2/tests/ResourceMappingTests.cpp
+++ b/cmds/idmap2/tests/ResourceMappingTests.cpp
@@ -212,7 +212,7 @@
.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 0b10d64..83f43bb 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9410,8 +9410,8 @@
method @NonNull public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForProfile(@Nullable android.os.UserHandle);
method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
method public boolean isRequestPinAppWidgetSupported();
- method public void notifyAppWidgetViewDataChanged(int[], int);
- method public void notifyAppWidgetViewDataChanged(int, int);
+ method @Deprecated public void notifyAppWidgetViewDataChanged(int[], int);
+ method @Deprecated public void notifyAppWidgetViewDataChanged(int, int);
method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews);
method public void partiallyUpdateAppWidget(int, android.widget.RemoteViews);
method public boolean requestPinAppWidget(@NonNull android.content.ComponentName, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent);
@@ -9530,6 +9530,7 @@
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 @@
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 @@
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 @@
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);
}
@@ -14347,11 +14351,13 @@
method public void execSQL(String, Object[]) throws android.database.SQLException;
method public static String findEditTable(String);
method public java.util.List<android.util.Pair<java.lang.String,java.lang.String>> getAttachedDbs();
+ method public long getLastChangedRowCount();
method public long getLastInsertRowId();
method public long getMaximumSize();
method public long getPageSize();
method public String getPath();
method @Deprecated public java.util.Map<java.lang.String,java.lang.String> getSyncedTables();
+ method public long getTotalChangedRowCount();
method public int getVersion();
method public boolean inTransaction();
method public long insert(String, String, android.content.ContentValues);
@@ -44999,6 +45005,7 @@
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/module-lib-current.txt b/core/api/module-lib-current.txt
index cda9ae3..b1feb41 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -15,6 +15,7 @@
@UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
method public final boolean addDumpable(@NonNull android.util.Dumpable);
+ method public final boolean isResumed();
}
public class ActivityManager {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 3a4c3f2..3429c7c 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1877,8 +1877,6 @@
public class AudioManager {
method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int abandonAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String);
- method @RequiresPermission("Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED") public boolean enterAudioFocusFreezeForTest(@NonNull java.util.List<java.lang.Integer>);
- method @RequiresPermission("Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED") public boolean exitAudioFocusFreezeForTest();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public void forceComputeCsdOnAllDevices(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public void forceUseFrameworkMel(boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioRecord getCallDownlinkExtractionAudioRecord(@NonNull android.media.AudioFormat);
@@ -1886,9 +1884,6 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public float getCsd();
method @Nullable public static android.media.AudioDeviceInfo getDeviceInfoFromType(int);
method @IntRange(from=0) @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public long getFadeOutDurationOnFocusLossMillis(@NonNull android.media.AudioAttributes);
- method @NonNull @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public java.util.List<java.lang.Integer> getFocusDuckedUidsForTest();
- method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public long getFocusFadeOutDurationForTest();
- method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public long getFocusUnmuteDelayAfterFadeOutForTest();
method @Nullable public static android.media.AudioHalVersionInfo getHalVersion();
method public static final int[] getPublicStreamTypes();
method @NonNull public java.util.List<java.lang.Integer> getReportedSurroundFormats();
@@ -3290,6 +3285,9 @@
field @Deprecated protected int mCapabilities;
}
+ public static class MmTelFeature.MmTelCapabilities extends android.telephony.ims.feature.ImsFeature.Capabilities {
+ }
+
}
package android.text {
@@ -3869,15 +3867,21 @@
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
}
+ public final class InsertModeGesture extends android.view.inputmethod.CancellableHandwritingGesture implements android.os.Parcelable {
+ }
+
}
package android.view.inspector {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index a5acf03..a366464 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2049,7 +2049,7 @@
* indicator that the activity became active and ready to receive input. This sometimes could
* also be a transit state toward another resting state. For instance, an activity may be
* relaunched to {@link #onPause} due to configuration changes and the activity was visible,
- * but wasn’t the top-most activity of an activity task. {@link #onResume} is guaranteed to be
+ * but wasn't the top-most activity of an activity task. {@link #onResume} is guaranteed to be
* called before {@link #onPause} in this case which honors the activity lifecycle policy and
* the activity eventually rests in {@link #onPause}.
*
@@ -9063,6 +9063,7 @@
* @hide
*/
@UnsupportedAppUsage
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public final boolean isResumed() {
return mResumed;
}
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index 0857c96..729e555 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -40,7 +40,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Collections;
-import java.util.LinkedHashSet;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
@@ -195,8 +195,7 @@
XmlUtils.beginDocument(parser, TAG_LOCALE_CONFIG);
int outerDepth = parser.getDepth();
AttributeSet attrs = Xml.asAttributeSet(parser);
- // LinkedHashSet to preserve insertion order
- Set<String> localeNames = new LinkedHashSet<>();
+ Set<String> localeNames = new HashSet<String>();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
if (TAG_LOCALE.equals(parser.getName())) {
final TypedArray attributes = res.obtainAttributes(
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index d8cedb8..7ee1332 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -26,12 +26,14 @@
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.media.AudioAttributes;
+import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.text.TextUtils;
+import android.util.Log;
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.Preconditions;
@@ -54,6 +56,7 @@
* A representation of settings that apply to a collection of similarly themed notifications.
*/
public final class NotificationChannel implements Parcelable {
+ private static final String TAG = "NotificationChannel";
/**
* The id of the default channel for an app. This id is reserved by the system. All
@@ -959,8 +962,11 @@
setLockscreenVisibility(safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY));
Uri sound = safeUri(parser, ATT_SOUND);
- setSound(forRestore ? restoreSoundUri(context, sound, pkgInstalled) : sound,
- safeAudioAttributes(parser));
+
+ final AudioAttributes audioAttributes = safeAudioAttributes(parser);
+ final int usage = audioAttributes.getUsage();
+ setSound(forRestore ? restoreSoundUri(context, sound, pkgInstalled, usage) : sound,
+ audioAttributes);
enableLights(safeBool(parser, ATT_LIGHTS, false));
setLightColor(safeInt(parser, ATT_LIGHT_COLOR, DEFAULT_LIGHT_COLOR));
@@ -1010,18 +1016,34 @@
if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
return uri;
}
-
return contentResolver.canonicalize(uri);
}
@Nullable
- private Uri getUncanonicalizedSoundUri(ContentResolver contentResolver, @NonNull Uri uri) {
+ private Uri getUncanonicalizedSoundUri(
+ ContentResolver contentResolver, @NonNull Uri uri, int usage) {
if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(uri)
|| ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())
|| ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
return uri;
}
- return contentResolver.uncanonicalize(uri);
+ int ringtoneType = 0;
+
+ // Consistent with UI(SoundPreferenceController.handlePreferenceTreeClick).
+ if (AudioAttributes.USAGE_ALARM == usage) {
+ ringtoneType = RingtoneManager.TYPE_ALARM;
+ } else if (AudioAttributes.USAGE_NOTIFICATION_RINGTONE == usage) {
+ ringtoneType = RingtoneManager.TYPE_RINGTONE;
+ } else {
+ ringtoneType = RingtoneManager.TYPE_NOTIFICATION;
+ }
+ try {
+ return RingtoneManager.getRingtoneUriForRestore(
+ contentResolver, uri.toString(), ringtoneType);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to uncanonicalized sound uri for " + uri + " " + e);
+ return Settings.System.DEFAULT_NOTIFICATION_URI;
+ }
}
/**
@@ -1033,7 +1055,8 @@
* @hide
*/
@Nullable
- public Uri restoreSoundUri(Context context, @Nullable Uri uri, boolean pkgInstalled) {
+ public Uri restoreSoundUri(
+ Context context, @Nullable Uri uri, boolean pkgInstalled, int usage) {
if (uri == null || Uri.EMPTY.equals(uri)) {
return null;
}
@@ -1060,7 +1083,7 @@
}
}
mSoundRestored = true;
- return getUncanonicalizedSoundUri(contentResolver, canonicalizedUri);
+ return getUncanonicalizedSoundUri(contentResolver, canonicalizedUri, usage);
}
/**
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index 1ebf565..a87187b 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -55,6 +55,11 @@
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
final class SharedPreferencesImpl implements SharedPreferences {
private static final String TAG = "SharedPreferencesImpl";
@@ -119,6 +124,10 @@
private final ExponentiallyBucketedHistogram mSyncTimes = new ExponentiallyBucketedHistogram(16);
private int mNumSync = 0;
+ private static final ThreadPoolExecutor sLoadExecutor = new ThreadPoolExecutor(0, 1, 10L,
+ TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
+ new SharedPreferencesThreadFactory());
+
@UnsupportedAppUsage
SharedPreferencesImpl(File file, int mode) {
mFile = file;
@@ -135,11 +144,10 @@
synchronized (mLock) {
mLoaded = false;
}
- new Thread("SharedPreferencesImpl-load") {
- public void run() {
- loadFromDisk();
- }
- }.start();
+
+ sLoadExecutor.execute(() -> {
+ loadFromDisk();
+ });
}
private void loadFromDisk() {
@@ -874,4 +882,14 @@
}
mcr.setDiskWriteResult(false, false);
}
+
+
+ private static final class SharedPreferencesThreadFactory implements ThreadFactory {
+ @Override
+ public Thread newThread(Runnable runnable) {
+ Thread thread = Executors.defaultThreadFactory().newThread(runnable);
+ thread.setName("SharedPreferences");
+ return thread;
+ }
+ }
}
diff --git a/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS b/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS
index 270cb18..62ad8c0 100644
--- a/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS
+++ b/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS
@@ -1,4 +1,3 @@
+file:EnterprisePlatform_OWNERS
rubinxu@google.com
pgrafov@google.com
-ayushsha@google.com
-alexkershaw@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/core/java/android/app/admin/EnterprisePlatformTest_OWNERS b/core/java/android/app/admin/EnterprisePlatformTest_OWNERS
new file mode 100644
index 0000000..eb23a03
--- /dev/null
+++ b/core/java/android/app/admin/EnterprisePlatformTest_OWNERS
@@ -0,0 +1,5 @@
+# Bug template url: https://b.corp.google.com/issues/new?component=1337891&template=1814288
+# Assign bugs to aep-automated-tests@google.com
+
+file:EnterprisePlatform_OWNERS
+scottjonathan@google.com
\ No newline at end of file
diff --git a/core/java/android/app/admin/EnterprisePlatform_OWNERS b/core/java/android/app/admin/EnterprisePlatform_OWNERS
index 6ce25cc..4d1ed590 100644
--- a/core/java/android/app/admin/EnterprisePlatform_OWNERS
+++ b/core/java/android/app/admin/EnterprisePlatform_OWNERS
@@ -1,5 +1,2 @@
-# Assign bugs to android-enterprise-triage@google.com
-file:WorkDeviceExperience_OWNERS
-file:Provisioning_OWNERS
-file:WorkProfile_OWNERS
-file:EnterprisePlatformSecurity_OWNERS
\ No newline at end of file
+sandness@google.com #{LAST_RESORT_SUGGESTION}
+scottjonathan@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/core/java/android/app/admin/OWNERS b/core/java/android/app/admin/OWNERS
index 10a5f14..308f1d6 100644
--- a/core/java/android/app/admin/OWNERS
+++ b/core/java/android/app/admin/OWNERS
@@ -1,5 +1,7 @@
# Bug component: 142675
+# Assign bugs to device-policy-manager-triage@google.com
-file:EnterprisePlatform_OWNERS
+file:WorkDeviceExperience_OWNERS
+file:EnterprisePlatformSecurity_OWNERS
yamasani@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/core/java/android/app/admin/Provisioning_OWNERS b/core/java/android/app/admin/Provisioning_OWNERS
index 2e5c2df..fa0a1f0 100644
--- a/core/java/android/app/admin/Provisioning_OWNERS
+++ b/core/java/android/app/admin/Provisioning_OWNERS
@@ -1,5 +1,4 @@
# Assign bugs to android-enterprise-triage@google.com
mdb.ae-provisioning-reviews@google.com
-petuska@google.com #{LAST_RESORT_SUGGESTION}
-nupursn@google.com #{LAST_RESORT_SUGGESTION}
-shreyacsingh@google.com #{LAST_RESORT_SUGGESTION}
+file:EnterprisePlatform_OWNERS
+petuska@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/core/java/android/app/admin/WorkDeviceExperience_OWNERS b/core/java/android/app/admin/WorkDeviceExperience_OWNERS
index 2033343..10ac59a 100644
--- a/core/java/android/app/admin/WorkDeviceExperience_OWNERS
+++ b/core/java/android/app/admin/WorkDeviceExperience_OWNERS
@@ -1,7 +1,5 @@
# Assign bugs to android-enterprise-triage@google.com
work-device-experience+reviews@google.com
-scottjonathan@google.com #{LAST_RESORT_SUGGESTION}
-eliselliott@google.com #{LAST_RESORT_SUGGESTION}
+file:EnterprisePlatform_OWNERS
kholoudm@google.com #{LAST_RESORT_SUGGESTION}
acjohnston@google.com #{LAST_RESORT_SUGGESTION}
-alexkershaw@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/core/java/android/app/admin/WorkProfile_OWNERS b/core/java/android/app/admin/WorkProfile_OWNERS
index 260b672..e9bcce2 100644
--- a/core/java/android/app/admin/WorkProfile_OWNERS
+++ b/core/java/android/app/admin/WorkProfile_OWNERS
@@ -1,5 +1,3 @@
# Assign bugs to android-enterprise-triage@google.com
-liahav@google.com
-olit@google.com
-scottjonathan@google.com #{LAST_RESORT_SUGGESTION}
-alexkershaw@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
+file:EnterprisePlatform_OWNERS
+liahav@google.com
\ No newline at end of file
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index b159321..3927b40 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -781,7 +781,18 @@
*
* @param appWidgetIds The AppWidget instances to notify of view data changes.
* @param viewId The collection view id.
+ * @deprecated The corresponding API
+ * {@link RemoteViews#setRemoteAdapter(int, Intent)} associated with this method has been
+ * deprecated. Moving forward please use
+ * {@link RemoteViews#setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)}
+ * instead to set {@link android.widget.RemoteViews.RemoteCollectionItems} for the remote
+ * adapter and update the widget views by calling {@link #updateAppWidget(int[], RemoteViews)},
+ * {@link #updateAppWidget(int, RemoteViews)},
+ * {@link #updateAppWidget(ComponentName, RemoteViews)},
+ * {@link #partiallyUpdateAppWidget(int[], RemoteViews)},
+ * or {@link #partiallyUpdateAppWidget(int, RemoteViews)}, whichever applicable.
*/
+ @Deprecated
public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) {
if (mService == null) {
return;
@@ -817,7 +828,18 @@
*
* @param appWidgetId The AppWidget instance to notify of view data changes.
* @param viewId The collection view id.
+ * @deprecated The corresponding API
+ * {@link RemoteViews#setRemoteAdapter(int, Intent)} associated with this method has been
+ * deprecated. Moving forward please use
+ * {@link RemoteViews#setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)}
+ * instead to set {@link android.widget.RemoteViews.RemoteCollectionItems} for the remote
+ * adapter and update the widget views by calling {@link #updateAppWidget(int[], RemoteViews)},
+ * {@link #updateAppWidget(int, RemoteViews)},
+ * {@link #updateAppWidget(ComponentName, RemoteViews)},
+ * {@link #partiallyUpdateAppWidget(int[], RemoteViews)},
+ * or {@link #partiallyUpdateAppWidget(int, RemoteViews)}, whichever applicable.
*/
+ @Deprecated
public void notifyAppWidgetViewDataChanged(int appWidgetId, int viewId) {
if (mService == null) {
return;
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 0958a806..7d62c79 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -57,6 +57,7 @@
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 @@
* @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 @@
mDeviceMacAddress = macAddress;
mDisplayName = displayName;
+ mTag = tag;
mDeviceProfile = deviceProfile;
mAssociatedDevice = associatedDevice;
@@ -116,6 +119,14 @@
}
/**
+ * @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 @@
+ "mId=" + mId
+ ", mUserId=" + mUserId
+ ", mPackageName='" + mPackageName + '\''
+ + ", mTag='" + mTag + '\''
+ ", mDeviceMacAddress=" + mDeviceMacAddress
+ ", mDisplayName='" + mDisplayName + '\''
+ ", mDeviceProfile='" + mDeviceProfile + '\''
@@ -315,6 +327,7 @@
&& 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 @@
@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 @@
dest.writeInt(mUserId);
dest.writeString(mPackageName);
+ dest.writeString(mTag);
dest.writeTypedObject(mDeviceMacAddress, 0);
dest.writeCharSequence(mDisplayName);
@@ -359,6 +373,7 @@
mUserId = in.readInt();
mPackageName = in.readString();
+ mTag = in.readString();
mDeviceMacAddress = in.readTypedObject(MacAddress.CREATOR);
mDisplayName = in.readCharSequence();
@@ -413,9 +428,11 @@
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 @@
}
/** @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 @@
/** @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 69e1653..3aa2877 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -1372,6 +1372,50 @@
}
}
+ /**
+ * 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 463dba2..a3b202a 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -115,4 +115,12 @@
@EnforcePermission("MANAGE_COMPANION_DEVICES")
void enableSecureTransport(boolean enabled);
+
+ void setAssociationTag(int associationId, String tag);
+
+ void clearAssociationTag(int associationId);
+
+ byte[] getBackupPayload(int userId);
+
+ void applyRestoredPayload(in byte[] payload, int userId);
}
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index 7e787c9..c4547b8 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -20,6 +20,7 @@
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 @@
* @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 @@
}
/**
+ * 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 @@
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 @@
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/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 508eeed..048289f 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1456,8 +1456,8 @@
private static AssetManager newConfiguredAssetManager() {
AssetManager assetManager = new AssetManager();
- assetManager.setConfiguration(0, 0, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT);
+ assetManager.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ Build.VERSION.RESOURCES_SDK_INT);
return assetManager;
}
@@ -9011,8 +9011,8 @@
}
AssetManager assets = new AssetManager();
- assets.setConfiguration(0, 0, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT);
+ assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ Build.VERSION.RESOURCES_SDK_INT);
assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
mCachedAssetManager = assets;
@@ -9086,8 +9086,8 @@
private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) {
final AssetManager assets = new AssetManager();
- assets.setConfiguration(0, 0, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT);
+ assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ Build.VERSION.RESOURCES_SDK_INT);
assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
return assets;
}
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index 7e30157..ea21d51 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -125,6 +125,9 @@
},
{
"exclude-annotation":"org.junit.Ignore"
+ },
+ {
+ "exclude-filter": "android.content.pm.cts.PackageManagerShellCommandMultiUserTest"
}
]
},
@@ -162,6 +165,14 @@
},
{
"name":"CtsInstallHostTestCases"
+ },
+ {
+ "name": "CtsPackageManagerTestCases",
+ "options": [
+ {
+ "include-filter": "android.content.pm.cts.PackageManagerShellCommandMultiUserTest"
+ }
+ ]
}
]
}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 23b9d0b..b225de4 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -1480,13 +1480,9 @@
int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp,
int screenHeightDp, int screenLayout, int uiMode, int colorMode, int grammaticalGender,
int majorVersion) {
- if (locale != null) {
- setConfiguration(mcc, mnc, null, new String[]{locale}, orientation, touchscreen,
- density, keyboard, keyboardHidden, navigation, screenWidth, screenHeight,
- smallestScreenWidthDp, screenWidthDp, screenHeightDp, screenLayout, uiMode,
- colorMode, grammaticalGender, majorVersion);
- } else {
- setConfiguration(mcc, mnc, null, null, orientation, touchscreen, density,
+ synchronized (this) {
+ ensureValidLocked();
+ nativeSetConfiguration(mObject, mcc, mnc, locale, orientation, touchscreen, density,
keyboard, keyboardHidden, navigation, screenWidth, screenHeight,
smallestScreenWidthDp, screenWidthDp, screenHeightDp, screenLayout, uiMode,
colorMode, grammaticalGender, majorVersion);
@@ -1494,25 +1490,6 @@
}
/**
- * Change the configuration used when retrieving resources. Not for use by
- * applications.
- * @hide
- */
- public void setConfiguration(int mcc, int mnc, String defaultLocale, String[] locales,
- int orientation, int touchscreen, int density, int keyboard, int keyboardHidden,
- int navigation, int screenWidth, int screenHeight, int smallestScreenWidthDp,
- int screenWidthDp, int screenHeightDp, int screenLayout, int uiMode, int colorMode,
- int grammaticalGender, int majorVersion) {
- synchronized (this) {
- ensureValidLocked();
- nativeSetConfiguration(mObject, mcc, mnc, defaultLocale, locales, orientation,
- touchscreen, density, keyboard, keyboardHidden, navigation, screenWidth,
- screenHeight, smallestScreenWidthDp, screenWidthDp, screenHeightDp,
- screenLayout, uiMode, colorMode, grammaticalGender, majorVersion);
- }
- }
-
- /**
* @hide
*/
@UnsupportedAppUsage
@@ -1595,11 +1572,10 @@
private static native void nativeSetApkAssets(long ptr, @NonNull ApkAssets[] apkAssets,
boolean invalidateCaches);
private static native void nativeSetConfiguration(long ptr, int mcc, int mnc,
- @Nullable String defaultLocale, @NonNull String[] locales, int orientation,
- int touchscreen, int density, int keyboard, int keyboardHidden, int navigation,
- int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp,
- int screenHeightDp, int screenLayout, int uiMode, int colorMode, int grammaticalGender,
- int majorVersion);
+ @Nullable String locale, int orientation, int touchscreen, int density, int keyboard,
+ int keyboardHidden, int navigation, int screenWidth, int screenHeight,
+ int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, int screenLayout,
+ int uiMode, int colorMode, int grammaticalGender, int majorVersion);
private static native @NonNull SparseArray<String> nativeGetAssignedPackageIdentifiers(
long ptr, boolean includeOverlays, boolean includeLoaders);
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index a937832..395fef2 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -425,12 +425,14 @@
mConfiguration.setLocales(locales);
}
- String[] selectedLocales = null;
- String defaultLocale = null;
if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
if (locales.size() > 1) {
String[] availableLocales;
- if (ResourcesManager.getInstance().getLocaleList().isEmpty()) {
+
+ LocaleList localeList = ResourcesManager.getInstance().getLocaleList();
+ if (!localeList.isEmpty()) {
+ availableLocales = localeList.toLanguageTags().split(",");
+ } else {
// The LocaleList has changed. We must query the AssetManager's
// available Locales and figure out the best matching Locale in the new
// LocaleList.
@@ -442,30 +444,14 @@
availableLocales = null;
}
}
- if (availableLocales != null) {
- final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
- availableLocales);
- if (bestLocale != null) {
- selectedLocales = new String[]{
- adjustLanguageTag(bestLocale.toLanguageTag())};
- if (!bestLocale.equals(locales.get(0))) {
- mConfiguration.setLocales(
- new LocaleList(bestLocale, locales));
- }
- }
- }
- } else {
- selectedLocales = locales.getIntersection(
- ResourcesManager.getInstance().getLocaleList());
- defaultLocale = ResourcesManager.getInstance()
- .getLocaleList().get(0).toLanguageTag();
}
- }
- }
- if (selectedLocales == null) {
- selectedLocales = new String[locales.size()];
- for (int i = 0; i < locales.size(); i++) {
- selectedLocales[i] = adjustLanguageTag(locales.get(i).toLanguageTag());
+ if (availableLocales != null) {
+ final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
+ availableLocales);
+ if (bestLocale != null && bestLocale != locales.get(0)) {
+ mConfiguration.setLocales(new LocaleList(bestLocale, locales));
+ }
+ }
}
}
@@ -502,8 +488,7 @@
}
mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
- defaultLocale,
- selectedLocales,
+ adjustLanguageTag(mConfiguration.getLocales().get(0).toLanguageTag()),
mConfiguration.orientation,
mConfiguration.touchscreen,
mConfiguration.densityDpi, mConfiguration.keyboard,
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 706e75e..f2980f4 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -1875,7 +1875,7 @@
* statement
* @hide
*/
- long getLastChangedRowsCount() {
+ long getLastChangedRowCount() {
try {
return nativeChanges(mConnectionPtr);
} finally {
@@ -1887,7 +1887,7 @@
* Return the total number of database changes made on the current connection.
* @hide
*/
- long getTotalChangedRowsCount() {
+ long getTotalChangedRowCount() {
try {
return nativeTotalChanges(mConnectionPtr);
} finally {
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index a3f8383..aec01f8 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -2208,10 +2208,9 @@
*
* @return The number of rows changed by the most recent sql statement
* @throws IllegalStateException if there is no current transaction.
- * @hide
*/
- public long getLastChangedRowsCount() {
- return getThreadSession().getLastChangedRowsCount();
+ public long getLastChangedRowCount() {
+ return getThreadSession().getLastChangedRowCount();
}
/**
@@ -2223,9 +2222,9 @@
* <code><pre>
* database.beginTransaction();
* try {
- * long initialValue = database.getTotalChangedRowsCount();
+ * long initialValue = database.getTotalChangedRowCount();
* // Execute SQL statements
- * long changedRows = database.getTotalChangedRowsCount() - initialValue;
+ * long changedRows = database.getTotalChangedRowCount() - initialValue;
* // changedRows counts the total number of rows updated in the transaction.
* } finally {
* database.endTransaction();
@@ -2236,10 +2235,9 @@
*
* @return The number of rows changed on the current connection.
* @throws IllegalStateException if there is no current transaction.
- * @hide
*/
- public long getTotalChangedRowsCount() {
- return getThreadSession().getTotalChangedRowsCount();
+ public long getTotalChangedRowCount() {
+ return getThreadSession().getTotalChangedRowCount();
}
/**
diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java
index ef1a9cb..7d9f02d 100644
--- a/core/java/android/database/sqlite/SQLiteSession.java
+++ b/core/java/android/database/sqlite/SQLiteSession.java
@@ -998,9 +998,9 @@
* this connection.
* @hide
*/
- long getLastChangedRowsCount() {
+ long getLastChangedRowCount() {
throwIfNoTransaction();
- return mConnection.getLastChangedRowsCount();
+ return mConnection.getLastChangedRowCount();
}
/**
@@ -1008,9 +1008,9 @@
* it was created.
* @hide
*/
- long getTotalChangedRowsCount() {
+ long getTotalChangedRowCount() {
throwIfNoTransaction();
- return mConnection.getTotalChangedRowsCount();
+ return mConnection.getTotalChangedRowCount();
}
/**
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index 889a43c..5ff0e7a 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.GraphicBuffer;
+import android.os.BadParcelableException;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -399,11 +400,14 @@
public static final @android.annotation.NonNull Parcelable.Creator<HardwareBuffer> CREATOR =
new Parcelable.Creator<HardwareBuffer>() {
public HardwareBuffer createFromParcel(Parcel in) {
+ if (in == null) {
+ throw new NullPointerException("null passed to createFromParcel");
+ }
long nativeObject = nReadHardwareBufferFromParcel(in);
if (nativeObject != 0) {
return new HardwareBuffer(nativeObject);
}
- return null;
+ throw new BadParcelableException("Failed to read hardware buffer");
}
public HardwareBuffer[] newArray(int size) {
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index c2fe080..c80124c 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -31,6 +31,7 @@
import android.content.pm.PackageManager;
import android.graphics.Point;
import android.hardware.CameraExtensionSessionStats;
+import android.hardware.CameraIdRemapping;
import android.hardware.CameraStatus;
import android.hardware.ICameraService;
import android.hardware.ICameraServiceListener;
@@ -1730,6 +1731,17 @@
}
/**
+ * Remaps Camera Ids in the CameraService.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA)
+ public void remapCameraIds(@NonNull CameraIdRemapping cameraIdRemapping)
+ throws CameraAccessException, SecurityException, IllegalArgumentException {
+ CameraManagerGlobal.get().remapCameraIds(cameraIdRemapping);
+ }
+
+ /**
* Reports {@link CameraExtensionSessionStats} to the {@link ICameraService} to be logged for
* currently active session. Validation is done downstream.
*
@@ -1802,6 +1814,13 @@
private final Object mLock = new Object();
+ /**
+ * The active CameraIdRemapping. This will be used to refresh the cameraIdRemapping state
+ * in the CameraService every time we connect to it, including when the CameraService
+ * Binder dies and we reconnect to it.
+ */
+ @Nullable private CameraIdRemapping mActiveCameraIdRemapping;
+
// Access only through getCameraService to deal with binder death
private ICameraService mCameraService;
private boolean mHasOpenCloseListenerPermission = false;
@@ -1944,6 +1963,41 @@
} catch (RemoteException e) {
// Camera service died in all probability
}
+
+ if (mActiveCameraIdRemapping != null) {
+ try {
+ cameraService.remapCameraIds(mActiveCameraIdRemapping);
+ } catch (ServiceSpecificException e) {
+ // Unexpected failure, ignore and continue.
+ Log.e(TAG, "Unable to remap camera Ids in the camera service");
+ } catch (RemoteException e) {
+ // Camera service died in all probability
+ }
+ }
+ }
+
+ /** Updates the cameraIdRemapping state in the CameraService. */
+ public void remapCameraIds(@NonNull CameraIdRemapping cameraIdRemapping)
+ throws CameraAccessException, SecurityException {
+ synchronized (mLock) {
+ ICameraService cameraService = getCameraService();
+ if (cameraService == null) {
+ throw new CameraAccessException(
+ CameraAccessException.CAMERA_DISCONNECTED,
+ "Camera service is currently unavailable.");
+ }
+
+ try {
+ cameraService.remapCameraIds(cameraIdRemapping);
+ mActiveCameraIdRemapping = cameraIdRemapping;
+ } catch (ServiceSpecificException e) {
+ throwAsPublicException(e);
+ } catch (RemoteException e) {
+ throw new CameraAccessException(
+ CameraAccessException.CAMERA_DISCONNECTED,
+ "Camera service is currently unavailable.");
+ }
+ }
}
private String[] extractCameraIdListLocked() {
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index a62d74e..22e3938 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -39,7 +39,8 @@
* Holds configuration used to create {@link VirtualDisplay} instances.
*
* @see DisplayManager#createVirtualDisplay(VirtualDisplayConfig, Handler, VirtualDisplay.Callback)
- * @see MediaProjection#createVirtualDisplay
+ * @see MediaProjection#createVirtualDisplay(String, int, int, int, int, Surface,
+ * VirtualDisplay.Callback, Handler)
*/
public final class VirtualDisplayConfig implements Parcelable {
diff --git a/core/java/android/hardware/usb/UsbConfiguration.java b/core/java/android/hardware/usb/UsbConfiguration.java
index 66269cb..b25f47b 100644
--- a/core/java/android/hardware/usb/UsbConfiguration.java
+++ b/core/java/android/hardware/usb/UsbConfiguration.java
@@ -172,7 +172,8 @@
String name = in.readString();
int attributes = in.readInt();
int maxPower = in.readInt();
- Parcelable[] interfaces = in.readParcelableArray(UsbInterface.class.getClassLoader());
+ Parcelable[] interfaces = in.readParcelableArray(
+ UsbInterface.class.getClassLoader(), UsbInterface.class);
UsbConfiguration configuration = new UsbConfiguration(id, name, attributes, maxPower);
configuration.setInterfaces(interfaces);
return configuration;
diff --git a/core/java/android/os/LocaleList.java b/core/java/android/os/LocaleList.java
index 82cdd28..b74bb33 100644
--- a/core/java/android/os/LocaleList.java
+++ b/core/java/android/os/LocaleList.java
@@ -30,7 +30,6 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
-import java.util.List;
import java.util.Locale;
/**
@@ -152,25 +151,6 @@
}
/**
- * Find the intersection between this LocaleList and another
- * @return a String array of the Locales in both LocaleLists
- * {@hide}
- */
- @NonNull
- public String[] getIntersection(@NonNull LocaleList other) {
- List<String> intersection = new ArrayList<>();
- for (Locale l1 : mList) {
- for (Locale l2 : other.mList) {
- if (matchesLanguageAndScript(l2, l1)) {
- intersection.add(l1.toLanguageTag());
- break;
- }
- }
- }
- return intersection.toArray(new String[0]);
- }
-
- /**
* Creates a new {@link LocaleList}.
*
* If two or more same locales are passed, the repeated locales will be dropped.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index bf58eaa..a565b3b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11836,6 +11836,13 @@
"accessibility_display_magnification_edge_haptic_enabled";
/**
+ * If 1, DND default allowed packages have been updated
+ *
+ * @hide
+ */
+ public static final String DND_CONFIGS_MIGRATED = "dnd_settings_migrated";
+
+ /**
* These entries are considered common between the personal and the managed profile,
* since the managed profile doesn't get to change them.
*/
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 4b96d74..0b2b6ce 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -375,6 +375,14 @@
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/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index c501f5a..751cd21 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -203,6 +203,7 @@
candidateView.getHandwritingDelegatorCallback().run();
mState.mHasPreparedHandwritingDelegation = true;
} else {
+ mState.mPendingConnectedView = new WeakReference<>(candidateView);
requestFocusWithoutReveal(candidateView);
}
}
@@ -264,8 +265,9 @@
mShowHoverIconForConnectedView = false;
return;
}
- if (mState != null && mState.mShouldInitHandwriting) {
- tryStartHandwriting();
+ if (mState != null && mState.mPendingConnectedView != null
+ && mState.mPendingConnectedView.get() == view) {
+ startHandwriting(view);
}
}
}
@@ -290,40 +292,6 @@
}
}
- /**
- * Try to initiate handwriting. For this method to successfully send startHandwriting signal,
- * the following 3 conditions should meet:
- * a) The stylus movement exceeds the touchSlop.
- * b) A View has built InputConnection with IME.
- * c) The stylus event lands into the connected View's boundary.
- * This method will immediately fail without any side effect if condition a or b is not met.
- * However, if both condition a and b are met but the condition c is not met, it will reset the
- * internal states. And HandwritingInitiator won't attempt to call startHandwriting until the
- * next ACTION_DOWN.
- */
- private void tryStartHandwriting() {
- if (!mState.mExceedHandwritingSlop) {
- return;
- }
- final View connectedView = getConnectedView();
- if (connectedView == null) {
- return;
- }
-
- if (!connectedView.isAutoHandwritingEnabled()) {
- clearConnectedView();
- return;
- }
-
- final Rect handwritingArea = getViewHandwritingArea(connectedView);
- if (isInHandwritingArea(
- handwritingArea, mState.mStylusDownX, mState.mStylusDownY, connectedView)) {
- startHandwriting(connectedView);
- } else {
- mState.mShouldInitHandwriting = false;
- }
- }
-
/** Starts a stylus handwriting session for the view. */
@VisibleForTesting
public void startHandwriting(@NonNull View view) {
@@ -626,6 +594,7 @@
private boolean mHasInitiatedHandwriting;
private boolean mHasPreparedHandwritingDelegation;
+
/**
* Whether the current ongoing stylus MotionEvent sequence already exceeds the
* handwriting slop.
@@ -634,6 +603,12 @@
*/
private boolean mExceedHandwritingSlop;
+ /**
+ * A view which has requested focus and is pending input connection creation. When an input
+ * connection is created for the view, a handwriting session should be started for the view.
+ */
+ private WeakReference<View> mPendingConnectedView = null;
+
/** The pointer id of the stylus pointer that is being tracked. */
private final int mStylusPointerId;
/** The time stamp when the stylus pointer goes down. */
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index f81dc5a..c35b690 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -37,6 +37,7 @@
import android.os.Parcelable;
import android.os.Vibrator;
import android.os.VibratorManager;
+import android.text.TextUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -483,10 +484,12 @@
mSources = sources;
mKeyboardType = keyboardType;
mKeyCharacterMap = keyCharacterMap;
- if (keyboardLanguageTag != null) {
- mKeyboardLanguageTag = ULocale
+ if (!TextUtils.isEmpty(keyboardLanguageTag)) {
+ String langTag;
+ langTag = ULocale
.createCanonical(ULocale.forLanguageTag(keyboardLanguageTag))
.toLanguageTag();
+ mKeyboardLanguageTag = TextUtils.equals(langTag, "und") ? null : langTag;
} else {
mKeyboardLanguageTag = null;
}
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 2db2132..ad46f2b 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -815,7 +815,7 @@
int syncResult = syncAndDrawFrame(frameInfo);
if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
- Log.w("OpenGLRenderer", "Surface lost, forcing relayout");
+ Log.w("HWUI", "Surface lost, forcing relayout");
// We lost our surface. For a relayout next frame which should give us a new
// surface from WindowManager, which hopefully will work.
attachInfo.mViewRootImpl.mForceNextWindowRelayout = true;
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 3554483..5d1a81f 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -913,7 +913,6 @@
* </application>
* </pre>
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION =
"android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
@@ -983,7 +982,6 @@
* </application>
* </pre>
*/
- // TODO(b/263984287): Make this public API.
String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS =
"android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
@@ -1018,7 +1016,6 @@
* </application>
* </pre>
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
/**
@@ -1056,7 +1053,6 @@
* </application>
* </pre>
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION =
"android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
@@ -1102,7 +1098,6 @@
* </application>
* </pre>
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH =
"android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
@@ -1151,7 +1146,6 @@
* </application>
* </pre>
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE =
"android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
@@ -1189,7 +1183,6 @@
* </application>
* </pre>
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE =
"android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE";
@@ -1233,7 +1226,6 @@
* </application>
* </pre>
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE =
"android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE";
@@ -1300,6 +1292,102 @@
"android.window.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES";
/**
+ * Application level
+ * {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * tag that (when set to false) informs the system the app has opted out of the
+ * user-facing aspect ratio compatibility override.
+ *
+ * <p>The compatibility override enables device users to set the app's aspect
+ * ratio or force the app to fill the display regardless of the aspect
+ * ratio or orientation specified in the app manifest.
+ *
+ * <p>The aspect ratio compatibility override is exposed to users in device
+ * settings. A menu in device settings lists all apps that don't opt out of
+ * the compatibility override. Users select apps from the menu and set the
+ * app aspect ratio on a per-app basis. Typically, the menu is available
+ * only on large screen devices.
+ *
+ * <p>When users apply the aspect ratio override, the minimum aspect ratio
+ * specified in the app manifest is overridden. If users choose a
+ * full-screen aspect ratio, the orientation of the activity is forced to
+ * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER};
+ * see {@link #PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE} to
+ * disable the full-screen option only.
+ *
+ * <p>The user override is intended to improve the app experience on devices
+ * that have the ignore orientation request display setting enabled by OEMs
+ * (enables compatibility mode for fixed orientation on Android 12 (API
+ * level 31) or higher; see
+ * <a href="https://developer.android.com/guide/topics/large-screens/large-screen-app-compatibility">
+ * Large screen app compatibility</a>
+ * for more details).
+ *
+ * <p>To opt out of the user aspect ratio compatibility override, add this property
+ * to your app manifest and set the value to {@code false}. Your app will be excluded
+ * from the list of apps in device settings, and users will not be able to override
+ * the app's aspect ratio.
+ *
+ * <p>Not setting this property at all, or setting this property to {@code true} has no effect.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <application>
+ * <property
+ * android:name="android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE"
+ * android:value="false"/>
+ * </application>
+ * </pre>
+ * @hide
+ */
+ // TODO(b/294227289): Make this public API
+ String PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE =
+ "android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE";
+
+ /**
+ * Application level
+ * {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * tag that (when set to false) informs the system the app has opted out of the
+ * full-screen option of the aspect ratio compatibility override. (For
+ * background information about the aspect ratio compatibility override, see
+ * {@link #PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE}.)
+ *
+ * <p>When users apply the aspect ratio compatibility override, the orientation
+ * of the activity is forced to {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER}.
+ *
+ * <p>The user override is intended to improve the app experience on devices
+ * that have the ignore orientation request display setting enabled by OEMs
+ * (enables compatibility mode for fixed orientation on Android 12 (API
+ * level 31) or higher; see
+ * <a href="https://developer.android.com/guide/topics/large-screens/large-screen-app-compatibility">
+ * Large screen app compatibility</a>
+ * for more details).
+ *
+ * <p>To opt out of the full-screen option of the user aspect ratio compatibility
+ * override, add this property to your app manifest and set the value to {@code false}.
+ * Your app will have full-screen option removed from the list of user aspect ratio
+ * override options in device settings, and users will not be able to apply
+ * full-screen override to your app.
+ *
+ * <p><b>Note:</b> If {@link #PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE} is
+ * {@code false}, this property has no effect.
+ *
+ * <p>Not setting this property at all, or setting this property to {@code true} has no effect.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <application>
+ * <property
+ * android:name="android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE"
+ * android:value="false"/>
+ * </application>
+ * </pre>
+ * @hide
+ */
+ // TODO(b/294227289): Make this public API
+ String PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE =
+ "android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE";
+
+ /**
* @hide
*/
public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index f14ec37..60f46e6 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -831,8 +831,17 @@
// A11yService -> App -> SurfaceFlinger -> A11yService
ScreenCapture.ScreenCaptureListener listener =
new ScreenCapture.ScreenCaptureListener(
- screenshot -> sendWindowScreenshotSuccess(screenshot,
- interactionId));
+ (screenshot, status) -> {
+ if (status != 0) {
+ sendTakeScreenshotOfWindowError(
+ AccessibilityService
+ .ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR,
+ interactionId);
+ } else {
+ sendWindowScreenshotSuccess(screenshot,
+ interactionId);
+ }
+ });
connection.takeScreenshotOfWindow(accessibilityWindowId, interactionId,
listener, this);
mMainHandler.postDelayed(() -> {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 7f8f611..c877873 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1516,7 +1516,7 @@
* @see #startStylusHandwriting(View)
*/
public boolean isStylusHandwritingAvailable() {
- return isStylusHandwritingAvailableAsUser(UserHandle.myUserId());
+ return isStylusHandwritingAvailableAsUser(UserHandle.of(UserHandle.myUserId()));
}
/**
@@ -1527,14 +1527,17 @@
* 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 @@
}
};
}
- isAvailable = mStylusHandwritingAvailableCache.query(userId);
+ isAvailable = mStylusHandwritingAvailableCache.query(user.getIdentifier());
}
return isAvailable;
}
@@ -1643,16 +1646,19 @@
* 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 @@
*
* @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 @@
*/
@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());
}
/**
@@ -2106,7 +2116,7 @@
// Re-dispatch if there is a context mismatch.
final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
if (fallbackImm != null) {
- return fallbackImm.showSoftInput(view, flags, resultReceiver);
+ return fallbackImm.showSoftInput(view, statsToken, flags, resultReceiver, reason);
}
checkFocus();
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index b0e5f777..7a96fd2 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -337,7 +337,7 @@
*
* @hide
*/
- private static final int MAX_ADAPTER_CONVERSION_WAITING_TIME_MS = 2000;
+ private static final int MAX_ADAPTER_CONVERSION_WAITING_TIME_MS = 5000;
/**
* Application that hosts the remote views.
@@ -4823,7 +4823,7 @@
public static boolean isAdapterConversionEnabled() {
return AppGlobals.getIntCoreSetting(
SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION,
- SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT ? 1 : 0) == 1;
+ SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT ? 1 : 0) != 0;
}
/**
diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java
index d4f4d19..a250a86 100644
--- a/core/java/android/widget/RemoteViewsService.java
+++ b/core/java/android/widget/RemoteViewsService.java
@@ -47,7 +47,7 @@
*
* @hide
*/
- private static final int MAX_NUM_ENTRY = 25;
+ private static final int MAX_NUM_ENTRY = 10;
/**
* An interface for an adapter between a remote collection view (ListView, GridView, etc) and
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 56349d1..7dbab96 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -13202,9 +13202,8 @@
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/ScreenCapture.java b/core/java/android/window/ScreenCapture.java
index 0cc9c64..de0fe25 100644
--- a/core/java/android/window/ScreenCapture.java
+++ b/core/java/android/window/ScreenCapture.java
@@ -33,7 +33,7 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
+import java.util.function.ObjIntConsumer;
/**
* Handles display and layer captures for the system.
@@ -43,13 +43,12 @@
public class ScreenCapture {
private static final String TAG = "ScreenCapture";
private static final int SCREENSHOT_WAIT_TIME_S = 1;
-
private static native int nativeCaptureDisplay(DisplayCaptureArgs captureArgs,
long captureListener);
private static native int nativeCaptureLayers(LayerCaptureArgs captureArgs,
long captureListener);
private static native long nativeCreateScreenCaptureListener(
- Consumer<ScreenshotHardwareBuffer> consumer);
+ ObjIntConsumer<ScreenshotHardwareBuffer> consumer);
private static native void nativeWriteListenerToParcel(long nativeObject, Parcel out);
private static native long nativeReadListenerFromParcel(Parcel in);
private static native long getNativeListenerFinalizer();
@@ -695,7 +694,7 @@
/**
* @param consumer The callback invoked when the screen capture is complete.
*/
- public ScreenCaptureListener(Consumer<ScreenshotHardwareBuffer> consumer) {
+ public ScreenCaptureListener(ObjIntConsumer<ScreenshotHardwareBuffer> consumer) {
mNativeObject = nativeCreateScreenCaptureListener(consumer);
sRegistry.registerNativeAllocation(this, mNativeObject);
}
@@ -748,8 +747,13 @@
public static SynchronousScreenCaptureListener createSyncCaptureListener() {
ScreenshotHardwareBuffer[] bufferRef = new ScreenshotHardwareBuffer[1];
CountDownLatch latch = new CountDownLatch(1);
- Consumer<ScreenshotHardwareBuffer> consumer = buffer -> {
- bufferRef[0] = buffer;
+ ObjIntConsumer<ScreenshotHardwareBuffer> consumer = (buffer, status) -> {
+ if (status != 0) {
+ bufferRef[0] = null;
+ Log.e(TAG, "Failed to generate screen capture. Error code: " + status);
+ } else {
+ bufferRef[0] = buffer;
+ }
latch.countDown();
};
@@ -758,7 +762,7 @@
// it references, the underlying JNI listener holds a weak reference to the consumer.
// This property exists to ensure the consumer stays alive during the listener's
// lifetime.
- private Consumer<ScreenshotHardwareBuffer> mConsumer = consumer;
+ private ObjIntConsumer<ScreenshotHardwareBuffer> mConsumer = consumer;
@Override
public ScreenshotHardwareBuffer getBuffer() {
@@ -779,7 +783,7 @@
* {@link #captureDisplay(DisplayCaptureArgs, ScreenCaptureListener)}
*/
public abstract static class SynchronousScreenCaptureListener extends ScreenCaptureListener {
- SynchronousScreenCaptureListener(Consumer<ScreenshotHardwareBuffer> consumer) {
+ SynchronousScreenCaptureListener(ObjIntConsumer<ScreenshotHardwareBuffer> consumer) {
super(consumer);
}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index a2c4b23..0a69ea8 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -534,7 +534,14 @@
/**
* (boolean) Whether to enable the adapter conversion in RemoteViews
*/
- public static final String REMOTEVIEWS_ADAPTER_CONVERSION = "remoteviews_adapter_conversion";
+ public static final String REMOTEVIEWS_ADAPTER_CONVERSION =
+ "CursorControlFeature__remoteviews_adapter_conversion";
+
+ /**
+ * The key name used in app core settings for {@link #REMOTEVIEWS_ADAPTER_CONVERSION}
+ */
+ public static final String KEY_REMOTEVIEWS_ADAPTER_CONVERSION =
+ "systemui__remoteviews_adapter_conversion";
/**
* Default value for whether the adapter conversion is enabled or not. This is set for
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index 561b5a6..9233050 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -82,7 +82,7 @@
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/util/NotificationMessagingUtil.java b/core/java/com/android/internal/util/NotificationMessagingUtil.java
index d3cc0e7..10856b3 100644
--- a/core/java/com/android/internal/util/NotificationMessagingUtil.java
+++ b/core/java/com/android/internal/util/NotificationMessagingUtil.java
@@ -16,6 +16,7 @@
package com.android.internal.util;
+import android.annotation.Nullable;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
@@ -39,10 +40,12 @@
private static final String DEFAULT_SMS_APP_SETTING = Settings.Secure.SMS_DEFAULT_APPLICATION;
private final Context mContext;
- private SparseArray<String> mDefaultSmsApp = new SparseArray<>();
+ private final SparseArray<String> mDefaultSmsApp = new SparseArray<>();
+ private final Object mStateLock;
- public NotificationMessagingUtil(Context context) {
+ public NotificationMessagingUtil(Context context, @Nullable Object stateLock) {
mContext = context;
+ mStateLock = stateLock != null ? stateLock : new Object();
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(DEFAULT_SMS_APP_SETTING), false, mSmsContentObserver);
}
@@ -63,16 +66,20 @@
private boolean isDefaultMessagingApp(StatusBarNotification sbn) {
final int userId = sbn.getUserId();
if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false;
- if (mDefaultSmsApp.get(userId) == null) {
- cacheDefaultSmsApp(userId);
+ synchronized (mStateLock) {
+ if (mDefaultSmsApp.get(userId) == null) {
+ cacheDefaultSmsApp(userId);
+ }
+ return Objects.equals(mDefaultSmsApp.get(userId), sbn.getPackageName());
}
- return Objects.equals(mDefaultSmsApp.get(userId), sbn.getPackageName());
}
private void cacheDefaultSmsApp(int userId) {
- mDefaultSmsApp.put(userId, Settings.Secure.getStringForUser(
- mContext.getContentResolver(),
- Settings.Secure.SMS_DEFAULT_APPLICATION, userId));
+ String smsApp = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+ Settings.Secure.SMS_DEFAULT_APPLICATION, userId);
+ synchronized (mStateLock) {
+ mDefaultSmsApp.put(userId, smsApp);
+ }
}
private final ContentObserver mSmsContentObserver = new ContentObserver(
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 635adca..117c670 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -383,7 +383,11 @@
updateContentEndPaddings();
}
- @RemotableViewMethod
+ /**
+ * Set conversation data
+ * @param extras Bundle contains conversation data
+ */
+ @RemotableViewMethod(asyncImpl = "setDataAsync")
public void setData(Bundle extras) {
Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
List<Notification.MessagingStyle.Message> newMessages
@@ -409,6 +413,18 @@
setUnreadCount(unreadCount);
}
+ /**
+ * RemotableViewMethod's asyncImpl of {@link #setData(Bundle)}.
+ * This should be called on a background thread, and returns a Runnable which is then must be
+ * called on the main thread to complete the operation and set text.
+ * @param extras Bundle contains conversation data
+ * @hide
+ */
+ @NonNull
+ public Runnable setDataAsync(Bundle extras) {
+ return () -> setData(extras);
+ }
+
@Override
public void setImageResolver(ImageResolver resolver) {
mImageResolver = resolver;
diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java
index de10bd2..0704cb8 100644
--- a/core/java/com/android/internal/widget/ImageFloatingTextView.java
+++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java
@@ -63,6 +63,8 @@
public ImageFloatingTextView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+ setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL_FAST);
+ setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY);
}
@Override
@@ -83,8 +85,8 @@
.setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
.setIncludePad(getIncludeFontPadding())
.setUseLineSpacingFromFallbacks(true)
- .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
- .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL_FAST);
+ .setBreakStrategy(getBreakStrategy())
+ .setHyphenationFrequency(getHyphenationFrequency());
int maxLines;
if (mMaxLinesForHeight > 0) {
maxLines = mMaxLinesForHeight;
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index 9d142f6..f187d5c 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -156,7 +156,11 @@
mConversationTitle = conversationTitle;
}
- @RemotableViewMethod
+ /**
+ * Set Messaging data
+ * @param extras Bundle contains messaging data
+ */
+ @RemotableViewMethod(asyncImpl = "setDataAsync")
public void setData(Bundle extras) {
Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
List<Notification.MessagingStyle.Message> newMessages
@@ -173,6 +177,18 @@
bind(newMessages, newHistoricMessages, showSpinner);
}
+ /**
+ * RemotableViewMethod's asyncImpl of {@link #setData(Bundle)}.
+ * This should be called on a background thread, and returns a Runnable which is then must be
+ * called on the main thread to complete the operation and set text.
+ * @param extras Bundle contains messaging data
+ * @hide
+ */
+ @NonNull
+ public Runnable setDataAsync(Bundle extras) {
+ return () -> setData(extras);
+ }
+
@Override
public void setImageResolver(ImageResolver resolver) {
mImageResolver = resolver;
diff --git a/core/java/com/android/server/backup/CompanionBackupHelper.java b/core/java/com/android/server/backup/CompanionBackupHelper.java
new file mode 100644
index 0000000..ef247c2
--- /dev/null
+++ b/core/java/com/android/server/backup/CompanionBackupHelper.java
@@ -0,0 +1,76 @@
+/*
+ * 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.backup;
+
+import android.annotation.UserIdInt;
+import android.app.backup.BlobBackupHelper;
+import android.companion.ICompanionDeviceManager;
+import android.content.Context;
+import android.os.ServiceManager;
+import android.util.Slog;
+
+/**
+ * CDM backup and restore helper.
+ */
+public class CompanionBackupHelper extends BlobBackupHelper {
+
+ private static final String TAG = "CompanionBackupHelper";
+
+ // current schema of the backup state blob
+ private static final int BLOB_VERSION = 1;
+
+ // key under which the CDM data blob is committed to back up
+ private static final String KEY_COMPANION = "companion";
+
+ @UserIdInt
+ private final int mUserId;
+
+ public CompanionBackupHelper(int userId) {
+ super(BLOB_VERSION, KEY_COMPANION);
+
+ mUserId = userId;
+ }
+
+ @Override
+ protected byte[] getBackupPayload(String key) {
+ byte[] payload = null;
+ if (KEY_COMPANION.equals(key)) {
+ try {
+ ICompanionDeviceManager cdm = ICompanionDeviceManager.Stub.asInterface(
+ ServiceManager.getService(Context.COMPANION_DEVICE_SERVICE));
+ payload = cdm.getBackupPayload(mUserId);
+ } catch (Exception e) {
+ Slog.e(TAG, "Error getting backup from CompanionDeviceManager.", e);
+ }
+ }
+ return payload;
+ }
+
+ @Override
+ protected void applyRestoredPayload(String key, byte[] payload) {
+ Slog.i(TAG, "Got companion backup data.");
+ if (KEY_COMPANION.equals(key)) {
+ try {
+ ICompanionDeviceManager cdm = ICompanionDeviceManager.Stub.asInterface(
+ ServiceManager.getService(Context.COMPANION_DEVICE_SERVICE));
+ cdm.applyRestoredPayload(payload, mUserId);
+ } catch (Exception e) {
+ Slog.e(TAG, "Error applying restored payload to CompanionDeviceManager.", e);
+ }
+ }
+ }
+}
diff --git a/core/jni/android_hardware_HardwareBuffer.cpp b/core/jni/android_hardware_HardwareBuffer.cpp
index 5fcc46e..2ea2158d 100644
--- a/core/jni/android_hardware_HardwareBuffer.cpp
+++ b/core/jni/android_hardware_HardwareBuffer.cpp
@@ -203,10 +203,10 @@
Parcel* parcel = parcelForJavaObject(env, in);
if (parcel) {
sp<GraphicBuffer> buffer = new GraphicBuffer();
- parcel->read(*buffer);
- return reinterpret_cast<jlong>(new GraphicBufferWrapper(buffer));
+ if (parcel->read(*buffer) == STATUS_OK) {
+ return reinterpret_cast<jlong>(new GraphicBufferWrapper(buffer));
+ }
}
-
return NULL;
}
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 1afae29..979c9e3 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -347,23 +347,14 @@
}
static void NativeSetConfiguration(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint mcc, jint mnc,
- jstring default_locale, jobjectArray locales, jint orientation,
- jint touchscreen, jint density, jint keyboard,
- jint keyboard_hidden, jint navigation, jint screen_width,
- jint screen_height, jint smallest_screen_width_dp,
- jint screen_width_dp, jint screen_height_dp, jint screen_layout,
- jint ui_mode, jint color_mode, jint grammatical_gender,
- jint major_version) {
+ jstring locale, jint orientation, jint touchscreen, jint density,
+ jint keyboard, jint keyboard_hidden, jint navigation,
+ jint screen_width, jint screen_height,
+ jint smallest_screen_width_dp, jint screen_width_dp,
+ jint screen_height_dp, jint screen_layout, jint ui_mode,
+ jint color_mode, jint grammatical_gender, jint major_version) {
ATRACE_NAME("AssetManager::SetConfiguration");
- const jsize locale_count = (locales == NULL) ? 0 : env->GetArrayLength(locales);
-
- // Constants duplicated from Java class android.content.res.Configuration.
- static const jint kScreenLayoutRoundMask = 0x300;
- static const jint kScreenLayoutRoundShift = 8;
-
- std::vector<ResTable_config> configs;
-
ResTable_config configuration;
memset(&configuration, 0, sizeof(configuration));
configuration.mcc = static_cast<uint16_t>(mcc);
@@ -384,37 +375,25 @@
configuration.colorMode = static_cast<uint8_t>(color_mode);
configuration.grammaticalInflection = static_cast<uint8_t>(grammatical_gender);
configuration.sdkVersion = static_cast<uint16_t>(major_version);
+
+ if (locale != nullptr) {
+ ScopedUtfChars locale_utf8(env, locale);
+ CHECK(locale_utf8.c_str() != nullptr);
+ configuration.setBcp47Locale(locale_utf8.c_str());
+ }
+
+ // Constants duplicated from Java class android.content.res.Configuration.
+ static const jint kScreenLayoutRoundMask = 0x300;
+ static const jint kScreenLayoutRoundShift = 8;
+
// In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer
// in C++. We must extract the round qualifier out of the Java screenLayout and put it
// into screenLayout2.
configuration.screenLayout2 =
- static_cast<uint8_t>((screen_layout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift);
-
- if (locale_count > 0) {
- configs.resize(locale_count, configuration);
- for (int i = 0; i < locale_count; i++) {
- jstring locale = (jstring)(env->GetObjectArrayElement(locales, i));
- ScopedUtfChars locale_utf8(env, locale);
- CHECK(locale_utf8.c_str() != nullptr);
- configs[i].setBcp47Locale(locale_utf8.c_str());
- }
- } else {
- configs.push_back(configuration);
- }
-
- uint32_t default_locale_int = 0;
- if (default_locale != nullptr) {
- ResTable_config config;
- static_assert(std::is_same_v<decltype(config.locale), decltype(default_locale_int)>);
- ScopedUtfChars locale_utf8(env, default_locale);
- CHECK(locale_utf8.c_str() != nullptr);
- config.setBcp47Locale(locale_utf8.c_str());
- default_locale_int = config.locale;
- }
+ static_cast<uint8_t>((screen_layout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift);
auto assetmanager = LockAndStartAssetManager(ptr);
- assetmanager->SetConfigurations(configs);
- assetmanager->SetDefaultLocale(default_locale_int);
+ assetmanager->SetConfiguration(configuration);
}
static jobject NativeGetAssignedPackageIdentifiers(JNIEnv* env, jclass /*clazz*/, jlong ptr,
@@ -1519,97 +1498,94 @@
// JNI registration.
static const JNINativeMethod gAssetManagerMethods[] = {
- // AssetManager setup methods.
- {"nativeCreate", "()J", (void*)NativeCreate},
- {"nativeDestroy", "(J)V", (void*)NativeDestroy},
- {"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;Z)V", (void*)NativeSetApkAssets},
- {"nativeSetConfiguration", "(JIILjava/lang/String;[Ljava/lang/String;IIIIIIIIIIIIIIII)V",
- (void*)NativeSetConfiguration},
- {"nativeGetAssignedPackageIdentifiers", "(JZZ)Landroid/util/SparseArray;",
- (void*)NativeGetAssignedPackageIdentifiers},
+ // AssetManager setup methods.
+ {"nativeCreate", "()J", (void*)NativeCreate},
+ {"nativeDestroy", "(J)V", (void*)NativeDestroy},
+ {"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;Z)V", (void*)NativeSetApkAssets},
+ {"nativeSetConfiguration", "(JIILjava/lang/String;IIIIIIIIIIIIIIII)V",
+ (void*)NativeSetConfiguration},
+ {"nativeGetAssignedPackageIdentifiers", "(JZZ)Landroid/util/SparseArray;",
+ (void*)NativeGetAssignedPackageIdentifiers},
- // AssetManager file methods.
- {"nativeContainsAllocatedTable", "(J)Z", (void*)ContainsAllocatedTable},
- {"nativeList", "(JLjava/lang/String;)[Ljava/lang/String;", (void*)NativeList},
- {"nativeOpenAsset", "(JLjava/lang/String;I)J", (void*)NativeOpenAsset},
- {"nativeOpenAssetFd", "(JLjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
- (void*)NativeOpenAssetFd},
- {"nativeOpenNonAsset", "(JILjava/lang/String;I)J", (void*)NativeOpenNonAsset},
- {"nativeOpenNonAssetFd", "(JILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
- (void*)NativeOpenNonAssetFd},
- {"nativeOpenXmlAsset", "(JILjava/lang/String;)J", (void*)NativeOpenXmlAsset},
- {"nativeOpenXmlAssetFd", "(JILjava/io/FileDescriptor;)J", (void*)NativeOpenXmlAssetFd},
+ // AssetManager file methods.
+ {"nativeContainsAllocatedTable", "(J)Z", (void*)ContainsAllocatedTable},
+ {"nativeList", "(JLjava/lang/String;)[Ljava/lang/String;", (void*)NativeList},
+ {"nativeOpenAsset", "(JLjava/lang/String;I)J", (void*)NativeOpenAsset},
+ {"nativeOpenAssetFd", "(JLjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
+ (void*)NativeOpenAssetFd},
+ {"nativeOpenNonAsset", "(JILjava/lang/String;I)J", (void*)NativeOpenNonAsset},
+ {"nativeOpenNonAssetFd", "(JILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
+ (void*)NativeOpenNonAssetFd},
+ {"nativeOpenXmlAsset", "(JILjava/lang/String;)J", (void*)NativeOpenXmlAsset},
+ {"nativeOpenXmlAssetFd", "(JILjava/io/FileDescriptor;)J", (void*)NativeOpenXmlAssetFd},
- // AssetManager resource methods.
- {"nativeGetResourceValue", "(JISLandroid/util/TypedValue;Z)I",
- (void*)NativeGetResourceValue},
- {"nativeGetResourceBagValue", "(JIILandroid/util/TypedValue;)I",
- (void*)NativeGetResourceBagValue},
- {"nativeGetStyleAttributes", "(JI)[I", (void*)NativeGetStyleAttributes},
- {"nativeGetResourceStringArray", "(JI)[Ljava/lang/String;",
- (void*)NativeGetResourceStringArray},
- {"nativeGetResourceStringArrayInfo", "(JI)[I", (void*)NativeGetResourceStringArrayInfo},
- {"nativeGetResourceIntArray", "(JI)[I", (void*)NativeGetResourceIntArray},
- {"nativeGetResourceArraySize", "(JI)I", (void*)NativeGetResourceArraySize},
- {"nativeGetResourceArray", "(JI[I)I", (void*)NativeGetResourceArray},
- {"nativeGetParentThemeIdentifier", "(JI)I", (void*)NativeGetParentThemeIdentifier},
+ // AssetManager resource methods.
+ {"nativeGetResourceValue", "(JISLandroid/util/TypedValue;Z)I", (void*)NativeGetResourceValue},
+ {"nativeGetResourceBagValue", "(JIILandroid/util/TypedValue;)I",
+ (void*)NativeGetResourceBagValue},
+ {"nativeGetStyleAttributes", "(JI)[I", (void*)NativeGetStyleAttributes},
+ {"nativeGetResourceStringArray", "(JI)[Ljava/lang/String;",
+ (void*)NativeGetResourceStringArray},
+ {"nativeGetResourceStringArrayInfo", "(JI)[I", (void*)NativeGetResourceStringArrayInfo},
+ {"nativeGetResourceIntArray", "(JI)[I", (void*)NativeGetResourceIntArray},
+ {"nativeGetResourceArraySize", "(JI)I", (void*)NativeGetResourceArraySize},
+ {"nativeGetResourceArray", "(JI[I)I", (void*)NativeGetResourceArray},
+ {"nativeGetParentThemeIdentifier", "(JI)I",
+ (void*)NativeGetParentThemeIdentifier},
- // AssetManager resource name/ID methods.
- {"nativeGetResourceIdentifier",
- "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
- (void*)NativeGetResourceIdentifier},
- {"nativeGetResourceName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceName},
- {"nativeGetResourcePackageName", "(JI)Ljava/lang/String;",
- (void*)NativeGetResourcePackageName},
- {"nativeGetResourceTypeName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceTypeName},
- {"nativeGetResourceEntryName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceEntryName},
- {"nativeSetResourceResolutionLoggingEnabled", "(JZ)V",
- (void*)NativeSetResourceResolutionLoggingEnabled},
- {"nativeGetLastResourceResolution", "(J)Ljava/lang/String;",
- (void*)NativeGetLastResourceResolution},
- {"nativeGetLocales", "(JZ)[Ljava/lang/String;", (void*)NativeGetLocales},
- {"nativeGetSizeConfigurations", "(J)[Landroid/content/res/Configuration;",
- (void*)NativeGetSizeConfigurations},
- {"nativeGetSizeAndUiModeConfigurations", "(J)[Landroid/content/res/Configuration;",
- (void*)NativeGetSizeAndUiModeConfigurations},
+ // AssetManager resource name/ID methods.
+ {"nativeGetResourceIdentifier", "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
+ (void*)NativeGetResourceIdentifier},
+ {"nativeGetResourceName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceName},
+ {"nativeGetResourcePackageName", "(JI)Ljava/lang/String;", (void*)NativeGetResourcePackageName},
+ {"nativeGetResourceTypeName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceTypeName},
+ {"nativeGetResourceEntryName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceEntryName},
+ {"nativeSetResourceResolutionLoggingEnabled", "(JZ)V",
+ (void*) NativeSetResourceResolutionLoggingEnabled},
+ {"nativeGetLastResourceResolution", "(J)Ljava/lang/String;",
+ (void*) NativeGetLastResourceResolution},
+ {"nativeGetLocales", "(JZ)[Ljava/lang/String;", (void*)NativeGetLocales},
+ {"nativeGetSizeConfigurations", "(J)[Landroid/content/res/Configuration;",
+ (void*)NativeGetSizeConfigurations},
+ {"nativeGetSizeAndUiModeConfigurations", "(J)[Landroid/content/res/Configuration;",
+ (void*)NativeGetSizeAndUiModeConfigurations},
- // Style attribute related methods.
- {"nativeAttributeResolutionStack", "(JJIII)[I", (void*)NativeAttributeResolutionStack},
- {"nativeApplyStyle", "(JJIIJ[IJJ)V", (void*)NativeApplyStyle},
- {"nativeResolveAttrs", "(JJII[I[I[I[I)Z", (void*)NativeResolveAttrs},
- {"nativeRetrieveAttributes", "(JJ[I[I[I)Z", (void*)NativeRetrieveAttributes},
+ // Style attribute related methods.
+ {"nativeAttributeResolutionStack", "(JJIII)[I", (void*)NativeAttributeResolutionStack},
+ {"nativeApplyStyle", "(JJIIJ[IJJ)V", (void*)NativeApplyStyle},
+ {"nativeResolveAttrs", "(JJII[I[I[I[I)Z", (void*)NativeResolveAttrs},
+ {"nativeRetrieveAttributes", "(JJ[I[I[I)Z", (void*)NativeRetrieveAttributes},
- // Theme related methods.
- {"nativeThemeCreate", "(J)J", (void*)NativeThemeCreate},
- {"nativeGetThemeFreeFunction", "()J", (void*)NativeGetThemeFreeFunction},
- {"nativeThemeApplyStyle", "(JJIZ)V", (void*)NativeThemeApplyStyle},
- {"nativeThemeRebase", "(JJ[I[ZI)V", (void*)NativeThemeRebase},
+ // Theme related methods.
+ {"nativeThemeCreate", "(J)J", (void*)NativeThemeCreate},
+ {"nativeGetThemeFreeFunction", "()J", (void*)NativeGetThemeFreeFunction},
+ {"nativeThemeApplyStyle", "(JJIZ)V", (void*)NativeThemeApplyStyle},
+ {"nativeThemeRebase", "(JJ[I[ZI)V", (void*)NativeThemeRebase},
- {"nativeThemeCopy", "(JJJJ)V", (void*)NativeThemeCopy},
- {"nativeThemeGetAttributeValue", "(JJILandroid/util/TypedValue;Z)I",
- (void*)NativeThemeGetAttributeValue},
- {"nativeThemeDump", "(JJILjava/lang/String;Ljava/lang/String;)V", (void*)NativeThemeDump},
- {"nativeThemeGetChangingConfigurations", "(J)I",
- (void*)NativeThemeGetChangingConfigurations},
+ {"nativeThemeCopy", "(JJJJ)V", (void*)NativeThemeCopy},
+ {"nativeThemeGetAttributeValue", "(JJILandroid/util/TypedValue;Z)I",
+ (void*)NativeThemeGetAttributeValue},
+ {"nativeThemeDump", "(JJILjava/lang/String;Ljava/lang/String;)V", (void*)NativeThemeDump},
+ {"nativeThemeGetChangingConfigurations", "(J)I", (void*)NativeThemeGetChangingConfigurations},
- // AssetInputStream methods.
- {"nativeAssetDestroy", "(J)V", (void*)NativeAssetDestroy},
- {"nativeAssetReadChar", "(J)I", (void*)NativeAssetReadChar},
- {"nativeAssetRead", "(J[BII)I", (void*)NativeAssetRead},
- {"nativeAssetSeek", "(JJI)J", (void*)NativeAssetSeek},
- {"nativeAssetGetLength", "(J)J", (void*)NativeAssetGetLength},
- {"nativeAssetGetRemainingLength", "(J)J", (void*)NativeAssetGetRemainingLength},
+ // AssetInputStream methods.
+ {"nativeAssetDestroy", "(J)V", (void*)NativeAssetDestroy},
+ {"nativeAssetReadChar", "(J)I", (void*)NativeAssetReadChar},
+ {"nativeAssetRead", "(J[BII)I", (void*)NativeAssetRead},
+ {"nativeAssetSeek", "(JJI)J", (void*)NativeAssetSeek},
+ {"nativeAssetGetLength", "(J)J", (void*)NativeAssetGetLength},
+ {"nativeAssetGetRemainingLength", "(J)J", (void*)NativeAssetGetRemainingLength},
- // System/idmap related methods.
- {"nativeGetOverlayableMap", "(JLjava/lang/String;)Ljava/util/Map;",
- (void*)NativeGetOverlayableMap},
- {"nativeGetOverlayablesToString", "(JLjava/lang/String;)Ljava/lang/String;",
- (void*)NativeGetOverlayablesToString},
+ // System/idmap related methods.
+ {"nativeGetOverlayableMap", "(JLjava/lang/String;)Ljava/util/Map;",
+ (void*)NativeGetOverlayableMap},
+ {"nativeGetOverlayablesToString", "(JLjava/lang/String;)Ljava/lang/String;",
+ (void*)NativeGetOverlayablesToString},
- // Global management/debug methods.
- {"getGlobalAssetCount", "()I", (void*)NativeGetGlobalAssetCount},
- {"getAssetAllocations", "()Ljava/lang/String;", (void*)NativeGetAssetAllocations},
- {"getGlobalAssetManagerCount", "()I", (void*)NativeGetGlobalAssetManagerCount},
+ // Global management/debug methods.
+ {"getGlobalAssetCount", "()I", (void*)NativeGetGlobalAssetCount},
+ {"getAssetAllocations", "()Ljava/lang/String;", (void*)NativeGetAssetAllocations},
+ {"getGlobalAssetManagerCount", "()I", (void*)NativeGetGlobalAssetManagerCount},
};
int register_android_content_AssetManager(JNIEnv* env) {
diff --git a/core/jni/android_window_ScreenCapture.cpp b/core/jni/android_window_ScreenCapture.cpp
index e729750..bdf7eaa 100644
--- a/core/jni/android_window_ScreenCapture.cpp
+++ b/core/jni/android_window_ScreenCapture.cpp
@@ -102,7 +102,8 @@
}
if (!captureResults.fenceResult.ok() || captureResults.buffer == nullptr) {
- env->CallVoidMethod(consumer.get(), gConsumerClassInfo.accept, nullptr);
+ env->CallVoidMethod(consumer.get(), gConsumerClassInfo.accept, nullptr,
+ fenceStatus(captureResults.fenceResult));
checkAndClearException(env, "accept");
return binder::Status::ok();
}
@@ -117,7 +118,8 @@
captureResults.capturedSecureLayers,
captureResults.capturedHdrLayers);
checkAndClearException(env, "builder");
- env->CallVoidMethod(consumer.get(), gConsumerClassInfo.accept, screenshotHardwareBuffer);
+ env->CallVoidMethod(consumer.get(), gConsumerClassInfo.accept, screenshotHardwareBuffer,
+ fenceStatus(captureResults.fenceResult));
checkAndClearException(env, "accept");
env->DeleteLocalRef(jhardwareBuffer);
env->DeleteLocalRef(screenshotHardwareBuffer);
@@ -285,7 +287,7 @@
(void*)nativeCaptureDisplay },
{"nativeCaptureLayers", "(Landroid/window/ScreenCapture$LayerCaptureArgs;J)I",
(void*)nativeCaptureLayers },
- {"nativeCreateScreenCaptureListener", "(Ljava/util/function/Consumer;)J",
+ {"nativeCreateScreenCaptureListener", "(Ljava/util/function/ObjIntConsumer;)J",
(void*)nativeCreateScreenCaptureListener },
{"nativeWriteListenerToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteListenerToParcel },
{"nativeReadListenerFromParcel", "(Landroid/os/Parcel;)J",
@@ -333,8 +335,8 @@
gLayerCaptureArgsClassInfo.childrenOnly =
GetFieldIDOrDie(env, layerCaptureArgsClazz, "mChildrenOnly", "Z");
- jclass consumer = FindClassOrDie(env, "java/util/function/Consumer");
- gConsumerClassInfo.accept = GetMethodIDOrDie(env, consumer, "accept", "(Ljava/lang/Object;)V");
+ jclass consumer = FindClassOrDie(env, "java/util/function/ObjIntConsumer");
+ gConsumerClassInfo.accept = GetMethodIDOrDie(env, consumer, "accept", "(Ljava/lang/Object;I)V");
jclass screenshotGraphicsBufferClazz =
FindClassOrDie(env, "android/window/ScreenCapture$ScreenshotHardwareBuffer");
diff --git a/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp b/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
index bba1760..d4f6e18 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 @@
jfieldID stringData;
jfieldID binaryData;
jfieldID configuration;
+ jfieldID binaryDataOffset;
+ jfieldID binaryDataSize;
} gFabricatedOverlayInternalEntryOffsets;
static struct parcel_file_descriptor_offsets_t {
@@ -281,10 +283,17 @@
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 @@
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/res/values/config.xml b/core/res/res/values/config.xml
index 71630cb..73a1abc 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4288,6 +4288,9 @@
<!-- Colon separated list of package names that should be granted DND access -->
<string name="config_defaultDndAccessPackages" translatable="false">com.android.camera2</string>
+ <!-- Colon separated list of package names that should be removed from DND access packages -->
+ <string name="config_defaultDndDeniedPackages" translatable="false"></string>
+
<!-- User restrictions set on the SYSTEM user when it is first created.
Note: Also update appropriate overlay files. -->
<string-array translatable="false" name="config_defaultFirstUserRestrictions">
diff --git a/core/res/res/values/config_device_idle.xml b/core/res/res/values/config_device_idle.xml
index 764dbbe..98a5ff9 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 18abe70..bda194a 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/symbols.xml b/core/res/res/values/symbols.xml
index 63f29a8..b14bf5b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3381,6 +3381,8 @@
<!-- Colon separated list of package names that should be granted DND access -->
<java-symbol type="string" name="config_defaultDndAccessPackages" />
+ <!-- Colon separated list of package names that should be removed from DND access packages -->
+ <java-symbol type="string" name="config_defaultDndDeniedPackages" />
<!-- For NetworkPolicyManagerService -->
<java-symbol type="string" name="config_networkOverLimitComponent" />
@@ -4520,7 +4522,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/src/android/app/NotificationChannelTest.java b/core/tests/coretests/src/android/app/NotificationChannelTest.java
index 647bfe8..d8305f0 100644
--- a/core/tests/coretests/src/android/app/NotificationChannelTest.java
+++ b/core/tests/coretests/src/android/app/NotificationChannelTest.java
@@ -16,19 +16,52 @@
package android.app;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.TestCase.assertEquals;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.AttributionSource;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.pm.ApplicationInfo;
+import android.database.MatrixCursor;
+import android.media.AudioAttributes;
import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
import android.os.Parcel;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.provider.MediaStore.Audio.AudioColumns;
+import android.test.mock.MockContentResolver;
+import android.util.Xml;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
import com.google.common.base.Strings;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
@RunWith(AndroidJUnit4.class)
@@ -36,6 +69,88 @@
public class NotificationChannelTest {
private final String CLASS = "android.app.NotificationChannel";
+ Context mContext;
+ ContentProvider mContentProvider;
+ IContentProvider mIContentProvider;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = mock(Context.class);
+ when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
+ MockContentResolver mContentResolver = new MockContentResolver(mContext);
+ when(mContext.getContentResolver()).thenReturn(mContentResolver);
+ mContentProvider = mock(ContentProvider.class);
+ mIContentProvider = mock(IContentProvider.class);
+ when(mContentProvider.getIContentProvider()).thenReturn(mIContentProvider);
+ doAnswer(
+ invocation -> {
+ AttributionSource attributionSource = invocation.getArgument(0);
+ Uri uri = invocation.getArgument(1);
+ RemoteCallback cb = invocation.getArgument(2);
+ IContentProvider mock = (IContentProvider) (invocation.getMock());
+ AsyncTask.SERIAL_EXECUTOR.execute(
+ () -> {
+ final Bundle bundle = new Bundle();
+ try {
+ bundle.putParcelable(
+ ContentResolver.REMOTE_CALLBACK_RESULT,
+ mock.canonicalize(attributionSource, uri));
+ } catch (RemoteException e) {
+ /* consume */
+ }
+ cb.sendResult(bundle);
+ });
+ return null;
+ })
+ .when(mIContentProvider)
+ .canonicalizeAsync(any(), any(), any());
+ doAnswer(
+ invocation -> {
+ AttributionSource attributionSource = invocation.getArgument(0);
+ Uri uri = invocation.getArgument(1);
+ RemoteCallback cb = invocation.getArgument(2);
+ IContentProvider mock = (IContentProvider) (invocation.getMock());
+ AsyncTask.SERIAL_EXECUTOR.execute(
+ () -> {
+ final Bundle bundle = new Bundle();
+ try {
+ bundle.putParcelable(
+ ContentResolver.REMOTE_CALLBACK_RESULT,
+ mock.uncanonicalize(attributionSource, uri));
+ } catch (RemoteException e) {
+ /* consume */
+ }
+ cb.sendResult(bundle);
+ });
+ return null;
+ })
+ .when(mIContentProvider)
+ .uncanonicalizeAsync(any(), any(), any());
+ doAnswer(
+ invocation -> {
+ Uri uri = invocation.getArgument(0);
+ RemoteCallback cb = invocation.getArgument(1);
+ IContentProvider mock = (IContentProvider) (invocation.getMock());
+ AsyncTask.SERIAL_EXECUTOR.execute(
+ () -> {
+ final Bundle bundle = new Bundle();
+ try {
+ bundle.putString(
+ ContentResolver.REMOTE_CALLBACK_RESULT,
+ mock.getType(uri));
+ } catch (RemoteException e) {
+ /* consume */
+ }
+ cb.sendResult(bundle);
+ });
+ return null;
+ })
+ .when(mIContentProvider)
+ .getTypeAsync(any(), any());
+
+ mContentResolver.addProvider("media", mContentProvider);
+ }
+
@Test
public void testLongStringFields() {
NotificationChannel channel = new NotificationChannel("id", "name", 3);
@@ -103,4 +218,139 @@
assertEquals(NotificationChannel.MAX_TEXT_LENGTH,
fromParcel.getSound().toString().length());
}
+
+ @Test
+ public void testRestoreSoundUri_customLookup() throws Exception {
+ Uri uriToBeRestoredUncanonicalized = Uri.parse("content://media/1");
+ Uri uriToBeRestoredCanonicalized = Uri.parse("content://media/1?title=Song&canonical=1");
+ Uri uriAfterRestoredUncanonicalized = Uri.parse("content://media/100");
+ Uri uriAfterRestoredCanonicalized = Uri.parse("content://media/100?title=Song&canonical=1");
+
+ NotificationChannel channel = new NotificationChannel("id", "name", 3);
+
+ MatrixCursor cursor = new MatrixCursor(new String[] {"_id"});
+ cursor.addRow(new Object[] {100L});
+
+ when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredUncanonicalized)))
+ .thenReturn(uriToBeRestoredCanonicalized);
+
+ // Mock the failure of regular uncanonicalize.
+ when(mIContentProvider.uncanonicalize(any(), eq(uriToBeRestoredCanonicalized)))
+ .thenReturn(null);
+
+ // Mock the custom lookup in RingtoneManager.getRingtoneUriForRestore.
+ when(mIContentProvider.query(any(), any(), any(), any(), any())).thenReturn(cursor);
+
+ // Mock the canonicalize in RingtoneManager.getRingtoneUriForRestore.
+ when(mIContentProvider.canonicalize(any(), eq(uriAfterRestoredUncanonicalized)))
+ .thenReturn(uriAfterRestoredCanonicalized);
+
+ assertThat(
+ channel.restoreSoundUri(
+ mContext,
+ uriToBeRestoredUncanonicalized,
+ true,
+ AudioAttributes.USAGE_NOTIFICATION))
+ .isEqualTo(uriAfterRestoredCanonicalized);
+ }
+
+ @Test
+ public void testWriteXmlForBackup_customLookup_notificationUsage() throws Exception {
+ testWriteXmlForBackup_customLookup(
+ AudioAttributes.USAGE_NOTIFICATION, AudioColumns.IS_NOTIFICATION);
+ }
+
+ @Test
+ public void testWriteXmlForBackup_customLookup_alarmUsage() throws Exception {
+ testWriteXmlForBackup_customLookup(AudioAttributes.USAGE_ALARM, AudioColumns.IS_ALARM);
+ }
+
+ @Test
+ public void testWriteXmlForBackup_customLookup_ringtoneUsage() throws Exception {
+ testWriteXmlForBackup_customLookup(
+ AudioAttributes.USAGE_NOTIFICATION_RINGTONE, AudioColumns.IS_RINGTONE);
+ }
+
+ @Test
+ public void testWriteXmlForBackup_customLookup_unknownUsage() throws Exception {
+ testWriteXmlForBackup_customLookup(
+ AudioAttributes.USAGE_UNKNOWN, AudioColumns.IS_NOTIFICATION);
+ }
+
+ private void testWriteXmlForBackup_customLookup(int usage, String customQuerySelection)
+ throws Exception {
+ Uri uriToBeRestoredUncanonicalized = Uri.parse("content://media/1");
+ Uri uriToBeRestoredCanonicalized = Uri.parse("content://media/1?title=Song&canonical=1");
+ Uri uriAfterRestoredUncanonicalized = Uri.parse("content://media/100");
+ Uri uriAfterRestoredCanonicalized = Uri.parse("content://media/100?title=Song&canonical=1");
+
+ AudioAttributes mAudioAttributes =
+ new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
+ .setUsage(usage)
+ .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
+ .build();
+
+ NotificationChannel channel = new NotificationChannel("id", "name", 3);
+ channel.setSound(uriToBeRestoredCanonicalized, mAudioAttributes);
+
+ TypedXmlSerializer serializer = Xml.newFastSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ serializer.startDocument(null, true);
+
+ // mock the canonicalize in writeXmlForBackup -> getSoundForBackup
+ when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredUncanonicalized)))
+ .thenReturn(uriToBeRestoredCanonicalized);
+ when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredCanonicalized)))
+ .thenReturn(uriToBeRestoredCanonicalized);
+
+ channel.writeXmlForBackup(serializer, mContext);
+ serializer.endDocument();
+ serializer.flush();
+
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ byte[] byteArray = baos.toByteArray();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(byteArray)), null);
+ parser.nextTag();
+
+ NotificationChannel targetChannel = new NotificationChannel("id", "name", 3);
+
+ MatrixCursor cursor = new MatrixCursor(new String[] {"_id"});
+ cursor.addRow(new Object[] {100L});
+
+ when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredCanonicalized)))
+ .thenReturn(uriToBeRestoredCanonicalized);
+
+ // Mock the failure of regular uncanonicalize.
+ when(mIContentProvider.uncanonicalize(any(), eq(uriToBeRestoredCanonicalized)))
+ .thenReturn(null);
+
+ Bundle expectedBundle =
+ ContentResolver.createSqlQueryBundle(
+ customQuerySelection + "=1 AND title=?", new String[] {"Song"}, null);
+
+ // Mock the custom lookup in RingtoneManager.getRingtoneUriForRestore.
+ when(mIContentProvider.query(
+ any(),
+ any(),
+ any(),
+ // any(),
+ argThat(
+ queryBundle -> {
+ return queryBundle != null
+ && expectedBundle
+ .toString()
+ .equals(queryBundle.toString());
+ }),
+ any()))
+ .thenReturn(cursor);
+
+ // Mock the canonicalize in RingtoneManager.getRingtoneUriForRestore.
+ when(mIContentProvider.canonicalize(any(), eq(uriAfterRestoredUncanonicalized)))
+ .thenReturn(uriAfterRestoredCanonicalized);
+
+ targetChannel.populateFromXmlForRestore(parser, true, mContext);
+ assertThat(targetChannel.getSound()).isEqualTo(uriAfterRestoredCanonicalized);
+ }
}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
index fc72f61..4ee987b 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -180,8 +180,8 @@
assertFalse(r);
s.reset();
assertEquals(i + 1, mDatabase.getLastInsertRowId());
- assertEquals(1, mDatabase.getLastChangedRowsCount());
- assertEquals(i + 2, mDatabase.getTotalChangedRowsCount());
+ assertEquals(1, mDatabase.getLastChangedRowCount());
+ assertEquals(i + 2, mDatabase.getTotalChangedRowCount());
}
}
mDatabase.setTransactionSuccessful();
@@ -205,8 +205,8 @@
assertFalse(r);
s.reset();
assertEquals(size + i + 1, mDatabase.getLastInsertRowId());
- assertEquals(1, mDatabase.getLastChangedRowsCount());
- assertEquals(size + i + 2, mDatabase.getTotalChangedRowsCount());
+ assertEquals(1, mDatabase.getLastChangedRowCount());
+ assertEquals(size + i + 2, mDatabase.getTotalChangedRowCount());
}
}
mDatabase.setTransactionSuccessful();
@@ -214,4 +214,21 @@
mDatabase.endTransaction();
}
}
+
+ @Test
+ public void testAutomaticCountersOutsideTransactions() {
+ try {
+ mDatabase.getLastChangedRowCount();
+ fail("getLastChangedRowCount() succeeded outside a transaction");
+ } catch (IllegalStateException e) {
+ // This exception is expected.
+ }
+
+ try {
+ mDatabase.getTotalChangedRowCount();
+ fail("getTotalChangedRowCount() succeeded outside a transaction");
+ } catch (IllegalStateException e) {
+ // This exception is expected.
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index 8028b14..f1eef75 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -221,8 +221,10 @@
@Test
public void onTouchEvent_startHandwriting_inputConnectionBuilt_stylusMoveInExtendedHWArea() {
+ // The stylus down point is between mTestView1 and mTestView2, but it is within the
+ // extended handwriting area of both views. It is closer to mTestView1.
final int x1 = sHwArea1.right + HW_BOUNDS_OFFSETS_RIGHT_PX / 2;
- final int y1 = sHwArea1.bottom + HW_BOUNDS_OFFSETS_BOTTOM_PX / 2;
+ final int y1 = sHwArea1.bottom + (sHwArea2.top - sHwArea1.bottom) / 3;
MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
mHandwritingInitiator.onTouchEvent(stylusEvent1);
@@ -231,10 +233,14 @@
MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
mHandwritingInitiator.onTouchEvent(stylusEvent2);
- // InputConnection is created after stylus movement.
- mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+ // First create InputConnection for mTestView2 and verify that handwriting is not started.
+ mHandwritingInitiator.onInputConnectionCreated(mTestView2);
+ verify(mHandwritingInitiator, never()).startHandwriting(mTestView2);
- verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
+ // Next create InputConnection for mTextView1. Handwriting is started for this view since
+ // the stylus down point is closest to this view.
+ mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+ verify(mHandwritingInitiator).startHandwriting(mTestView1);
}
@Test
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 87e6b18..6057852 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -691,6 +691,12 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/Transition.java"
},
+ "-1449515133": {
+ "message": "Content Recording: stopping active projection for display %d",
+ "level": "ERROR",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"-1443029505": {
"message": "SAFE MODE ENABLED (menu=%d s=%d dpad=%d trackball=%d)",
"level": "INFO",
@@ -1285,6 +1291,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-921346089": {
+ "message": "Content Recording: Unable to tell MediaProjectionManagerService to stop the active projection for display %d: %s",
+ "level": "ERROR",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"-917215012": {
"message": "%s: caller %d is using old GET_TASKS but privileged; allowing",
"level": "WARN",
@@ -2233,12 +2245,6 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "-88873335": {
- "message": "Content Recording: Unable to tell MediaProjectionManagerService to stop the active projection: %s",
- "level": "ERROR",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecorder.java"
- },
"-87705714": {
"message": "findFocusedWindow: focusedApp=null using new focus @ %s",
"level": "VERBOSE",
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 3d72963..079cfa3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -831,7 +831,8 @@
return true;
}
- if (!isOnReparent && getContainerWithActivity(activity) == null
+ final TaskFragmentContainer container = getContainerWithActivity(activity);
+ if (!isOnReparent && container == null
&& getTaskFragmentTokenFromActivityClientRecord(activity) != null) {
// We can't find the new launched activity in any recorded container, but it is
// currently placed in an embedded TaskFragment. This can happen in two cases:
@@ -843,11 +844,21 @@
return true;
}
- final TaskFragmentContainer container = getContainerWithActivity(activity);
- if (!isOnReparent && container != null
- && container.getTaskContainer().getTopNonFinishingTaskFragmentContainer()
+ // Skip resolving if the activity is on a pinned TaskFragmentContainer.
+ // TODO(b/243518738): skip resolving for overlay container.
+ if (container != null) {
+ final TaskContainer taskContainer = container.getTaskContainer();
+ if (taskContainer.isTaskFragmentContainerPinned(container)) {
+ return true;
+ }
+ }
+
+ final TaskContainer taskContainer = container != null ? container.getTaskContainer() : null;
+ if (!isOnReparent && taskContainer != null
+ && taskContainer.getTopNonFinishingTaskFragmentContainer(false /* includePin */)
!= container) {
- // Do not resolve if the launched activity is not the top-most container in the Task.
+ // Do not resolve if the launched activity is not the top-most container (excludes
+ // the pinned container) in the Task.
return true;
}
@@ -1244,6 +1255,19 @@
@GuardedBy("mLock")
TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct,
int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
+ // Skip resolving if started from pinned TaskFragmentContainer.
+ // TODO(b/243518738): skip resolving for overlay container.
+ if (launchingActivity != null) {
+ final TaskFragmentContainer taskFragmentContainer = getContainerWithActivity(
+ launchingActivity);
+ final TaskContainer taskContainer =
+ taskFragmentContainer != null ? taskFragmentContainer.getTaskContainer() : null;
+ if (taskContainer != null && taskContainer.isTaskFragmentContainerPinned(
+ taskFragmentContainer)) {
+ return null;
+ }
+ }
+
/*
* We will check the following to see if there is any embedding rule matched:
* 1. Whether the new activity intent should always expand.
@@ -1584,6 +1608,13 @@
return;
}
+ // If the secondary container is pinned, it should not be removed.
+ final SplitContainer activeContainer =
+ getActiveSplitForContainer(existingSplitContainer.getSecondaryContainer());
+ if (activeContainer instanceof SplitPinContainer) {
+ return;
+ }
+
existingSplitContainer.getSecondaryContainer().finish(
false /* shouldFinishDependent */, mPresenter, wct, this);
}
@@ -1625,12 +1656,7 @@
// background.
return;
}
- final SplitContainer splitContainer = getActiveSplitForContainer(container);
- if (splitContainer instanceof SplitPinContainer
- && updateSplitContainerIfNeeded(splitContainer, wct, null /* splitAttributes */)) {
- // A SplitPinContainer exists and is updated.
- return;
- }
+
if (launchPlaceholderIfNecessary(wct, container)) {
// Placeholder was launched, the positions will be updated when the activity is added
// to the secondary container.
@@ -1643,6 +1669,7 @@
// If the info is not available yet the task fragment will be expanded when it's ready
return;
}
+ final SplitContainer splitContainer = getActiveSplitForContainer(container);
if (splitContainer == null) {
return;
}
@@ -1826,6 +1853,10 @@
// Don't launch placeholder for primary split container.
return false;
}
+ if (splitContainer instanceof SplitPinContainer) {
+ // Don't launch placeholder if pinned
+ return false;
+ }
return true;
}
@@ -2080,8 +2111,9 @@
* Returns {@code true} if an Activity with the provided component name should always be
* expanded to occupy full task bounds. Such activity must not be put in a split.
*/
+ @VisibleForTesting
@GuardedBy("mLock")
- private boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) {
+ boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) {
for (EmbeddingRule rule : mSplitRules) {
if (!(rule instanceof ActivityRule)) {
continue;
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 fa1eb9e..16d8cb4 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -179,8 +179,16 @@
@Nullable
TaskFragmentContainer getTopNonFinishingTaskFragmentContainer() {
+ return getTopNonFinishingTaskFragmentContainer(true /* includePin */);
+ }
+
+ @Nullable
+ TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin) {
for (int i = mContainers.size() - 1; i >= 0; i--) {
final TaskFragmentContainer container = mContainers.get(i);
+ if (!includePin && isTaskFragmentContainerPinned(container)) {
+ continue;
+ }
if (!container.isFinished()) {
return container;
}
@@ -266,6 +274,11 @@
return mSplitPinContainer;
}
+ boolean isTaskFragmentContainerPinned(@NonNull TaskFragmentContainer taskFragmentContainer) {
+ return mSplitPinContainer != null
+ && mSplitPinContainer.getSecondaryContainer() == taskFragmentContainer;
+ }
+
void addTaskFragmentContainer(@NonNull TaskFragmentContainer taskFragmentContainer) {
mContainers.add(taskFragmentContainer);
onTaskFragmentContainerUpdated();
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 b504b0c..d440a3e 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
@@ -595,6 +595,18 @@
}
@Test
+ public void testResolveStartActivityIntent_skipIfPinned() {
+ final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+ final TaskContainer taskContainer = container.getTaskContainer();
+ spyOn(taskContainer);
+ final Intent intent = new Intent();
+ setupSplitRule(mActivity, intent);
+ doReturn(true).when(taskContainer).isTaskFragmentContainerPinned(container);
+ assertNull(mSplitController.resolveStartActivityIntent(mTransaction, TASK_ID, intent,
+ mActivity));
+ }
+
+ @Test
public void testPlaceActivityInTopContainer() {
mSplitController.placeActivityInTopContainer(mTransaction, mActivity);
@@ -1044,6 +1056,29 @@
}
@Test
+ public void testResolveActivityToContainer_skipIfNonTopOrPinned() {
+ final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+ final Activity pinnedActivity = createMockActivity();
+ final TaskFragmentContainer topContainer = mSplitController.newContainer(pinnedActivity,
+ TASK_ID);
+ final TaskContainer taskContainer = container.getTaskContainer();
+ spyOn(taskContainer);
+ doReturn(container).when(taskContainer).getTopNonFinishingTaskFragmentContainer(false);
+ doReturn(true).when(taskContainer).isTaskFragmentContainerPinned(topContainer);
+
+ // No need to handle when the new launched activity is in a pinned TaskFragment.
+ assertTrue(mSplitController.resolveActivityToContainer(mTransaction, pinnedActivity,
+ false /* isOnReparent */));
+ verify(mSplitController, never()).shouldExpand(any(), any());
+
+ // Should proceed to resolve if the new launched activity is in the next top TaskFragment
+ // (e.g. the top-most TaskFragment is pinned)
+ mSplitController.resolveActivityToContainer(mTransaction, mActivity,
+ false /* isOnReparent */);
+ verify(mSplitController).shouldExpand(any(), any());
+ }
+
+ @Test
public void testGetPlaceholderOptions() {
// Setup to make sure a transaction record is started.
mTransactionManager.startNewTransaction();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
index 9bf3b80..42dc19c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
@@ -52,12 +52,13 @@
/**
* Ensures the back animation background color layer is present.
+ *
* @param startRect The start bounds of the closing target.
* @param color The background color.
* @param transaction The animation transaction.
*/
- void ensureBackground(Rect startRect, int color,
- @NonNull SurfaceControl.Transaction transaction) {
+ public void ensureBackground(
+ Rect startRect, int color, @NonNull SurfaceControl.Transaction transaction) {
if (mBackgroundSurface != null) {
return;
}
@@ -81,7 +82,12 @@
mIsRequestingStatusBarAppearance = false;
}
- void removeBackground(@NonNull SurfaceControl.Transaction transaction) {
+ /**
+ * Remove the back animation background.
+ *
+ * @param transaction The animation transaction.
+ */
+ public void removeBackground(@NonNull SurfaceControl.Transaction transaction) {
if (mBackgroundSurface == null) {
return;
}
@@ -93,11 +99,21 @@
mIsRequestingStatusBarAppearance = false;
}
+ /**
+ * Attach a {@link StatusBarCustomizer} instance to allow status bar animate with back progress.
+ *
+ * @param customizer The {@link StatusBarCustomizer} to be used.
+ */
void setStatusBarCustomizer(StatusBarCustomizer customizer) {
mCustomizer = customizer;
}
- void onBackProgressed(float progress) {
+ /**
+ * Update back animation background with for the progress.
+ *
+ * @param progress Progress value from {@link android.window.BackProgressAnimator}
+ */
+ public void onBackProgressed(float progress) {
if (mCustomizer == null || mStartBounds.isEmpty()) {
return;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index bb543f2..3790f04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -25,6 +25,7 @@
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
import android.content.ContentResolver;
@@ -43,7 +44,6 @@
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.MathUtils;
-import android.util.SparseArray;
import android.view.IRemoteAnimationRunner;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
@@ -70,6 +70,7 @@
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -113,7 +114,11 @@
private boolean mShouldStartOnNextMoveEvent = false;
/** @see #setTriggerBack(boolean) */
private boolean mTriggerBack;
- private FlingAnimationUtils mFlingAnimationUtils;
+
+ private final FlingAnimationUtils mFlingAnimationUtils;
+
+ /** Registry for the back animations */
+ private final ShellBackAnimationRegistry mShellBackAnimationRegistry;
@Nullable
private BackNavigationInfo mBackNavigationInfo;
@@ -135,13 +140,9 @@
private final TouchTracker mTouchTracker = new TouchTracker();
- private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>();
@Nullable
private IOnBackInvokedCallback mActiveCallback;
- private CrossActivityAnimation mDefaultActivityAnimation;
- private CustomizeActivityAnimation mCustomizeActivityAnimation;
-
@VisibleForTesting
final RemoteCallback mNavigationObserver = new RemoteCallback(
new RemoteCallback.OnResultListener() {
@@ -169,10 +170,18 @@
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler backgroundHandler,
Context context,
- @NonNull BackAnimationBackground backAnimationBackground) {
- this(shellInit, shellController, shellExecutor, backgroundHandler,
- ActivityTaskManager.getService(), context, context.getContentResolver(),
- backAnimationBackground);
+ @NonNull BackAnimationBackground backAnimationBackground,
+ ShellBackAnimationRegistry shellBackAnimationRegistry) {
+ this(
+ shellInit,
+ shellController,
+ shellExecutor,
+ backgroundHandler,
+ ActivityTaskManager.getService(),
+ context,
+ context.getContentResolver(),
+ backAnimationBackground,
+ shellBackAnimationRegistry);
}
@VisibleForTesting
@@ -182,8 +191,10 @@
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler bgHandler,
@NonNull IActivityTaskManager activityTaskManager,
- Context context, ContentResolver contentResolver,
- @NonNull BackAnimationBackground backAnimationBackground) {
+ Context context,
+ ContentResolver contentResolver,
+ @NonNull BackAnimationBackground backAnimationBackground,
+ ShellBackAnimationRegistry shellBackAnimationRegistry) {
mShellController = shellController;
mShellExecutor = shellExecutor;
mActivityTaskManager = activityTaskManager;
@@ -197,11 +208,7 @@
.setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS)
.setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
.build();
- }
-
- @VisibleForTesting
- void setEnableUAnimation(boolean enable) {
- IS_U_ANIMATION_ENABLED = enable;
+ mShellBackAnimationRegistry = shellBackAnimationRegistry;
}
private void onInit() {
@@ -209,26 +216,6 @@
createAdapter();
mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
this::createExternalInterface, this);
-
- initBackAnimationRunners();
- }
-
- private void initBackAnimationRunners() {
- if (!IS_U_ANIMATION_ENABLED) {
- return;
- }
-
- final CrossTaskBackAnimation crossTaskAnimation =
- new CrossTaskBackAnimation(mContext, mAnimationBackground);
- mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK,
- crossTaskAnimation.mBackAnimationRunner);
- mDefaultActivityAnimation =
- new CrossActivityAnimation(mContext, mAnimationBackground);
- mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
- mDefaultActivityAnimation.mBackAnimationRunner);
- mCustomizeActivityAnimation =
- new CustomizeActivityAnimation(mContext, mAnimationBackground);
- // TODO (236760237): register dialog close animation when it's completed.
}
private void setupAnimationDeveloperSettingsObserver(
@@ -359,11 +346,11 @@
void registerAnimation(@BackNavigationInfo.BackTargetType int type,
@NonNull BackAnimationRunner runner) {
- mAnimationDefinition.set(type, runner);
+ mShellBackAnimationRegistry.registerAnimation(type, runner);
}
void unregisterAnimation(@BackNavigationInfo.BackTargetType int type) {
- mAnimationDefinition.remove(type);
+ mShellBackAnimationRegistry.unregisterAnimation(type);
}
/**
@@ -434,9 +421,7 @@
final int backType = backNavigationInfo.getType();
final boolean shouldDispatchToAnimator = shouldDispatchToAnimator();
if (shouldDispatchToAnimator) {
- if (mAnimationDefinition.contains(backType)) {
- mAnimationDefinition.get(backType).startGesture();
- } else {
+ if (!mShellBackAnimationRegistry.startGesture(backType)) {
mActiveCallback = null;
}
} else {
@@ -459,6 +444,7 @@
sendBackEvent(KeyEvent.ACTION_UP);
}
+ @SuppressLint("MissingPermission")
private void sendBackEvent(int action) {
final long when = SystemClock.uptimeMillis();
final KeyEvent ev = new KeyEvent(when, when, action, KeyEvent.KEYCODE_BACK, 0 /* repeat */,
@@ -671,21 +657,17 @@
}
final int backType = mBackNavigationInfo.getType();
- final BackAnimationRunner runner = mAnimationDefinition.get(backType);
// Simply trigger and finish back navigation when no animator defined.
- if (!shouldDispatchToAnimator() || runner == null) {
+ if (!shouldDispatchToAnimator()
+ || mShellBackAnimationRegistry.isAnimationCancelledOrNull(backType)) {
invokeOrCancelBack();
return;
- }
- if (runner.isWaitingAnimation()) {
+ } else if (mShellBackAnimationRegistry.isWaitingAnimation(backType)) {
ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Gesture released, but animation didn't ready.");
// Supposed it is in post commit animation state, and start the timeout to watch
// if the animation is ready.
mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
return;
- } else if (runner.isAnimationCancelled()) {
- invokeOrCancelBack();
- return;
}
startPostCommitAnimation();
}
@@ -737,12 +719,7 @@
mShouldStartOnNextMoveEvent = false;
mTouchTracker.reset();
mActiveCallback = null;
- // reset to default
- if (mDefaultActivityAnimation != null
- && mAnimationDefinition.contains(BackNavigationInfo.TYPE_CROSS_ACTIVITY)) {
- mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
- mDefaultActivityAnimation.mBackAnimationRunner);
- }
+ mShellBackAnimationRegistry.resetDefaultCrossActivity();
if (mBackNavigationInfo != null) {
mBackNavigationInfo.onBackNavigationFinished(mTriggerBack);
mBackNavigationInfo = null;
@@ -750,86 +727,88 @@
mTriggerBack = false;
}
- private BackAnimationRunner getAnimationRunnerAndInit() {
- int type = mBackNavigationInfo.getType();
- // Initiate customized cross-activity animation, or fall back to cross activity animation
- if (type == BackNavigationInfo.TYPE_CROSS_ACTIVITY && mAnimationDefinition.contains(type)) {
- final BackNavigationInfo.CustomAnimationInfo animationInfo =
- mBackNavigationInfo.getCustomAnimationInfo();
- if (animationInfo != null && mCustomizeActivityAnimation != null
- && mCustomizeActivityAnimation.prepareNextAnimation(animationInfo)) {
- mAnimationDefinition.get(type).resetWaitingAnimation();
- mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
- mCustomizeActivityAnimation.mBackAnimationRunner);
- }
- }
- return mAnimationDefinition.get(type);
- }
private void createAdapter() {
- IBackAnimationRunner runner = new IBackAnimationRunner.Stub() {
- @Override
- public void onAnimationStart(RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
- IBackAnimationFinishedCallback finishedCallback) {
- mShellExecutor.execute(() -> {
- if (mBackNavigationInfo == null) {
- Log.e(TAG, "Lack of navigation info to start animation.");
- return;
- }
- final int type = mBackNavigationInfo.getType();
- final BackAnimationRunner runner = getAnimationRunnerAndInit();
- if (runner == null) {
- Log.e(TAG, "Animation didn't be defined for type "
- + BackNavigationInfo.typeToString(type));
- if (finishedCallback != null) {
- try {
- finishedCallback.onAnimationFinished(false);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call IBackNaviAnimationController", e);
- }
- }
- return;
- }
- mActiveCallback = runner.getCallback();
- mBackAnimationFinishedCallback = finishedCallback;
+ IBackAnimationRunner runner =
+ new IBackAnimationRunner.Stub() {
+ @Override
+ public void onAnimationStart(
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ IBackAnimationFinishedCallback finishedCallback) {
+ mShellExecutor.execute(
+ () -> {
+ if (mBackNavigationInfo == null) {
+ Log.e(TAG, "Lack of navigation info to start animation.");
+ return;
+ }
+ final BackAnimationRunner runner =
+ mShellBackAnimationRegistry.getAnimationRunnerAndInit(
+ mBackNavigationInfo);
+ if (runner == null) {
+ if (finishedCallback != null) {
+ try {
+ finishedCallback.onAnimationFinished(false);
+ } catch (RemoteException e) {
+ Log.w(
+ TAG,
+ "Failed call IBackNaviAnimationController",
+ e);
+ }
+ }
+ return;
+ }
+ mActiveCallback = runner.getCallback();
+ mBackAnimationFinishedCallback = finishedCallback;
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startAnimation()");
- runner.startAnimation(apps, wallpapers, nonApps, () -> mShellExecutor.execute(
- BackAnimationController.this::onBackAnimationFinished));
+ ProtoLog.d(
+ WM_SHELL_BACK_PREVIEW,
+ "BackAnimationController: startAnimation()");
+ runner.startAnimation(
+ apps,
+ wallpapers,
+ nonApps,
+ () ->
+ mShellExecutor.execute(
+ BackAnimationController.this
+ ::onBackAnimationFinished));
- if (apps.length >= 1) {
- dispatchOnBackStarted(
- mActiveCallback, mTouchTracker.createStartEvent(apps[0]));
+ if (apps.length >= 1) {
+ dispatchOnBackStarted(
+ mActiveCallback,
+ mTouchTracker.createStartEvent(apps[0]));
+ }
+
+ // Dispatch the first progress after animation start for
+ // smoothing the initial animation, instead of waiting for next
+ // onMove.
+ final BackMotionEvent backFinish =
+ mTouchTracker.createProgressEvent();
+ dispatchOnBackProgressed(mActiveCallback, backFinish);
+ if (!mBackGestureStarted) {
+ // if the down -> up gesture happened before animation
+ // start, we have to trigger the uninterruptible transition
+ // to finish the back animation.
+ startPostCommitAnimation();
+ }
+ });
}
- // Dispatch the first progress after animation start for smoothing the initial
- // animation, instead of waiting for next onMove.
- final BackMotionEvent backFinish = mTouchTracker.createProgressEvent();
- dispatchOnBackProgressed(mActiveCallback, backFinish);
- if (!mBackGestureStarted) {
- // if the down -> up gesture happened before animation start, we have to
- // trigger the uninterruptible transition to finish the back animation.
- startPostCommitAnimation();
+ @Override
+ public void onAnimationCancelled() {
+ mShellExecutor.execute(
+ () -> {
+ if (!mShellBackAnimationRegistry.cancel(
+ mBackNavigationInfo.getType())) {
+ return;
+ }
+ if (!mBackGestureStarted) {
+ invokeOrCancelBack();
+ }
+ });
}
- });
- }
-
- @Override
- public void onAnimationCancelled() {
- mShellExecutor.execute(() -> {
- final BackAnimationRunner runner = mAnimationDefinition.get(
- mBackNavigationInfo.getType());
- if (runner == null) {
- return;
- }
- runner.cancelAnimation();
- if (!mBackGestureStarted) {
- invokeOrCancelBack();
- }
- });
- }
- };
+ };
mBackAnimationAdapter = new BackAnimationAdapter(runner);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index 913239f7..431df21 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -32,7 +32,7 @@
* before it received IBackAnimationRunner#onAnimationStart, so the controller could continue
* trigger the real back behavior.
*/
-class BackAnimationRunner {
+public class BackAnimationRunner {
private static final String TAG = "ShellBackPreview";
private final IOnBackInvokedCallback mCallback;
@@ -44,8 +44,8 @@
/** True when the back animation is cancelled */
private boolean mAnimationCancelled;
- BackAnimationRunner(@NonNull IOnBackInvokedCallback callback,
- @NonNull IRemoteAnimationRunner runner) {
+ public BackAnimationRunner(
+ @NonNull IOnBackInvokedCallback callback, @NonNull IRemoteAnimationRunner runner) {
mCallback = callback;
mRunner = runner;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
index edefe9e..9181281 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -51,9 +51,11 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import javax.inject.Inject;
+
/** Class that defines cross-activity animation. */
@ShellMainThread
-class CrossActivityAnimation {
+public class CrossActivityAnimation extends ShellBackAnimation {
/**
* Minimum scale of the entering/closing window.
*/
@@ -106,6 +108,7 @@
private final SpringAnimation mLeavingProgressSpring;
// Max window x-shift in pixels.
private final float mWindowXShift;
+ private final BackAnimationRunner mBackAnimationRunner;
private float mEnteringProgress = 0f;
private float mLeavingProgress = 0f;
@@ -126,11 +129,11 @@
private IRemoteAnimationFinishedCallback mFinishCallback;
private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
- final BackAnimationRunner mBackAnimationRunner;
private final BackAnimationBackground mBackground;
- CrossActivityAnimation(Context context, BackAnimationBackground background) {
+ @Inject
+ public CrossActivityAnimation(Context context, BackAnimationBackground background) {
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
mBackground = background;
@@ -357,6 +360,11 @@
mTransaction.apply();
}
+ @Override
+ public BackAnimationRunner getRunner() {
+ return mBackAnimationRunner;
+ }
+
private final class Callback extends IOnBackInvokedCallback.Default {
@Override
public void onBackStarted(BackMotionEvent backEvent) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index a7dd27a..209d853 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -47,21 +47,23 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import javax.inject.Inject;
+
/**
* Controls the animation of swiping back and returning to another task.
*
- * This is a two part animation. The first part is an animation that tracks gesture location to
- * scale and move the closing and entering app windows.
- * Once the gesture is committed, the second part remains the closing window in place.
- * The entering window plays the rest of app opening transition to enter full screen.
+ * <p>This is a two part animation. The first part is an animation that tracks gesture location to
+ * scale and move the closing and entering app windows. Once the gesture is committed, the second
+ * part remains the closing window in place. The entering window plays the rest of app opening
+ * transition to enter full screen.
*
- * This animation is used only for apps that enable back dispatching via
- * {@link android.window.OnBackInvokedDispatcher}. The controller registers
- * an {@link IOnBackInvokedCallback} with WM Shell and receives back dispatches when a back
- * navigation to launcher starts.
+ * <p>This animation is used only for apps that enable back dispatching via {@link
+ * android.window.OnBackInvokedDispatcher}. The controller registers an {@link
+ * IOnBackInvokedCallback} with WM Shell and receives back dispatches when a back navigation to
+ * launcher starts.
*/
@ShellMainThread
-class CrossTaskBackAnimation {
+public class CrossTaskBackAnimation extends ShellBackAnimation {
private static final int BACKGROUNDCOLOR = 0x43433A;
/**
@@ -104,28 +106,41 @@
private final float[] mTmpFloat9 = new float[9];
private final float[] mTmpTranslate = {0, 0, 0};
-
+ private final BackAnimationRunner mBackAnimationRunner;
+ private final BackAnimationBackground mBackground;
private RemoteAnimationTarget mEnteringTarget;
private RemoteAnimationTarget mClosingTarget;
private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
-
private boolean mBackInProgress = false;
-
private boolean mIsRightEdge;
private float mProgress = 0;
private PointF mTouchPos = new PointF();
private IRemoteAnimationFinishedCallback mFinishCallback;
private BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
- final BackAnimationRunner mBackAnimationRunner;
- private final BackAnimationBackground mBackground;
-
- CrossTaskBackAnimation(Context context, BackAnimationBackground background) {
+ @Inject
+ public CrossTaskBackAnimation(Context context, BackAnimationBackground background) {
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
mBackground = background;
}
+ private static void computeScaleTransformMatrix(float scale, float[] matrix) {
+ matrix[0] = scale;
+ matrix[1] = 0;
+ matrix[2] = 0;
+ matrix[3] = 0;
+ matrix[4] = scale;
+ matrix[5] = 0;
+ matrix[6] = 0;
+ matrix[7] = 0;
+ matrix[8] = scale;
+ }
+
+ private static float mapRange(float value, float min, float max) {
+ return min + (value * (max - min));
+ }
+
private float getInterpolatedProgress(float backProgress) {
return 1 - (1 - backProgress) * (1 - backProgress) * (1 - backProgress);
}
@@ -233,18 +248,6 @@
mTransaction.setColorTransform(leash, mTmpFloat9, mTmpTranslate);
}
- static void computeScaleTransformMatrix(float scale, float[] matrix) {
- matrix[0] = scale;
- matrix[1] = 0;
- matrix[2] = 0;
- matrix[3] = 0;
- matrix[4] = scale;
- matrix[5] = 0;
- matrix[6] = 0;
- matrix[7] = 0;
- matrix[8] = scale;
- }
-
private void finishAnimation() {
if (mEnteringTarget != null) {
mEnteringTarget.leash.release();
@@ -314,11 +317,12 @@
valueAnimator.start();
}
- private static float mapRange(float value, float min, float max) {
- return min + (value * (max - min));
+ @Override
+ public BackAnimationRunner getRunner() {
+ return mBackAnimationRunner;
}
- private final class Callback extends IOnBackInvokedCallback.Default {
+ private final class Callback extends IOnBackInvokedCallback.Default {
@Override
public void onBackStarted(BackMotionEvent backEvent) {
mProgressAnimator.onBackStarted(backEvent,
@@ -340,7 +344,7 @@
mProgressAnimator.reset();
onGestureCommitted();
}
- };
+ }
private final class Runner extends IRemoteAnimationRunner.Default {
@Override
@@ -360,5 +364,5 @@
startBackAnimation();
mFinishCallback = finishedCallback;
}
- };
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
index 2d6ec75..aca638c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
@@ -55,13 +55,13 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.annotations.ShellMainThread;
-/**
- * Class that handle customized close activity transition animation.
- */
+import javax.inject.Inject;
+
+/** Class that handle customized close activity transition animation. */
@ShellMainThread
-class CustomizeActivityAnimation {
+public class CustomizeActivityAnimation extends ShellBackAnimation {
private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
- final BackAnimationRunner mBackAnimationRunner;
+ private final BackAnimationRunner mBackAnimationRunner;
private final float mCornerRadius;
private final SurfaceControl.Transaction mTransaction;
private final BackAnimationBackground mBackground;
@@ -88,7 +88,8 @@
private final Choreographer mChoreographer;
- CustomizeActivityAnimation(Context context, BackAnimationBackground background) {
+ @Inject
+ public CustomizeActivityAnimation(Context context, BackAnimationBackground background) {
this(context, background, new SurfaceControl.Transaction(), null);
}
@@ -258,10 +259,12 @@
valueAnimator.start();
}
- /**
- * Load customize animation before animation start.
- */
- boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
+ /** Load customize animation before animation start. */
+ @Override
+ public boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
+ if (animationInfo == null) {
+ return false;
+ }
final AnimationLoadResult result = mCustomAnimationLoader.loadAll(animationInfo);
if (result != null) {
mCloseAnimation = result.mCloseAnimation;
@@ -272,6 +275,11 @@
return false;
}
+ @Override
+ public BackAnimationRunner getRunner() {
+ return mBackAnimationRunner;
+ }
+
private final class Callback extends IOnBackInvokedCallback.Default {
@Override
public void onBackStarted(BackMotionEvent backEvent) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java
new file mode 100644
index 0000000..312e88d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java
@@ -0,0 +1,48 @@
+/*
+ * 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.back;
+
+import android.window.BackNavigationInfo;
+
+import javax.inject.Qualifier;
+
+/** Base class for all back animations. */
+public abstract class ShellBackAnimation {
+ @Qualifier
+ public @interface CrossActivity {}
+
+ @Qualifier
+ public @interface CrossTask {}
+
+ @Qualifier
+ public @interface CustomizeActivity {}
+
+ @Qualifier
+ public @interface ReturnToHome {}
+
+ /** Retrieve the {@link BackAnimationRunner} associated with this animation. */
+ public abstract BackAnimationRunner getRunner();
+
+ /**
+ * Prepare the next animation with customized animation.
+ *
+ * @return true if this type of back animation should override the default.
+ */
+ public boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
+ return false;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java
new file mode 100644
index 0000000..62b18f3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java
@@ -0,0 +1,145 @@
+/*
+ * 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.back;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+import android.util.SparseArray;
+import android.window.BackNavigationInfo;
+
+/** Registry for all types of default back animations */
+public class ShellBackAnimationRegistry {
+ private static final String TAG = "ShellBackPreview";
+
+ private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>();
+ private final ShellBackAnimation mDefaultCrossActivityAnimation;
+ private final ShellBackAnimation mCustomizeActivityAnimation;
+
+ public ShellBackAnimationRegistry(
+ @ShellBackAnimation.CrossActivity @Nullable ShellBackAnimation crossActivityAnimation,
+ @ShellBackAnimation.CrossTask @Nullable ShellBackAnimation crossTaskAnimation,
+ @ShellBackAnimation.CustomizeActivity @Nullable
+ ShellBackAnimation customizeActivityAnimation,
+ @ShellBackAnimation.ReturnToHome @Nullable
+ ShellBackAnimation defaultBackToHomeAnimation) {
+ if (crossActivityAnimation != null) {
+ mAnimationDefinition.set(
+ BackNavigationInfo.TYPE_CROSS_TASK, crossTaskAnimation.getRunner());
+ }
+ if (crossActivityAnimation != null) {
+ mAnimationDefinition.set(
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY, crossActivityAnimation.getRunner());
+ }
+ if (defaultBackToHomeAnimation != null) {
+ mAnimationDefinition.set(
+ BackNavigationInfo.TYPE_RETURN_TO_HOME, defaultBackToHomeAnimation.getRunner());
+ }
+
+ mDefaultCrossActivityAnimation = crossActivityAnimation;
+ mCustomizeActivityAnimation = customizeActivityAnimation;
+
+ // TODO(b/236760237): register dialog close animation when it's completed.
+ }
+
+ void registerAnimation(
+ @BackNavigationInfo.BackTargetType int type, @NonNull BackAnimationRunner runner) {
+ mAnimationDefinition.set(type, runner);
+ }
+
+ void unregisterAnimation(@BackNavigationInfo.BackTargetType int type) {
+ mAnimationDefinition.remove(type);
+ }
+
+ /**
+ * Start the {@link BackAnimationRunner} associated with a back target type.
+ *
+ * @param type back target type
+ * @return true if the animation is started, false if animation is not found for that type.
+ */
+ boolean startGesture(@BackNavigationInfo.BackTargetType int type) {
+ BackAnimationRunner runner = mAnimationDefinition.get(type);
+ if (runner == null) {
+ return false;
+ }
+ runner.startGesture();
+ return true;
+ }
+
+ /**
+ * Cancel the {@link BackAnimationRunner} associated with a back target type.
+ *
+ * @param type back target type
+ * @return true if the animation is started, false if animation is not found for that type.
+ */
+ boolean cancel(@BackNavigationInfo.BackTargetType int type) {
+ BackAnimationRunner runner = mAnimationDefinition.get(type);
+ if (runner == null) {
+ return false;
+ }
+ runner.cancelAnimation();
+ return true;
+ }
+
+ boolean isAnimationCancelledOrNull(@BackNavigationInfo.BackTargetType int type) {
+ BackAnimationRunner runner = mAnimationDefinition.get(type);
+ if (runner == null) {
+ return true;
+ }
+ return runner.isAnimationCancelled();
+ }
+
+ boolean isWaitingAnimation(@BackNavigationInfo.BackTargetType int type) {
+ BackAnimationRunner runner = mAnimationDefinition.get(type);
+ if (runner == null) {
+ return false;
+ }
+ return runner.isWaitingAnimation();
+ }
+
+ void resetDefaultCrossActivity() {
+ if (mDefaultCrossActivityAnimation == null
+ || !mAnimationDefinition.contains(BackNavigationInfo.TYPE_CROSS_ACTIVITY)) {
+ return;
+ }
+ mAnimationDefinition.set(
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY, mDefaultCrossActivityAnimation.getRunner());
+ }
+
+ BackAnimationRunner getAnimationRunnerAndInit(BackNavigationInfo backNavigationInfo) {
+ int type = backNavigationInfo.getType();
+ // Initiate customized cross-activity animation, or fall back to cross activity animation
+ if (type == BackNavigationInfo.TYPE_CROSS_ACTIVITY && mAnimationDefinition.contains(type)) {
+ if (mCustomizeActivityAnimation != null
+ && mCustomizeActivityAnimation.prepareNextAnimation(
+ backNavigationInfo.getCustomAnimationInfo())) {
+ mAnimationDefinition.get(type).resetWaitingAnimation();
+ mAnimationDefinition.set(
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+ mCustomizeActivityAnimation.getRunner());
+ }
+ }
+ BackAnimationRunner runner = mAnimationDefinition.get(type);
+ if (runner == null) {
+ Log.e(
+ TAG,
+ "Animation didn't be defined for type "
+ + BackNavigationInfo.typeToString(type));
+ }
+ return runner;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
index 9facbd5..b52a118 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
@@ -49,11 +49,11 @@
import java.util.Optional;
/**
- * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only
- * accessible from components within the WM subcomponent (can be explicitly exposed to the
- * SysUIComponent, see {@link WMComponent}).
+ * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only accessible
+ * from components within the WM subcomponent (can be explicitly exposed to the SysUIComponent, see
+ * {@link com.android.systemui.dagger.WMComponent}).
*
- * This module only defines Shell dependencies for the TV SystemUI implementation. Common
+ * <p>This module only defines Shell dependencies for the TV SystemUI implementation. Common
* dependencies should go into {@link WMShellBaseModule}.
*/
@Module(includes = {TvPipModule.class})
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 422e3b0..430fa95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -36,6 +36,7 @@
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.back.BackAnimationBackground;
import com.android.wm.shell.back.BackAnimationController;
+import com.android.wm.shell.back.ShellBackAnimationRegistry;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.common.DevicePostureController;
@@ -107,9 +108,9 @@
/**
* Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
* accessible from components within the WM subcomponent (can be explicitly exposed to the
- * SysUIComponent, see {@link WMComponent}).
+ * SysUIComponent, see {@link com.android.systemui.dagger.WMComponent}).
*
- * This module only defines *common* dependencies across various SystemUI implementations,
+ * <p>This module only defines *common* dependencies across various SystemUI implementations,
* dependencies that are device/form factor SystemUI implementation specific should go into their
* respective modules (ie. {@link WMShellModule} for handheld, {@link TvWMShellModule} for tv, etc.)
*/
@@ -303,16 +304,25 @@
ShellController shellController,
@ShellMainThread ShellExecutor shellExecutor,
@ShellBackgroundThread Handler backgroundHandler,
- BackAnimationBackground backAnimationBackground
- ) {
+ BackAnimationBackground backAnimationBackground,
+ Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry) {
if (BackAnimationController.IS_ENABLED) {
- return Optional.of(
- new BackAnimationController(shellInit, shellController, shellExecutor,
- backgroundHandler, context, backAnimationBackground));
+ return shellBackAnimationRegistry.map(
+ (animations) ->
+ new BackAnimationController(
+ shellInit,
+ shellController,
+ shellExecutor,
+ backgroundHandler,
+ context,
+ backAnimationBackground,
+ animations));
}
return Optional.empty();
}
+ @BindsOptionalOf
+ abstract ShellBackAnimationRegistry optionalBackAnimationRegistry();
//
// Bubbles (optional feature)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 881c8f5..36d2a70 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -52,6 +52,7 @@
import com.android.wm.shell.common.annotations.ShellAnimationThread;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
import com.android.wm.shell.dagger.pip.PipModule;
import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
@@ -100,17 +101,19 @@
import java.util.Optional;
/**
- * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only
- * accessible from components within the WM subcomponent (can be explicitly exposed to the
- * SysUIComponent, see {@link WMComponent}).
+ * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only accessible
+ * from components within the WM subcomponent (can be explicitly exposed to the SysUIComponent, see
+ * {@link WMComponent}).
*
- * This module only defines Shell dependencies for handheld SystemUI implementation. Common
+ * <p>This module only defines Shell dependencies for handheld SystemUI implementation. Common
* dependencies should go into {@link WMShellBaseModule}.
*/
-@Module(includes = {
- WMShellBaseModule.class,
- PipModule.class
-})
+@Module(
+ includes = {
+ WMShellBaseModule.class,
+ PipModule.class,
+ ShellBackAnimationModule.class,
+ })
public abstract class WMShellModule {
//
@@ -325,12 +328,13 @@
Optional<RecentTasksController> recentTasks,
LaunchAdjacentController launchAdjacentController,
Optional<WindowDecorViewModel> windowDecorViewModel,
+ Optional<DesktopTasksController> desktopTasksController,
@ShellMainThread ShellExecutor mainExecutor) {
return new SplitScreenController(context, shellInit, shellCommandHandler, shellController,
shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, displayController,
displayImeController, displayInsetsController, dragAndDropController, transitions,
transactionPool, iconProvider, recentTasks, launchAdjacentController,
- windowDecorViewModel, mainExecutor);
+ windowDecorViewModel, desktopTasksController, mainExecutor);
}
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java
new file mode 100644
index 0000000..b34c6b2
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java
@@ -0,0 +1,62 @@
+/*
+ * 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.dagger.back;
+
+import com.android.wm.shell.back.CrossActivityAnimation;
+import com.android.wm.shell.back.CrossTaskBackAnimation;
+import com.android.wm.shell.back.CustomizeActivityAnimation;
+import com.android.wm.shell.back.ShellBackAnimation;
+import com.android.wm.shell.back.ShellBackAnimationRegistry;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+
+/** Default animation definitions for predictive back. */
+@Module
+public interface ShellBackAnimationModule {
+ /** Default animation registry */
+ @Provides
+ static ShellBackAnimationRegistry provideBackAnimationRegistry(
+ @ShellBackAnimation.CrossActivity ShellBackAnimation crossActivity,
+ @ShellBackAnimation.CrossTask ShellBackAnimation crossTask,
+ @ShellBackAnimation.CustomizeActivity ShellBackAnimation customizeActivity) {
+ return new ShellBackAnimationRegistry(
+ crossActivity,
+ crossTask,
+ customizeActivity,
+ /* defaultBackToHomeAnimation= */ null);
+ }
+
+ /** Default cross activity back animation */
+ @Binds
+ @ShellBackAnimation.CrossActivity
+ ShellBackAnimation bindCrossActivityShellBackAnimation(
+ CrossActivityAnimation crossActivityAnimation);
+
+ /** Default cross task back animation */
+ @Binds
+ @ShellBackAnimation.CrossTask
+ ShellBackAnimation provideCrossTaskShellBackAnimation(
+ CrossTaskBackAnimation crossTaskBackAnimation);
+
+ /** Default customized activity back animation */
+ @Binds
+ @ShellBackAnimation.CustomizeActivity
+ ShellBackAnimation provideCustomizeActivityShellBackAnimation(
+ CustomizeActivityAnimation customizeActivityAnimation);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index db6c258..5b24d7a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -535,6 +535,11 @@
}
@Override
+ public void onDesktopSplitSelectAnimComplete(RunningTaskInfo taskInfo) {
+
+ }
+
+ @Override
public void stashDesktopApps(int displayId) throws RemoteException {
// Stashing of desktop apps not needed. Apps always launch on desktop
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index b15fd91..a4c086b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -22,6 +22,7 @@
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.app.WindowConfiguration.WindowingMode
import android.content.Context
@@ -33,7 +34,6 @@
import android.os.SystemProperties
import android.util.DisplayMetrics.DENSITY_DEFAULT
import android.view.SurfaceControl
-import android.view.SurfaceControl.Transaction
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_NONE
import android.view.WindowManager.TRANSIT_OPEN
@@ -56,6 +56,7 @@
import com.android.wm.shell.common.annotations.ShellMainThread
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
@@ -105,6 +106,9 @@
get() = context.resources.getDimensionPixelSize(
com.android.wm.shell.R.dimen.desktop_mode_transition_area_height)
+ // This is public to avoid cyclic dependency; it is set by SplitScreenController
+ lateinit var splitScreenController: SplitScreenController
+
init {
desktopMode = DesktopModeImpl()
if (DesktopModeStatus.isProto2Enabled()) {
@@ -262,6 +266,19 @@
}
}
+ /**
+ * Perform needed cleanup transaction once animation is complete. Bounds need to be set
+ * here instead of initial wct to both avoid flicker and to have task bounds to use for
+ * the staging animation.
+ *
+ * @param taskInfo task entering split that requires a bounds update
+ */
+ fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo) {
+ val wct = WindowContainerTransaction()
+ wct.setBounds(taskInfo.token, null)
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+
/** Move a task with given `taskId` to fullscreen */
fun moveToFullscreen(taskId: Int) {
shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToFullscreen(task) }
@@ -688,11 +705,41 @@
wct.setWindowingMode(taskInfo.token, targetWindowingMode)
wct.setBounds(taskInfo.token, null)
if (isDesktopDensityOverrideSet()) {
- wct.setDensityDpi(taskInfo.token, getFullscreenDensityDpi())
+ wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
}
}
- private fun getFullscreenDensityDpi(): Int {
+ /**
+ * Adds split screen changes to a transaction. Note that bounds are not reset here due to
+ * animation; see {@link onDesktopSplitSelectAnimComplete}
+ */
+ private fun addMoveToSplitChanges(
+ wct: WindowContainerTransaction,
+ taskInfo: RunningTaskInfo
+ ) {
+ wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_MULTI_WINDOW)
+ // The task's density may have been overridden in freeform; revert it here as we don't
+ // want it overridden in multi-window.
+ wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
+ }
+
+ /**
+ * Requests a task be transitioned from desktop to split select. Applies needed windowing
+ * changes if this transition is enabled.
+ */
+ fun requestSplit(
+ taskInfo: RunningTaskInfo
+ ) {
+ val windowingMode = taskInfo.windowingMode
+ if (windowingMode == WINDOWING_MODE_FULLSCREEN || windowingMode == WINDOWING_MODE_FREEFORM
+ ) {
+ val wct = WindowContainerTransaction()
+ addMoveToSplitChanges(wct, taskInfo)
+ splitScreenController.requestEnterSplitSelect(taskInfo, wct)
+ }
+ }
+
+ private fun getDefaultDensityDpi(): Int {
return context.resources.displayMetrics.densityDpi
}
@@ -969,6 +1016,13 @@
return result[0]
}
+ override fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo) {
+ ExecutorUtils.executeRemoteCallWithTaskPermission(
+ controller,
+ "onDesktopSplitSelectAnimComplete"
+ ) { c -> c.onDesktopSplitSelectAnimComplete(taskInfo) }
+ }
+
override fun setTaskListener(listener: IDesktopTaskListener?) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index ee3a080..47edfd4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -16,6 +16,7 @@
package com.android.wm.shell.desktopmode;
+import android.app.ActivityManager.RunningTaskInfo;
import com.android.wm.shell.desktopmode.IDesktopTaskListener;
/**
@@ -38,6 +39,9 @@
/** Get count of visible desktop tasks on the given display */
int getVisibleTaskCount(int displayId);
+ /** Perform cleanup transactions after the animation to split select is complete */
+ oneway void onDesktopSplitSelectAnimComplete(in RunningTaskInfo taskInfo);
+
/** Set listener that will receive callbacks about updates to desktop tasks */
oneway void setTaskListener(IDesktopTaskListener listener);
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index c414e70..14304a3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -27,6 +27,7 @@
import android.window.RemoteTransition;
import com.android.wm.shell.splitscreen.ISplitScreenListener;
+import com.android.wm.shell.splitscreen.ISplitSelectListener;
/**
* Interface that is exposed to remote callers to manipulate the splitscreen feature.
@@ -44,6 +45,16 @@
oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2;
/**
+ * Registers a split select listener.
+ */
+ oneway void registerSplitSelectListener(in ISplitSelectListener listener) = 20;
+
+ /**
+ * Unregisters a split select listener.
+ */
+ oneway void unregisterSplitSelectListener(in ISplitSelectListener listener) = 21;
+
+ /**
* Removes a task from the side stage.
*/
oneway void removeFromSideStage(int taskId) = 4;
@@ -148,4 +159,4 @@
*/
RemoteAnimationTarget[] onStartingSplitLegacy(in RemoteAnimationTarget[] appTargets) = 14;
}
-// Last id = 19
\ No newline at end of file
+// Last id = 21
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl
new file mode 100644
index 0000000..7171da5
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.wm.shell.splitscreen;
+
+import android.app.ActivityManager.RunningTaskInfo;
+
+/**
+ * Listener interface that Launcher attaches to SystemUI to get split-select callbacks.
+ */
+interface ISplitSelectListener {
+ /**
+ * Called when a task requests to enter split select
+ */
+ boolean onRequestSplitSelect(in RunningTaskInfo taskInfo);
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 2f2bc77..f20fe0b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.app.ActivityManager;
import android.graphics.Rect;
import com.android.wm.shell.common.annotations.ExternalThread;
@@ -63,6 +64,13 @@
default void onSplitVisibilityChanged(boolean visible) {}
}
+ /** Callback interface for listening to requests to enter split select */
+ interface SplitSelectListener {
+ default boolean onRequestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo) {
+ return false;
+ }
+ }
+
/** Registers listener that gets split screen callback. */
void registerSplitScreenListener(@NonNull SplitScreenListener listener,
@NonNull Executor executor);
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 5fa2654..210bf68 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
@@ -87,6 +87,7 @@
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.common.split.SplitScreenUtils;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -104,6 +105,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.Optional;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Class manages split-screen multitasking mode and implements the main interface
@@ -177,6 +179,7 @@
private final Optional<RecentTasksController> mRecentTasksOptional;
private final LaunchAdjacentController mLaunchAdjacentController;
private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
+ private final Optional<DesktopTasksController> mDesktopTasksController;
private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
private final String[] mAppsSupportMultiInstances;
@@ -205,6 +208,7 @@
Optional<RecentTasksController> recentTasks,
LaunchAdjacentController launchAdjacentController,
Optional<WindowDecorViewModel> windowDecorViewModel,
+ Optional<DesktopTasksController> desktopTasksController,
ShellExecutor mainExecutor) {
mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
@@ -223,6 +227,7 @@
mRecentTasksOptional = recentTasks;
mLaunchAdjacentController = launchAdjacentController;
mWindowDecorViewModel = windowDecorViewModel;
+ mDesktopTasksController = desktopTasksController;
mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
// TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
// override for this controller from the base module
@@ -254,6 +259,7 @@
RecentTasksController recentTasks,
LaunchAdjacentController launchAdjacentController,
WindowDecorViewModel windowDecorViewModel,
+ DesktopTasksController desktopTasksController,
ShellExecutor mainExecutor,
StageCoordinator stageCoordinator) {
mShellCommandHandler = shellCommandHandler;
@@ -273,6 +279,7 @@
mRecentTasksOptional = Optional.of(recentTasks);
mLaunchAdjacentController = launchAdjacentController;
mWindowDecorViewModel = Optional.of(windowDecorViewModel);
+ mDesktopTasksController = Optional.of(desktopTasksController);
mStageCoordinator = stageCoordinator;
mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
shellInit.addInitCallback(this::onInit, this);
@@ -306,6 +313,7 @@
}
mDragAndDropController.ifPresent(controller -> controller.setSplitScreenController(this));
mWindowDecorViewModel.ifPresent(viewModel -> viewModel.setSplitScreenController(this));
+ mDesktopTasksController.ifPresent(controller -> controller.setSplitScreenController(this));
}
protected StageCoordinator createStageCoordinator() {
@@ -468,6 +476,16 @@
mStageCoordinator.unregisterSplitScreenListener(listener);
}
+ /** Register a split select listener */
+ public void registerSplitSelectListener(SplitScreen.SplitSelectListener listener) {
+ mStageCoordinator.registerSplitSelectListener(listener);
+ }
+
+ /** Unregister a split select listener */
+ public void unregisterSplitSelectListener(SplitScreen.SplitSelectListener listener) {
+ mStageCoordinator.unregisterSplitSelectListener(listener);
+ }
+
public void goToFullscreenFromSplit() {
mStageCoordinator.goToFullscreenFromSplit();
}
@@ -485,6 +503,16 @@
return mStageCoordinator.getActivateSplitPosition(taskInfo);
}
+ /**
+ * Move a task to split select
+ * @param taskInfo the task being moved to split select
+ * @param wct transaction to apply if this is a valid request
+ */
+ public void requestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
+ WindowContainerTransaction wct) {
+ mStageCoordinator.requestEnterSplitSelect(taskInfo, wct);
+ }
+
public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
final int[] result = new int[1];
IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@@ -1088,6 +1116,8 @@
private SplitScreenController mController;
private final SingleInstanceRemoteListener<SplitScreenController,
ISplitScreenListener> mListener;
+ private final SingleInstanceRemoteListener<SplitScreenController,
+ ISplitSelectListener> mSelectListener;
private final SplitScreen.SplitScreenListener mSplitScreenListener =
new SplitScreen.SplitScreenListener() {
@Override
@@ -1101,11 +1131,25 @@
}
};
+ private final SplitScreen.SplitSelectListener mSplitSelectListener =
+ new SplitScreen.SplitSelectListener() {
+ @Override
+ public boolean onRequestEnterSplitSelect(
+ ActivityManager.RunningTaskInfo taskInfo) {
+ AtomicBoolean result = new AtomicBoolean(false);
+ mSelectListener.call(l -> result.set(l.onRequestSplitSelect(taskInfo)));
+ return result.get();
+ }
+ };
+
public ISplitScreenImpl(SplitScreenController controller) {
mController = controller;
mListener = new SingleInstanceRemoteListener<>(controller,
c -> c.registerSplitScreenListener(mSplitScreenListener),
c -> c.unregisterSplitScreenListener(mSplitScreenListener));
+ mSelectListener = new SingleInstanceRemoteListener<>(controller,
+ c -> c.registerSplitSelectListener(mSplitSelectListener),
+ c -> c.unregisterSplitSelectListener(mSplitSelectListener));
}
/**
@@ -1131,6 +1175,18 @@
}
@Override
+ public void registerSplitSelectListener(ISplitSelectListener listener) {
+ executeRemoteCallWithTaskPermission(mController, "registerSplitSelectListener",
+ (controller) -> mSelectListener.register(listener));
+ }
+
+ @Override
+ public void unregisterSplitSelectListener(ISplitSelectListener listener) {
+ executeRemoteCallWithTaskPermission(mController, "unregisterSplitSelectListener",
+ (controller) -> mSelectListener.unregister());
+ }
+
+ @Override
public void exitSplitScreen(int toTopTaskId) {
executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
(controller) -> controller.exitSplitScreen(toTopTaskId, EXIT_REASON_UNKNOWN));
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 99be5b8..7dec12a 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
@@ -324,8 +324,10 @@
void startFullscreenTransition(WindowContainerTransaction wct,
@Nullable RemoteTransition handler) {
- mTransitions.startTransition(TRANSIT_OPEN, wct,
- new OneShotRemoteHandler(mTransitions.getMainExecutor(), handler));
+ OneShotRemoteHandler fullscreenHandler =
+ new OneShotRemoteHandler(mTransitions.getMainExecutor(), handler);
+ fullscreenHandler.setTransition(mTransitions
+ .startTransition(TRANSIT_OPEN, wct, fullscreenHandler));
}
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 3758b68..6970068 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
@@ -144,8 +144,10 @@
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
/**
* Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
@@ -185,6 +187,7 @@
private final ShellTaskOrganizer mTaskOrganizer;
private final Context mContext;
private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
+ private final Set<SplitScreen.SplitSelectListener> mSelectListeners = new HashSet<>();
private final DisplayController mDisplayController;
private final DisplayImeController mDisplayImeController;
private final DisplayInsetsController mDisplayInsetsController;
@@ -462,6 +465,15 @@
return mLogger;
}
+ void requestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
+ WindowContainerTransaction wct) {
+ boolean enteredSplitSelect = false;
+ for (SplitScreen.SplitSelectListener listener : mSelectListeners) {
+ enteredSplitSelect |= listener.onRequestEnterSplitSelect(taskInfo);
+ }
+ if (enteredSplitSelect) mTaskOrganizer.applyTransaction(wct);
+ }
+
void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
Bundle options, UserHandle user) {
final boolean isEnteringSplit = !isSplitActive();
@@ -1657,6 +1669,14 @@
mListeners.remove(listener);
}
+ void registerSplitSelectListener(SplitScreen.SplitSelectListener listener) {
+ mSelectListeners.add(listener);
+ }
+
+ void unregisterSplitSelectListener(SplitScreen.SplitSelectListener listener) {
+ mSelectListeners.remove(listener);
+ }
+
void sendStatusToListener(SplitScreen.SplitScreenListener listener) {
listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
index a2301b1..c101425 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
@@ -87,7 +87,7 @@
syncQueue, rootTDAOrganizer, displayController, displayImeController,
displayInsetsController, dragAndDropController, transitions, transactionPool,
iconProvider, recentTasks, launchAdjacentController, Optional.empty(),
- mainExecutor);
+ Optional.empty(), mainExecutor);
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 2b19da2..2be7a49 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -365,6 +365,11 @@
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
decoration.closeHandleMenu();
+ } else if (id == R.id.split_screen_button) {
+ decoration.closeHandleMenu();
+ mDesktopTasksController.ifPresent(c -> {
+ c.requestSplit(decoration.mTaskInfo);
+ });
} else if (id == R.id.collapse_menu_button) {
decoration.closeHandleMenu();
} else if (id == R.id.select_button) {
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 de64f78..cb5a60d 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.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/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 3d8bd38..e7d0f60 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -67,6 +67,7 @@
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.sysui.ShellSharedConstants;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -85,12 +86,11 @@
private static final String ANIMATION_ENABLED = "1";
private final TestShellExecutor mShellExecutor = new TestShellExecutor();
- private ShellInit mShellInit;
-
@Rule
public TestableContext mContext =
new TestableContext(InstrumentationRegistry.getInstrumentation().getContext());
+ private ShellInit mShellInit;
@Mock
private IActivityTaskManager mActivityTaskManager;
@@ -116,6 +116,8 @@
private TestableContentResolver mContentResolver;
private TestableLooper mTestableLooper;
+ private ShellBackAnimationRegistry mShellBackAnimationRegistry;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -126,11 +128,23 @@
ANIMATION_ENABLED);
mTestableLooper = TestableLooper.get(this);
mShellInit = spy(new ShellInit(mShellExecutor));
- mController = new BackAnimationController(mShellInit, mShellController,
- mShellExecutor, new Handler(mTestableLooper.getLooper()),
- mActivityTaskManager, mContext,
- mContentResolver, mAnimationBackground);
- mController.setEnableUAnimation(true);
+ mShellBackAnimationRegistry =
+ new ShellBackAnimationRegistry(
+ new CrossActivityAnimation(mContext, mAnimationBackground),
+ new CrossTaskBackAnimation(mContext, mAnimationBackground),
+ new CustomizeActivityAnimation(mContext, mAnimationBackground),
+ null);
+ mController =
+ new BackAnimationController(
+ mShellInit,
+ mShellController,
+ mShellExecutor,
+ new Handler(mTestableLooper.getLooper()),
+ mActivityTaskManager,
+ mContext,
+ mContentResolver,
+ mAnimationBackground,
+ mShellBackAnimationRegistry);
mShellInit.init();
mShellExecutor.flushAll();
}
@@ -138,12 +152,13 @@
private void createNavigationInfo(int backType,
boolean enableAnimation,
boolean isAnimationCallback) {
- BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder()
- .setType(backType)
- .setOnBackNavigationDone(new RemoteCallback((bundle) -> {}))
- .setOnBackInvokedCallback(mAppCallback)
- .setPrepareRemoteAnimation(enableAnimation)
- .setAnimationCallback(isAnimationCallback);
+ BackNavigationInfo.Builder builder =
+ new BackNavigationInfo.Builder()
+ .setType(backType)
+ .setOnBackNavigationDone(new RemoteCallback((bundle) -> {}))
+ .setOnBackInvokedCallback(mAppCallback)
+ .setPrepareRemoteAnimation(enableAnimation)
+ .setAnimationCallback(isAnimationCallback);
createNavigationInfo(builder);
}
@@ -188,18 +203,21 @@
@Test
public void verifyNavigationFinishes() throws RemoteException {
- final int[] testTypes = new int[] {BackNavigationInfo.TYPE_RETURN_TO_HOME,
- BackNavigationInfo.TYPE_CROSS_TASK,
- BackNavigationInfo.TYPE_CROSS_ACTIVITY,
- BackNavigationInfo.TYPE_DIALOG_CLOSE,
- BackNavigationInfo.TYPE_CALLBACK };
+ final int[] testTypes =
+ new int[] {
+ BackNavigationInfo.TYPE_RETURN_TO_HOME,
+ BackNavigationInfo.TYPE_CROSS_TASK,
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+ BackNavigationInfo.TYPE_DIALOG_CLOSE,
+ BackNavigationInfo.TYPE_CALLBACK
+ };
- for (int type: testTypes) {
+ for (int type : testTypes) {
registerAnimation(type);
}
- for (int type: testTypes) {
- final ResultListener result = new ResultListener();
+ for (int type : testTypes) {
+ final ResultListener result = new ResultListener();
createNavigationInfo(new BackNavigationInfo.Builder()
.setType(type)
.setOnBackInvokedCallback(mAppCallback)
@@ -275,10 +293,17 @@
// Toggle the setting off
Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0");
ShellInit shellInit = new ShellInit(mShellExecutor);
- mController = new BackAnimationController(shellInit, mShellController,
- mShellExecutor, new Handler(mTestableLooper.getLooper()),
- mActivityTaskManager, mContext,
- mContentResolver, mAnimationBackground);
+ mController =
+ new BackAnimationController(
+ shellInit,
+ mShellController,
+ mShellExecutor,
+ new Handler(mTestableLooper.getLooper()),
+ mActivityTaskManager,
+ mContext,
+ mContentResolver,
+ mAnimationBackground,
+ mShellBackAnimationRegistry);
shellInit.init();
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
@@ -398,17 +423,19 @@
@Test
public void animationNotDefined() throws RemoteException {
- final int[] testTypes = new int[] {
- BackNavigationInfo.TYPE_RETURN_TO_HOME,
- BackNavigationInfo.TYPE_CROSS_TASK,
- BackNavigationInfo.TYPE_CROSS_ACTIVITY,
- BackNavigationInfo.TYPE_DIALOG_CLOSE};
+ final int[] testTypes =
+ new int[] {
+ BackNavigationInfo.TYPE_RETURN_TO_HOME,
+ BackNavigationInfo.TYPE_CROSS_TASK,
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+ BackNavigationInfo.TYPE_DIALOG_CLOSE
+ };
- for (int type: testTypes) {
+ for (int type : testTypes) {
unregisterAnimation(type);
}
- for (int type: testTypes) {
+ for (int type : testTypes) {
final ResultListener result = new ResultListener();
createNavigationInfo(new BackNavigationInfo.Builder()
.setType(type)
@@ -468,16 +495,14 @@
public void testBackToActivity() throws RemoteException {
final CrossActivityAnimation animation = new CrossActivityAnimation(mContext,
mAnimationBackground);
- verifySystemBackBehavior(
- BackNavigationInfo.TYPE_CROSS_ACTIVITY, animation.mBackAnimationRunner);
+ verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_ACTIVITY, animation.getRunner());
}
@Test
public void testBackToTask() throws RemoteException {
final CrossTaskBackAnimation animation = new CrossTaskBackAnimation(mContext,
mAnimationBackground);
- verifySystemBackBehavior(
- BackNavigationInfo.TYPE_CROSS_TASK, animation.mBackAnimationRunner);
+ verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_TASK, animation.getRunner());
}
private void verifySystemBackBehavior(int type, BackAnimationRunner animation)
@@ -554,10 +579,12 @@
private static class ResultListener implements RemoteCallback.OnResultListener {
boolean mBackNavigationDone = false;
boolean mTriggerBack = false;
+
@Override
public void onResult(@Nullable Bundle result) {
mBackNavigationDone = true;
mTriggerBack = result.getBoolean(KEY_TRIGGER_BACK);
}
- };
+ }
+ ;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
index e7d4598..cebbbd8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
@@ -102,15 +102,17 @@
// start animation with remote animation targets
final CountDownLatch finishCalled = new CountDownLatch(1);
final Runnable finishCallback = finishCalled::countDown;
- mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
- new RemoteAnimationTarget[]{close, open}, null, null, finishCallback);
+ mCustomizeActivityAnimation
+ .getRunner()
+ .startAnimation(
+ new RemoteAnimationTarget[] {close, open}, null, null, finishCallback);
verify(mMockCloseAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
eq(BOUND_SIZE), eq(BOUND_SIZE));
verify(mMockOpenAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
eq(BOUND_SIZE), eq(BOUND_SIZE));
try {
- mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackInvoked();
+ mCustomizeActivityAnimation.getRunner().getCallback().onBackInvoked();
} catch (RemoteException r) {
fail("onBackInvoked throw remote exception");
}
@@ -133,15 +135,17 @@
// start animation with remote animation targets
final CountDownLatch finishCalled = new CountDownLatch(1);
final Runnable finishCallback = finishCalled::countDown;
- mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
- new RemoteAnimationTarget[]{close, open}, null, null, finishCallback);
+ mCustomizeActivityAnimation
+ .getRunner()
+ .startAnimation(
+ new RemoteAnimationTarget[] {close, open}, null, null, finishCallback);
verify(mMockCloseAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
eq(BOUND_SIZE), eq(BOUND_SIZE));
verify(mMockOpenAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
eq(BOUND_SIZE), eq(BOUND_SIZE));
try {
- mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackCancelled();
+ mCustomizeActivityAnimation.getRunner().getCallback().onBackCancelled();
} catch (RemoteException r) {
fail("onBackCancelled throw remote exception");
}
@@ -155,11 +159,12 @@
// start animation without any remote animation targets
final CountDownLatch finishCalled = new CountDownLatch(1);
final Runnable finishCallback = finishCalled::countDown;
- mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
- new RemoteAnimationTarget[]{}, null, null, finishCallback);
+ mCustomizeActivityAnimation
+ .getRunner()
+ .startAnimation(new RemoteAnimationTarget[] {}, null, null, finishCallback);
try {
- mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackInvoked();
+ mCustomizeActivityAnimation.getRunner().getCallback().onBackInvoked();
} catch (RemoteException r) {
fail("onBackInvoked throw remote exception");
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index e8a1e91..568db91 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -65,6 +65,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -106,6 +107,7 @@
@Mock RecentTasksController mRecentTasks;
@Mock LaunchAdjacentController mLaunchAdjacentController;
@Mock WindowDecorViewModel mWindowDecorViewModel;
+ @Mock DesktopTasksController mDesktopTasksController;
@Captor ArgumentCaptor<Intent> mIntentCaptor;
private ShellController mShellController;
@@ -122,7 +124,7 @@
mRootTDAOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
- mMainExecutor, mStageCoordinator));
+ mDesktopTasksController, mMainExecutor, mStageCoordinator));
}
@Test
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 5ffec34..b1ef4e5 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -91,17 +91,12 @@
StringPoolRef entry_string_ref;
};
-AssetManager2::AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration) {
- configurations_.push_back(configuration);
-
+AssetManager2::AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration)
+ : configuration_(configuration) {
// Don't invalidate caches here as there's nothing cached yet.
SetApkAssets(apk_assets, false);
}
-AssetManager2::AssetManager2() {
- configurations_.resize(1);
-}
-
bool AssetManager2::SetApkAssets(ApkAssetsList apk_assets, bool invalidate_caches) {
BuildDynamicRefTable(apk_assets);
RebuildFilterList();
@@ -426,16 +421,9 @@
return false;
}
-void AssetManager2::SetConfigurations(std::vector<ResTable_config> configurations) {
- int diff = 0;
- if (configurations_.size() != configurations.size()) {
- diff = -1;
- } else {
- for (int i = 0; i < configurations_.size(); i++) {
- diff |= configurations_[i].diff(configurations[i]);
- }
- }
- configurations_ = std::move(configurations);
+void AssetManager2::SetConfiguration(const ResTable_config& configuration) {
+ const int diff = configuration_.diff(configuration);
+ configuration_ = configuration;
if (diff) {
RebuildFilterList();
@@ -632,6 +620,16 @@
auto op = StartOperation();
+ // Might use this if density_override != 0.
+ ResTable_config density_override_config;
+
+ // Select our configuration or generate a density override configuration.
+ const ResTable_config* desired_config = &configuration_;
+ if (density_override != 0 && density_override != configuration_.density) {
+ density_override_config = configuration_;
+ density_override_config.density = density_override;
+ desired_config = &density_override_config;
+ }
// Retrieve the package group from the package id of the resource id.
if (UNLIKELY(!is_valid_resid(resid))) {
@@ -650,160 +648,119 @@
}
const PackageGroup& package_group = package_groups_[package_idx];
- std::optional<FindEntryResult> final_result;
- bool final_has_locale = false;
- bool final_overlaid = false;
- for (auto & config : configurations_) {
- // Might use this if density_override != 0.
- ResTable_config density_override_config;
+ auto result = FindEntryInternal(package_group, type_idx, entry_idx, *desired_config,
+ stop_at_first_match, ignore_configuration);
+ if (UNLIKELY(!result.has_value())) {
+ return base::unexpected(result.error());
+ }
- // Select our configuration or generate a density override configuration.
- const ResTable_config* desired_config = &config;
- if (density_override != 0 && density_override != config.density) {
- density_override_config = config;
- density_override_config.density = density_override;
- desired_config = &density_override_config;
+ bool overlaid = false;
+ if (!stop_at_first_match && !ignore_configuration) {
+ const auto& assets = GetApkAssets(result->cookie);
+ if (!assets) {
+ ALOGE("Found expired ApkAssets #%d for resource ID 0x%08x.", result->cookie, resid);
+ return base::unexpected(std::nullopt);
}
-
- auto result = FindEntryInternal(package_group, type_idx, entry_idx, *desired_config,
- stop_at_first_match, ignore_configuration);
- if (UNLIKELY(!result.has_value())) {
- return base::unexpected(result.error());
- }
- bool overlaid = false;
- if (!stop_at_first_match && !ignore_configuration) {
- const auto& assets = GetApkAssets(result->cookie);
- if (!assets) {
- ALOGE("Found expired ApkAssets #%d for resource ID 0x%08x.", result->cookie, resid);
- return base::unexpected(std::nullopt);
- }
- if (!assets->IsLoader()) {
- for (const auto& id_map : package_group.overlays_) {
- auto overlay_entry = id_map.overlay_res_maps_.Lookup(resid);
- if (!overlay_entry) {
- // No id map entry exists for this target resource.
- continue;
- }
- if (overlay_entry.IsInlineValue()) {
- // The target resource is overlaid by an inline value not represented by a resource.
- ConfigDescription best_frro_config;
- Res_value best_frro_value;
- bool frro_found = false;
- for( const auto& [config, value] : overlay_entry.GetInlineValue()) {
- if ((!frro_found || config.isBetterThan(best_frro_config, desired_config))
- && config.match(*desired_config)) {
- frro_found = true;
- best_frro_config = config;
- best_frro_value = value;
- }
+ if (!assets->IsLoader()) {
+ for (const auto& id_map : package_group.overlays_) {
+ auto overlay_entry = id_map.overlay_res_maps_.Lookup(resid);
+ if (!overlay_entry) {
+ // No id map entry exists for this target resource.
+ continue;
+ }
+ if (overlay_entry.IsInlineValue()) {
+ // The target resource is overlaid by an inline value not represented by a resource.
+ ConfigDescription best_frro_config;
+ Res_value best_frro_value;
+ bool frro_found = false;
+ for( const auto& [config, value] : overlay_entry.GetInlineValue()) {
+ if ((!frro_found || config.isBetterThan(best_frro_config, desired_config))
+ && config.match(*desired_config)) {
+ frro_found = true;
+ best_frro_config = config;
+ best_frro_value = value;
}
- if (!frro_found) {
- continue;
- }
- result->entry = best_frro_value;
- result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
- result->cookie = id_map.cookie;
-
- if (UNLIKELY(logging_enabled)) {
- last_resolution_.steps.push_back(Resolution::Step{
- Resolution::Step::Type::OVERLAID_INLINE, result->cookie, String8()});
- if (auto path = assets->GetPath()) {
- const std::string overlay_path = path->data();
- if (IsFabricatedOverlay(overlay_path)) {
- // FRRO don't have package name so we use the creating package here.
- String8 frro_name = String8("FRRO");
- // Get the first part of it since the expected one should be like
- // {overlayPackageName}-{overlayName}-{4 alphanumeric chars}.frro
- // under /data/resource-cache/.
- const std::string name = overlay_path.substr(overlay_path.rfind('/') + 1);
- const size_t end = name.find('-');
- if (frro_name.size() != overlay_path.size() && end != std::string::npos) {
- frro_name.append(base::StringPrintf(" created by %s",
- name.substr(0 /* pos */,
- end).c_str()).c_str());
- }
- last_resolution_.best_package_name = frro_name;
- } else {
- last_resolution_.best_package_name = result->package_name->c_str();
- }
- }
- overlaid = true;
- }
+ }
+ if (!frro_found) {
continue;
}
-
- auto overlay_result = FindEntry(overlay_entry.GetResourceId(), density_override,
- false /* stop_at_first_match */,
- false /* ignore_configuration */);
- if (UNLIKELY(IsIOError(overlay_result))) {
- return base::unexpected(overlay_result.error());
- }
- if (!overlay_result.has_value()) {
- continue;
- }
-
- if (!overlay_result->config.isBetterThan(result->config, desired_config)
- && overlay_result->config.compare(result->config) != 0) {
- // The configuration of the entry for the overlay must be equal to or better than the
- // target configuration to be chosen as the better value.
- continue;
- }
-
- result->cookie = overlay_result->cookie;
- result->entry = overlay_result->entry;
- result->config = overlay_result->config;
+ result->entry = best_frro_value;
result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
+ result->cookie = id_map.cookie;
if (UNLIKELY(logging_enabled)) {
last_resolution_.steps.push_back(
- Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result->cookie,
- overlay_result->config.toString()});
- last_resolution_.best_package_name =
- overlay_result->package_name->c_str();
+ Resolution::Step{Resolution::Step::Type::OVERLAID_INLINE, result->cookie, String8()});
+ if (auto path = assets->GetPath()) {
+ const std::string overlay_path = path->data();
+ if (IsFabricatedOverlay(overlay_path)) {
+ // FRRO don't have package name so we use the creating package here.
+ String8 frro_name = String8("FRRO");
+ // Get the first part of it since the expected one should be like
+ // {overlayPackageName}-{overlayName}-{4 alphanumeric chars}.frro
+ // under /data/resource-cache/.
+ const std::string name = overlay_path.substr(overlay_path.rfind('/') + 1);
+ const size_t end = name.find('-');
+ if (frro_name.size() != overlay_path.size() && end != std::string::npos) {
+ frro_name.append(base::StringPrintf(" created by %s",
+ name.substr(0 /* pos */,
+ end).c_str()).c_str());
+ }
+ last_resolution_.best_package_name = frro_name;
+ } else {
+ last_resolution_.best_package_name = result->package_name->c_str();
+ }
+ }
overlaid = true;
}
+ continue;
+ }
+
+ auto overlay_result = FindEntry(overlay_entry.GetResourceId(), density_override,
+ false /* stop_at_first_match */,
+ false /* ignore_configuration */);
+ if (UNLIKELY(IsIOError(overlay_result))) {
+ return base::unexpected(overlay_result.error());
+ }
+ if (!overlay_result.has_value()) {
+ continue;
+ }
+
+ if (!overlay_result->config.isBetterThan(result->config, desired_config)
+ && overlay_result->config.compare(result->config) != 0) {
+ // The configuration of the entry for the overlay must be equal to or better than the target
+ // configuration to be chosen as the better value.
+ continue;
+ }
+
+ result->cookie = overlay_result->cookie;
+ result->entry = overlay_result->entry;
+ result->config = overlay_result->config;
+ result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
+
+ if (UNLIKELY(logging_enabled)) {
+ last_resolution_.steps.push_back(
+ Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result->cookie,
+ overlay_result->config.toString()});
+ last_resolution_.best_package_name =
+ overlay_result->package_name->c_str();
+ overlaid = true;
}
}
}
-
- bool has_locale = false;
- if (result->config.locale == 0) {
- if (default_locale_ != 0) {
- ResTable_config conf;
- conf.locale = default_locale_;
- // Since we know conf has a locale and only a locale, match will tell us if that locale
- // matches
- has_locale = conf.match(config);
- }
- } else {
- has_locale = true;
- }
-
- // if we don't have a result yet
- if (!final_result ||
- // or this config is better before the locale than the existing result
- result->config.isBetterThanBeforeLocale(final_result->config, desired_config) ||
- // or the existing config isn't better before locale and this one specifies a locale
- // whereas the existing one doesn't
- (!final_result->config.isBetterThanBeforeLocale(result->config, desired_config)
- && has_locale && !final_has_locale)) {
- final_result = result.value();
- final_overlaid = overlaid;
- final_has_locale = has_locale;
- }
}
if (UNLIKELY(logging_enabled)) {
- last_resolution_.cookie = final_result->cookie;
- last_resolution_.type_string_ref = final_result->type_string_ref;
- last_resolution_.entry_string_ref = final_result->entry_string_ref;
- last_resolution_.best_config_name = final_result->config.toString();
- if (!final_overlaid) {
- last_resolution_.best_package_name = final_result->package_name->c_str();
+ last_resolution_.cookie = result->cookie;
+ last_resolution_.type_string_ref = result->type_string_ref;
+ last_resolution_.entry_string_ref = result->entry_string_ref;
+ last_resolution_.best_config_name = result->config.toString();
+ if (!overlaid) {
+ last_resolution_.best_package_name = result->package_name->c_str();
}
}
- return *final_result;
+ return result;
}
base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
@@ -821,10 +778,8 @@
// If `desired_config` is not the same as the set configuration or the caller will accept a value
// from any configuration, then we cannot use our filtered list of types since it only it contains
// types matched to the set configuration.
- const bool use_filtered = !ignore_configuration && std::find_if(
- configurations_.begin(), configurations_.end(),
- [&desired_config](auto& value) { return &desired_config == &value; })
- != configurations_.end();
+ const bool use_filtered = !ignore_configuration && &desired_config == &configuration_;
+
const size_t package_count = package_group.packages_.size();
for (size_t pi = 0; pi < package_count; pi++) {
const ConfiguredPackage& loaded_package_impl = package_group.packages_[pi];
@@ -979,22 +934,10 @@
}
std::stringstream log_stream;
- if (configurations_.size() == 1) {
- log_stream << base::StringPrintf("Resolution for 0x%08x %s\n"
- "\tFor config - %s", resid, resource_name_string.c_str(),
- configurations_[0].toString().c_str());
- } else {
- ResTable_config conf = configurations_[0];
- conf.clearLocale();
- log_stream << base::StringPrintf("Resolution for 0x%08x %s\n\tFor config - %s and locales",
- resid, resource_name_string.c_str(), conf.toString().c_str());
- char str[40];
- str[0] = '\0';
- for(auto iter = configurations_.begin(); iter < configurations_.end(); iter++) {
- iter->getBcp47Locale(str);
- log_stream << base::StringPrintf(" %s%s", str, iter < configurations_.end() ? "," : "");
- }
- }
+ log_stream << base::StringPrintf("Resolution for 0x%08x %s\n"
+ "\tFor config - %s", resid, resource_name_string.c_str(),
+ configuration_.toString().c_str());
+
for (const Resolution::Step& step : last_resolution_.steps) {
constexpr static std::array kStepStrings = {
"Found initial",
@@ -1484,14 +1427,11 @@
package.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) {
FilteredConfigGroup* group = nullptr;
for (const auto& type_entry : type_spec.type_entries) {
- for (auto & config : configurations_) {
- if (type_entry.config.match(config)) {
- if (!group) {
- group = &package.filtered_configs_.editItemAt(type_id - 1);
- }
- group->type_entries.push_back(&type_entry);
- break;
+ if (type_entry.config.match(configuration_)) {
+ if (!group) {
+ group = &package.filtered_configs_.editItemAt(type_id - 1);
}
+ group->type_entries.push_back(&type_entry);
}
}
});
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 06d19e0..5a63612 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -2568,22 +2568,6 @@
return false;
}
-bool ResTable_config::isBetterThanBeforeLocale(const ResTable_config& o,
- const ResTable_config* requested) const {
- if (requested) {
- if (imsi || o.imsi) {
- if ((mcc != o.mcc) && requested->mcc) {
- return (mcc);
- }
-
- if ((mnc != o.mnc) && requested->mnc) {
- return (mnc);
- }
- }
- }
- return false;
-}
-
bool ResTable_config::isBetterThan(const ResTable_config& o,
const ResTable_config* requested) const {
if (requested) {
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index d9ff35b..f611d0d 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -100,7 +100,7 @@
using ApkAssetsWPtr = wp<const ApkAssets>;
using ApkAssetsList = std::span<const ApkAssetsPtr>;
- AssetManager2();
+ AssetManager2() = default;
explicit AssetManager2(AssetManager2&& other) = default;
AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration);
@@ -156,14 +156,10 @@
// Sets/resets the configuration for this AssetManager. This will cause all
// caches that are related to the configuration change to be invalidated.
- void SetConfigurations(std::vector<ResTable_config> configurations);
+ void SetConfiguration(const ResTable_config& configuration);
- inline const std::vector<ResTable_config>& GetConfigurations() const {
- return configurations_;
- }
-
- inline void SetDefaultLocale(uint32_t default_locale) {
- default_locale_ = default_locale;
+ inline const ResTable_config& GetConfiguration() const {
+ return configuration_;
}
// Returns all configurations for which there are resources defined, or an I/O error if reading
@@ -469,11 +465,9 @@
// without taking too much memory.
std::array<uint8_t, std::numeric_limits<uint8_t>::max() + 1> package_ids_;
- uint32_t default_locale_;
-
- // The current configurations set for this AssetManager. When this changes, cached resources
+ // The current configuration set for this AssetManager. When this changes, cached resources
// may need to be purged.
- std::vector<ResTable_config> configurations_;
+ ResTable_config configuration_ = {};
// Cached set of bags. These are cached because they can inherit keys from parent bags,
// which involves some calculation.
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 52666ab..6de1d1e 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1375,8 +1375,6 @@
// match the requested configuration at all.
bool isLocaleBetterThan(const ResTable_config& o, const ResTable_config* requested) const;
- bool isBetterThanBeforeLocale(const ResTable_config& o, const ResTable_config* requested) const;
-
String8 toString() const;
};
@@ -1872,6 +1870,8 @@
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/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp
index 2caa98c..6fae72a 100644
--- a/libs/androidfw/tests/AssetManager2_bench.cpp
+++ b/libs/androidfw/tests/AssetManager2_bench.cpp
@@ -228,12 +228,10 @@
ResTable_config config;
memset(&config, 0, sizeof(config));
- std::vector<ResTable_config> configs;
- configs.push_back(config);
while (state.KeepRunning()) {
- configs[0].sdkVersion = ~configs[0].sdkVersion;
- assets.SetConfigurations(configs);
+ config.sdkVersion = ~config.sdkVersion;
+ assets.SetConfiguration(config);
}
}
BENCHMARK(BM_AssetManagerSetConfigurationFramework);
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index c62f095..df3fa02 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -113,7 +113,7 @@
desired_config.language[1] = 'e';
AssetManager2 assetmanager;
- assetmanager.SetConfigurations({desired_config});
+ assetmanager.SetConfiguration(desired_config);
assetmanager.SetApkAssets({basic_assets_});
auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -137,7 +137,7 @@
desired_config.language[1] = 'e';
AssetManager2 assetmanager;
- assetmanager.SetConfigurations({desired_config});
+ assetmanager.SetConfiguration(desired_config);
assetmanager.SetApkAssets({basic_assets_, basic_de_fr_assets_});
auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -466,10 +466,10 @@
TEST_F(AssetManager2Test, DensityOverride) {
AssetManager2 assetmanager;
assetmanager.SetApkAssets({basic_assets_, basic_xhdpi_assets_, basic_xxhdpi_assets_});
- assetmanager.SetConfigurations({{
+ assetmanager.SetConfiguration({
.density = ResTable_config::DENSITY_XHIGH,
.sdkVersion = 21,
- }});
+ });
auto value = assetmanager.GetResource(basic::R::string::density, false /*may_be_bag*/);
ASSERT_TRUE(value.has_value());
@@ -721,7 +721,7 @@
ResTable_config desired_config;
AssetManager2 assetmanager;
- assetmanager.SetConfigurations({desired_config});
+ assetmanager.SetConfiguration(desired_config);
assetmanager.SetApkAssets({basic_assets_});
assetmanager.SetResourceResolutionLoggingEnabled(false);
@@ -736,7 +736,7 @@
ResTable_config desired_config;
AssetManager2 assetmanager;
- assetmanager.SetConfigurations({desired_config});
+ assetmanager.SetConfiguration(desired_config);
assetmanager.SetApkAssets({basic_assets_});
auto result = assetmanager.GetLastResourceResolution();
@@ -751,7 +751,7 @@
AssetManager2 assetmanager;
assetmanager.SetResourceResolutionLoggingEnabled(true);
- assetmanager.SetConfigurations({desired_config});
+ assetmanager.SetConfiguration(desired_config);
assetmanager.SetApkAssets({basic_assets_});
auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -774,7 +774,7 @@
AssetManager2 assetmanager;
assetmanager.SetResourceResolutionLoggingEnabled(true);
- assetmanager.SetConfigurations({desired_config});
+ assetmanager.SetConfiguration(desired_config);
assetmanager.SetApkAssets({basic_assets_, basic_de_fr_assets_});
auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -796,7 +796,7 @@
AssetManager2 assetmanager;
assetmanager.SetResourceResolutionLoggingEnabled(true);
- assetmanager.SetConfigurations({desired_config});
+ assetmanager.SetConfiguration(desired_config);
assetmanager.SetApkAssets({basic_assets_});
auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -817,7 +817,7 @@
AssetManager2 assetmanager;
assetmanager.SetResourceResolutionLoggingEnabled(true);
- assetmanager.SetConfigurations({desired_config});
+ assetmanager.SetConfiguration(desired_config);
assetmanager.SetApkAssets({overlayable_assets_});
const auto map = assetmanager.GetOverlayableMapForPackage(0x7f);
diff --git a/libs/androidfw/tests/BenchmarkHelpers.cpp b/libs/androidfw/tests/BenchmarkHelpers.cpp
index 8b883f4..b97dd96 100644
--- a/libs/androidfw/tests/BenchmarkHelpers.cpp
+++ b/libs/androidfw/tests/BenchmarkHelpers.cpp
@@ -66,7 +66,7 @@
AssetManager2 assetmanager;
assetmanager.SetApkAssets(apk_assets);
if (config != nullptr) {
- assetmanager.SetConfigurations({*config});
+ assetmanager.SetConfiguration(*config);
}
while (state.KeepRunning()) {
diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp
index 181d141..e08a6a7 100644
--- a/libs/androidfw/tests/Theme_test.cpp
+++ b/libs/androidfw/tests/Theme_test.cpp
@@ -260,7 +260,7 @@
ResTable_config night{};
night.uiMode = ResTable_config::UI_MODE_NIGHT_YES;
night.version = 8u;
- am_night.SetConfigurations({night});
+ am_night.SetConfiguration(night);
auto theme = am.NewTheme();
{
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index b5e6f94..7f80dff 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -44,7 +44,7 @@
"-DEGL_EGLEXT_PROTOTYPES",
"-DGL_GLEXT_PROTOTYPES",
"-DATRACE_TAG=ATRACE_TAG_VIEW",
- "-DLOG_TAG=\"OpenGLRenderer\"",
+ "-DLOG_TAG=\"HWUI\"",
"-Wall",
"-Wthread-safety",
"-Wno-unused-parameter",
diff --git a/libs/hwui/apex/android_bitmap.cpp b/libs/hwui/apex/android_bitmap.cpp
index c442a7b..c80a9b4 100644
--- a/libs/hwui/apex/android_bitmap.cpp
+++ b/libs/hwui/apex/android_bitmap.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "Bitmap"
#include <log/log.h>
#include "android/graphics/bitmap.h"
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index 09ae7e7..883f273 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -25,9 +25,6 @@
#include <sys/cdefs.h>
#include <vulkan/vulkan.h>
-#undef LOG_TAG
-#define LOG_TAG "AndroidGraphicsJNI"
-
extern int register_android_graphics_Bitmap(JNIEnv*);
extern int register_android_graphics_BitmapFactory(JNIEnv*);
extern int register_android_graphics_BitmapRegionDecoder(JNIEnv*);
diff --git a/libs/hwui/effects/GainmapRenderer.cpp b/libs/hwui/effects/GainmapRenderer.cpp
index 613f52b..db58b2b 100644
--- a/libs/hwui/effects/GainmapRenderer.cpp
+++ b/libs/hwui/effects/GainmapRenderer.cpp
@@ -245,11 +245,18 @@
// This can happen if a BitmapShader is used on multiple canvas', such as a
// software + hardware canvas, which is otherwise valid as SkShader is "immutable"
std::lock_guard _lock(mUniformGuard);
- const float Wunclamped = (sk_float_log(targetHdrSdrRatio) -
- sk_float_log(mGainmapInfo.fDisplayRatioSdr)) /
- (sk_float_log(mGainmapInfo.fDisplayRatioHdr) -
- sk_float_log(mGainmapInfo.fDisplayRatioSdr));
- const float W = std::max(std::min(Wunclamped, 1.f), 0.f);
+ // Compute the weight parameter that will be used to blend between the images.
+ float W = 0.f;
+ if (targetHdrSdrRatio > mGainmapInfo.fDisplayRatioSdr) {
+ if (targetHdrSdrRatio < mGainmapInfo.fDisplayRatioHdr) {
+ W = (sk_float_log(targetHdrSdrRatio) -
+ sk_float_log(mGainmapInfo.fDisplayRatioSdr)) /
+ (sk_float_log(mGainmapInfo.fDisplayRatioHdr) -
+ sk_float_log(mGainmapInfo.fDisplayRatioSdr));
+ } else {
+ W = 1.f;
+ }
+ }
mBuilder.uniform("W") = W;
uniforms = mBuilder.uniforms();
}
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index 701a87f..588463c 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -43,9 +43,6 @@
#include <memory>
-#undef LOG_TAG
-#define LOG_TAG "ImageDecoder"
-
using namespace android;
sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const {
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index 6ee7576..9e21f86 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -1,5 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "Bitmap"
// #define LOG_NDEBUG 0
#include "Bitmap.h"
diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index 8abcd9a..3d0a534 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "BitmapFactory"
-
#include "BitmapFactory.h"
#include <Gainmap.h>
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index 740988f..ea5c144 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "BitmapRegionDecoder"
-
#include "BitmapRegionDecoder.h"
#include <HardwareBitmapUploader.h>
diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp
index af1668f..0c3af61 100644
--- a/libs/hwui/jni/FontFamily.cpp
+++ b/libs/hwui/jni/FontFamily.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "Minikin"
-
#include <nativehelper/ScopedPrimitiveArray.h>
#include <nativehelper/ScopedUtfChars.h>
#include "FontUtils.h"
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index 78b4f7b..7cc4866 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "GraphicsJNI"
-
#include <assert.h>
#include <unistd.h>
diff --git a/libs/hwui/jni/GraphicsStatsService.cpp b/libs/hwui/jni/GraphicsStatsService.cpp
index e32c911..54369b9 100644
--- a/libs/hwui/jni/GraphicsStatsService.cpp
+++ b/libs/hwui/jni/GraphicsStatsService.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "GraphicsStatsService"
-
#include <JankTracker.h>
#include <log/log.h>
#include <nativehelper/ScopedPrimitiveArray.h>
diff --git a/libs/hwui/jni/NinePatch.cpp b/libs/hwui/jni/NinePatch.cpp
index d50a8a2..67ef143 100644
--- a/libs/hwui/jni/NinePatch.cpp
+++ b/libs/hwui/jni/NinePatch.cpp
@@ -15,8 +15,6 @@
** limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "9patch"
#define LOG_NDEBUG 1
#include <androidfw/ResourceTypes.h>
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index d2a4efe..1ba7f70 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -15,9 +15,6 @@
** limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "Paint"
-
#include <hwui/BlurDrawLooper.h>
#include <hwui/MinikinSkia.h>
#include <hwui/MinikinUtils.h>
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 7eb79be..2c13ceb 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "ShaderJNI"
-
#include <vector>
#include "Gainmap.h"
diff --git a/libs/hwui/jni/YuvToJpegEncoder.cpp b/libs/hwui/jni/YuvToJpegEncoder.cpp
index 69418b0..4dbfa88 100644
--- a/libs/hwui/jni/YuvToJpegEncoder.cpp
+++ b/libs/hwui/jni/YuvToJpegEncoder.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "YuvToJpegEncoder"
-
#include "CreateJavaOutputStreamAdaptor.h"
#include "SkStream.h"
#include "YuvToJpegEncoder.h"
diff --git a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
index 706f18c..e3cdee6 100644
--- a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "HardwareBufferRenderer"
#define ATRACE_TAG ATRACE_TAG_VIEW
#include <GraphicsJNI.h>
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index ee22f7c..422ffea 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "ThreadedRenderer"
#define ATRACE_TAG ATRACE_TAG_VIEW
#include <FrameInfo.h>
diff --git a/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp b/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
index 764eff9..b86c74fe 100644
--- a/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
+++ b/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
#include <Interpolator.h>
#include <cutils/log.h>
diff --git a/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp b/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp
index c6d26f8..40be924 100644
--- a/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp
+++ b/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
#include <Animator.h>
#include <Interpolator.h>
#include <RenderProperties.h>
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index 8cfdeeb7..2ec94c9 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "Minikin"
-
#include "Font.h"
#include "SkData.h"
#include "SkFont.h"
diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp
index 1e392b1..462c8c8 100644
--- a/libs/hwui/jni/fonts/FontFamily.cpp
+++ b/libs/hwui/jni/fonts/FontFamily.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "Minikin"
-
#include "graphics_jni_helpers.h"
#include <nativehelper/ScopedUtfChars.h>
diff --git a/libs/hwui/jni/pdf/PdfEditor.cpp b/libs/hwui/jni/pdf/PdfEditor.cpp
index 427bafa..3b18f5f 100644
--- a/libs/hwui/jni/pdf/PdfEditor.cpp
+++ b/libs/hwui/jni/pdf/PdfEditor.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "PdfEditor"
-
#include <sys/types.h>
#include <unistd.h>
diff --git a/libs/hwui/jni/pdf/PdfUtils.cpp b/libs/hwui/jni/pdf/PdfUtils.cpp
index 06d2028..6887fda 100644
--- a/libs/hwui/jni/pdf/PdfUtils.cpp
+++ b/libs/hwui/jni/pdf/PdfUtils.cpp
@@ -16,14 +16,11 @@
#include "PdfUtils.h"
-#include "jni.h"
#include <nativehelper/JNIHelp.h>
+#include <utils/Log.h>
#include "fpdfview.h"
-
-#undef LOG_TAG
-#define LOG_TAG "PdfUtils"
-#include <utils/Log.h>
+#include "jni.h"
namespace android {
diff --git a/libs/hwui/jni/text/GraphemeBreak.cpp b/libs/hwui/jni/text/GraphemeBreak.cpp
index 55f03bd..322af7e 100644
--- a/libs/hwui/jni/text/GraphemeBreak.cpp
+++ b/libs/hwui/jni/text/GraphemeBreak.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "GraphemeBreaker"
-
#include <minikin/GraphemeBreak.h>
#include <nativehelper/ScopedPrimitiveArray.h>
diff --git a/libs/hwui/jni/text/LineBreaker.cpp b/libs/hwui/jni/text/LineBreaker.cpp
index 6986517..9ebf23c 100644
--- a/libs/hwui/jni/text/LineBreaker.cpp
+++ b/libs/hwui/jni/text/LineBreaker.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "LineBreaker"
-
#include "utils/misc.h"
#include "utils/Log.h"
#include "graphics_jni_helpers.h"
diff --git a/libs/hwui/jni/text/MeasuredText.cpp b/libs/hwui/jni/text/MeasuredText.cpp
index c13c800..081713a 100644
--- a/libs/hwui/jni/text/MeasuredText.cpp
+++ b/libs/hwui/jni/text/MeasuredText.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "MeasuredText"
-
#include "GraphicsJNI.h"
#include "utils/misc.h"
#include "utils/Log.h"
diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp
index 8c377b9..6c05346 100644
--- a/libs/hwui/jni/text/TextShaper.cpp
+++ b/libs/hwui/jni/text/TextShaper.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "TextShaper"
-
#include "graphics_jni_helpers.h"
#include <nativehelper/ScopedStringChars.h>
#include <nativehelper/ScopedPrimitiveArray.h>
diff --git a/libs/hwui/private/hwui/DrawGlInfo.h b/libs/hwui/private/hwui/DrawGlInfo.h
index eb1f930..ed3fabc 100644
--- a/libs/hwui/private/hwui/DrawGlInfo.h
+++ b/libs/hwui/private/hwui/DrawGlInfo.h
@@ -24,8 +24,7 @@
namespace uirenderer {
/**
- * Structure used by OpenGLRenderer::callDrawGLFunction() to pass and
- * receive data from OpenGL functors.
+ * Structure used to pass and receive data from OpenGL functors.
*/
struct DrawGlInfo {
// Input: current clip rect
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index f340945..a6e8c08f 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -34,9 +34,6 @@
#include "pipeline/skia/ShaderCache.h"
#include "renderstate/RenderState.h"
-#undef LOG_TAG
-#define LOG_TAG "VulkanManager"
-
namespace android {
namespace uirenderer {
namespace renderthread {
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index b0ba619..20b743b 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -25,9 +25,6 @@
#include "VulkanManager.h"
#include "utils/Color.h"
-#undef LOG_TAG
-#define LOG_TAG "VulkanSurface"
-
namespace android {
namespace uirenderer {
namespace renderthread {
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 9b48677..e8c9d0d 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -4739,97 +4739,6 @@
/**
* @hide
- * Test method to return the list of UIDs currently marked as ducked because of their
- * audio focus status
- * @return the list of UIDs, can be empty when no app is being ducked.
- */
- @TestApi
- @RequiresPermission("android.permission.QUERY_AUDIO_STATE")
- public @NonNull List<Integer> getFocusDuckedUidsForTest() {
- try {
- return getService().getFocusDuckedUidsForTest();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * @hide
- * Test method to return the duration of the fade out applied on the players of a focus loser
- * @return the fade out duration in ms
- */
- @TestApi
- @RequiresPermission("android.permission.QUERY_AUDIO_STATE")
- public long getFocusFadeOutDurationForTest() {
- try {
- return getService().getFocusFadeOutDurationForTest();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * @hide
- * Test method to return the length of time after a fade-out before the focus loser is unmuted
- * (and is faded back in).
- * @return the time gap after a fade-out completion on focus loss, and fade-in start in ms.
- */
- @TestApi
- @RequiresPermission("android.permission.QUERY_AUDIO_STATE")
- public long getFocusUnmuteDelayAfterFadeOutForTest() {
- try {
- return getService().getFocusUnmuteDelayAfterFadeOutForTest();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * @hide
- * Test method to start preventing applications from requesting audio focus during a test,
- * which could interfere with the functionality/behavior under test.
- * Calling this method needs to be paired with a call to {@link #exitAudioFocusFreezeForTest}
- * when the testing is done. If this is not the case (e.g. in case of a test crash),
- * a death observer mechanism will ensure the system is not left in a bad state, but this should
- * not be relied on when implementing tests.
- * @param exemptedUids a list of UIDs that are exempt from the freeze. This would for instance
- * be those of the test runner and other players used in the test, or the "fake" UIDs used
- * for testing with {@link #requestAudioFocusForTest(AudioFocusRequest, String, int, int)}.
- * @return true if the focus freeze mode is successfully entered, false if there was an issue,
- * such as another freeze in place at the time of invocation.
- * A false result should result in a test failure as this would indicate the system is not
- * in a proper state with a predictable behavior for audio focus management.
- */
- @TestApi
- @RequiresPermission("Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED")
- public boolean enterAudioFocusFreezeForTest(@NonNull List<Integer> exemptedUids) {
- Objects.requireNonNull(exemptedUids);
- try {
- final int[] uids = exemptedUids.stream().mapToInt(Integer::intValue).toArray();
- return getService().enterAudioFocusFreezeForTest(mICallBack, uids);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * @hide
- * Test method to end preventing applications from requesting audio focus during a test.
- * @return true if the focus freeze mode is successfully exited, false if there was an issue,
- * such as the freeze already having ended, or not started.
- */
- @TestApi
- @RequiresPermission("Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED")
- public boolean exitAudioFocusFreezeForTest() {
- try {
- return getService().exitAudioFocusFreezeForTest(mICallBack);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * @hide
* Request or lock audio focus.
* This method is to be used by system components that have registered an
* {@link android.media.audiopolicy.AudioPolicy} to request audio focus, but also to "lock" it
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 5837894..b2466e9 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -520,23 +520,6 @@
long getFadeOutDurationOnFocusLossMillis(in AudioAttributes aa);
- @EnforcePermission("QUERY_AUDIO_STATE")
- /* Returns a List<Integer> */
- @SuppressWarnings(value = {"untyped-collection"})
- List getFocusDuckedUidsForTest();
-
- @EnforcePermission("QUERY_AUDIO_STATE")
- long getFocusFadeOutDurationForTest();
-
- @EnforcePermission("QUERY_AUDIO_STATE")
- long getFocusUnmuteDelayAfterFadeOutForTest();
-
- @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
- boolean enterAudioFocusFreezeForTest(IBinder cb, in int[] uids);
-
- @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
- boolean exitAudioFocusFreezeForTest(IBinder cb);
-
void registerModeDispatcher(IAudioModeDispatcher dispatcher);
oneway void unregisterModeDispatcher(IAudioModeDispatcher dispatcher);
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 0ad8c24..0ff1b1e 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -42,7 +42,9 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.BaseColumns;
import android.provider.MediaStore;
+import android.provider.MediaStore.Audio.AudioColumns;
import android.provider.MediaStore.MediaColumns;
import android.provider.Settings;
import android.provider.Settings.System;
@@ -543,6 +545,95 @@
return getUriFromCursor(mContext, mCursor);
}
+ /**
+ * Gets the valid ringtone uri by a given uri string and ringtone type for the restore purpose.
+ *
+ * @param contentResolver ContentResolver to execute media query.
+ * @param value a canonicalized uri which refers to the ringtone.
+ * @param ringtoneType an integer representation of the kind of uri that is being restored, can
+ * be RingtoneManager.TYPE_RINGTONE, RingtoneManager.TYPE_NOTIFICATION, or
+ * RingtoneManager.TYPE_ALARM.
+ * @hide
+ */
+ public static @Nullable Uri getRingtoneUriForRestore(
+ @NonNull ContentResolver contentResolver, @Nullable String value, int ringtoneType)
+ throws FileNotFoundException, IllegalArgumentException {
+ if (value == null) {
+ // Return a valid null. It means the null value is intended instead of a failure.
+ return null;
+ }
+
+ Uri ringtoneUri;
+ final Uri canonicalUri = Uri.parse(value);
+
+ // Try to get the media uri via the regular uncanonicalize method first.
+ ringtoneUri = contentResolver.uncanonicalize(canonicalUri);
+ if (ringtoneUri != null) {
+ // Canonicalize it to make the result contain the right metadata of the media asset.
+ ringtoneUri = contentResolver.canonicalize(ringtoneUri);
+ return ringtoneUri;
+ }
+
+ // Query the media by title and ringtone type.
+ final String title = canonicalUri.getQueryParameter(AudioColumns.TITLE);
+ Uri baseUri = ContentUris.removeId(canonicalUri).buildUpon().clearQuery().build();
+ String ringtoneTypeSelection = "";
+ switch (ringtoneType) {
+ case RingtoneManager.TYPE_RINGTONE:
+ ringtoneTypeSelection = MediaStore.Audio.AudioColumns.IS_RINGTONE;
+ break;
+ case RingtoneManager.TYPE_NOTIFICATION:
+ ringtoneTypeSelection = MediaStore.Audio.AudioColumns.IS_NOTIFICATION;
+ break;
+ case RingtoneManager.TYPE_ALARM:
+ ringtoneTypeSelection = MediaStore.Audio.AudioColumns.IS_ALARM;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown ringtone type: " + ringtoneType);
+ }
+
+ final String selection = ringtoneTypeSelection + "=1 AND " + AudioColumns.TITLE + "=?";
+ Cursor cursor = null;
+ try {
+ cursor =
+ contentResolver.query(
+ baseUri,
+ /* projection */ new String[] {BaseColumns._ID},
+ /* selection */ selection,
+ /* selectionArgs */ new String[] {title},
+ /* sortOrder */ null,
+ /* cancellationSignal */ null);
+
+ } catch (IllegalArgumentException e) {
+ throw new FileNotFoundException("Volume not found for " + baseUri);
+ }
+ if (cursor == null) {
+ throw new FileNotFoundException("Missing cursor for " + baseUri);
+ } else if (cursor.getCount() == 0) {
+ FileUtils.closeQuietly(cursor);
+ throw new FileNotFoundException("No item found for " + baseUri);
+ } else if (cursor.getCount() > 1) {
+ // Find more than 1 result.
+ // We are not sure which one is the right ringtone file so just abandon this case.
+ FileUtils.closeQuietly(cursor);
+ throw new FileNotFoundException(
+ "Find multiple ringtone candidates by title+ringtone_type query: count: "
+ + cursor.getCount());
+ }
+ if (cursor.moveToFirst()) {
+ ringtoneUri = ContentUris.withAppendedId(baseUri, cursor.getLong(0));
+ FileUtils.closeQuietly(cursor);
+ } else {
+ FileUtils.closeQuietly(cursor);
+ throw new FileNotFoundException("Failed to read row from the result.");
+ }
+
+ // Canonicalize it to make the result contain the right metadata of the media asset.
+ ringtoneUri = contentResolver.canonicalize(ringtoneUri);
+ Log.v(TAG, "Find a valid result: " + ringtoneUri);
+ return ringtoneUri;
+ }
+
private static Uri getUriFromCursor(Context context, Cursor cursor) {
final Uri uri = ContentUris.withAppendedId(Uri.parse(cursor.getString(URI_COLUMN_INDEX)),
cursor.getLong(ID_COLUMN_INDEX));
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index fb72c7b..223b432c 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -83,6 +83,7 @@
try {
mImpl.start(new MediaProjectionCallback());
} catch (RemoteException e) {
+ Log.e(TAG, "Content Recording: Failed to start media projection", e);
throw new RuntimeException("Failed to start media projection", e);
}
mDisplayManager = displayManager;
@@ -105,11 +106,18 @@
* @see #unregisterCallback
*/
public void registerCallback(@NonNull Callback callback, @Nullable Handler handler) {
- final Callback c = Objects.requireNonNull(callback);
- if (handler == null) {
- handler = new Handler();
+ try {
+ final Callback c = Objects.requireNonNull(callback);
+ if (handler == null) {
+ handler = new Handler();
+ }
+ mCallbacks.put(c, new CallbackRecord(c, handler));
+ } catch (NullPointerException e) {
+ Log.e(TAG, "Content Recording: cannot register null Callback", e);
+ throw e;
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Content Recording: failed to create new Handler to register Callback", e);
}
- mCallbacks.put(c, new CallbackRecord(c, handler));
}
/**
@@ -120,8 +128,13 @@
* @see #registerCallback
*/
public void unregisterCallback(@NonNull Callback callback) {
- final Callback c = Objects.requireNonNull(callback);
- mCallbacks.remove(c);
+ try {
+ final Callback c = Objects.requireNonNull(callback);
+ mCallbacks.remove(c);
+ } catch (NullPointerException e) {
+ Log.d(TAG, "Content Recording: cannot unregister null Callback", e);
+ throw e;
+ }
}
/**
@@ -203,9 +216,11 @@
@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
if (shouldMediaProjectionRequireCallback()) {
if (mCallbacks.isEmpty()) {
- throw new IllegalStateException(
+ final IllegalStateException e = new IllegalStateException(
"Must register a callback before starting capture, to manage resources in"
+ " response to MediaProjection states.");
+ Log.e(TAG, "Content Recording: no callback registered for virtual display", e);
+ throw e;
}
}
final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
@@ -272,6 +287,7 @@
*/
public void stop() {
try {
+ Log.d(TAG, "Content Recording: stopping projection");
mImpl.stop();
} catch (RemoteException e) {
Log.e(TAG, "Unable to stop projection", e);
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 5a68c53..9790d02 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -256,6 +256,7 @@
*/
public void stopActiveProjection() {
try {
+ Log.d(TAG, "Content Recording: stopping active projection");
mService.stopActiveProjection();
} catch (RemoteException e) {
Log.e(TAG, "Unable to stop the currently active media projection", e);
@@ -269,6 +270,7 @@
*/
public void addCallback(@NonNull Callback callback, @Nullable Handler handler) {
if (callback == null) {
+ Log.w(TAG, "Content Recording: cannot add null callback");
throw new IllegalArgumentException("callback must not be null");
}
CallbackDelegate delegate = new CallbackDelegate(callback, handler);
@@ -286,6 +288,7 @@
*/
public void removeCallback(@NonNull Callback callback) {
if (callback == null) {
+ Log.w(TAG, "ContentRecording: cannot remove null callback");
throw new IllegalArgumentException("callback must not be null");
}
CallbackDelegate delegate = mCallbacks.remove(callback);
diff --git a/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java b/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java
index 73dbe67..69d836d 100644
--- a/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java
+++ b/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java
@@ -20,70 +20,100 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.util.Log;
import androidx.concurrent.futures.CallbackToFutureAdapter;
-import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
import java.lang.ref.WeakReference;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.TimeUnit;
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 {
- public static final String TAG = "MediaTestUtils";
-
- public static ListenableFuture<Intent> getFutureForIntent(Context context, String action,
- Predicate<Intent> pred) {
+ /**
+ * 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);
- // Doesn't need to be thread safe since the resolver is called inline
- final WeakReference<BroadcastReceiver> wrapper[] = new WeakReference[1];
- ListenableFuture<Intent> future = CallbackToFutureAdapter.getFuture(completer -> {
- var receiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
+ return getFutureForListener(
+ (recv) ->
+ context.registerReceiver(
+ recv, new IntentFilter(action), Context.RECEIVER_NOT_EXPORTED),
+ (recv) -> {
try {
- if (action.equals(intent.getAction()) && pred.test(intent)) {
- completer.set(intent);
- }
- } catch (Exception e) {
- completer.setException(e);
+ context.unregisterReceiver(recv);
+ } catch (IllegalArgumentException e) {
+ // Thrown when receiver is already unregistered, nothing to do
}
- }
- };
- wrapper[0] = new WeakReference(receiver);
- context.registerReceiver(receiver, new IntentFilter(action),
- Context.RECEIVER_NOT_EXPORTED);
- return "Intent receiver future for ";
- });
+ },
+ (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("CallbackToFutureAdapter resolver should be called inline");
+ throw new AssertionError("Resolver should be called inline");
}
final var weakref = wrapper[0];
- future.addListener(() -> {
- try {
- var recv = weakref.get();
- // If there is no reference left, the receiver has already been unregistered
- if (recv != null) {
- context.unregisterReceiver(recv);
- return;
- }
- } catch (IllegalArgumentException e) {
- // Receiver already unregistered, nothing to do.
- }
- Log.d(TAG, "Intent receiver future for action: " + action +
- "unregistered prior to future completion/cancellation.");
- } , MoreExecutors.directExecutor()); // Direct executor is fine since lightweight
+ 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/tests/src/java/com/android/media/mediatestutils/GetFutureForIntentTest.java b/media/tests/mediatestutils/tests/src/java/com/android/media/mediatestutils/GetFutureForIntentTest.java
index 70b46ad..a4266a3 100644
--- 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
@@ -19,6 +19,7 @@
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;
@@ -30,6 +31,8 @@
import androidx.test.runner.AndroidJUnit4;
+import com.google.common.util.concurrent.ListenableFuture;
+
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -100,6 +103,51 @@
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);
diff --git a/native/android/configuration.cpp b/native/android/configuration.cpp
index 283445f..b50514d 100644
--- a/native/android/configuration.cpp
+++ b/native/android/configuration.cpp
@@ -36,7 +36,7 @@
void AConfiguration_fromAssetManager(AConfiguration* out, AAssetManager* am) {
ScopedLock<AssetManager2> locked_mgr(*AssetManagerForNdkAssetManager(am));
- ResTable_config config = locked_mgr->GetConfigurations()[0];
+ ResTable_config config = locked_mgr->GetConfiguration();
// AConfiguration is not a virtual subclass, so we can memcpy.
memcpy(out, &config, sizeof(config));
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
index 0c4cb8e..74acf67 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
+import android.app.ActivityOptions;
import android.app.LoaderManager;
import android.content.ComponentName;
import android.content.Context;
@@ -714,8 +715,13 @@
try {
mPrinterForInfoIntent = printer;
+ Bundle options = ActivityOptions.makeBasic()
+ .setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+ .toBundle();
startIntentSenderForResult(printer.getInfoIntent().getIntentSender(),
- INFO_INTENT_REQUEST_CODE, fillInIntent, 0, 0, 0);
+ INFO_INTENT_REQUEST_CODE, fillInIntent, 0, 0, 0,
+ options);
} catch (SendIntentException e) {
mPrinterForInfoIntent = null;
Log.e(LOG_TAG, "Could not execute pending info intent: %s", e);
diff --git a/packages/SettingsLib/Spa/gallery/res/values/strings.xml b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
index 0d1a1fe..ec60f8c 100644
--- a/packages/SettingsLib/Spa/gallery/res/values/strings.xml
+++ b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
@@ -24,4 +24,6 @@
<string name="single_line_summary_preference_title" translatable="false">Preference (singleLineSummary = true)</string>
<!-- Summary for single line summary preference. [DO NOT TRANSLATE] -->
<string name="single_line_summary_preference_summary" translatable="false">A very long summary to show case a preference which only shows a single line summary.</string>
+ <!-- Footer text with two links. [DO NOT TRANSLATE] -->
+ <string name="footer_with_two_links" translatable="false">Annotated string with <a href="https://www.android.com/">link 1</a> and <a href="https://source.android.com/">link 2</a>.</string>
</resources>
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPageProvider.kt
similarity index 92%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPageProvider.kt
index 9c7e0ce..50c0eb7 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPageProvider.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 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.
@@ -28,9 +28,11 @@
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.AnnotatedText
import com.android.settingslib.spa.widget.ui.Footer
private const val TITLE = "Sample Footer"
@@ -78,6 +80,9 @@
entry.UiLayout()
}
Footer(footerText = "Footer text always at the end of page.")
+ Footer {
+ AnnotatedText(R.string.footer_with_two_links)
+ }
}
}
}
diff --git a/packages/SettingsLib/Spa/spa/res/values/colors.xml b/packages/SettingsLib/Spa/spa/res/values/colors.xml
new file mode 100644
index 0000000..ca4a0b2
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/res/values/colors.xml
@@ -0,0 +1,67 @@
+<?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>
+ <color name="settingslib_protection_color">@android:color/white</color>
+
+ <!-- Dynamic colors-->
+ <color name="settingslib_color_blue600">#1a73e8</color>
+ <color name="settingslib_color_blue400">#669df6</color>
+ <color name="settingslib_color_blue300">#8ab4f8</color>
+ <color name="settingslib_color_blue100">#d2e3fc</color>
+ <color name="settingslib_color_blue50">#e8f0fe</color>
+ <color name="settingslib_color_green600">#1e8e3e</color>
+ <color name="settingslib_color_green500">#34A853</color>
+ <color name="settingslib_color_green400">#5bb974</color>
+ <color name="settingslib_color_green100">#ceead6</color>
+ <color name="settingslib_color_green50">#e6f4ea</color>
+ <color name="settingslib_color_red600">#d93025</color>
+ <color name="settingslib_color_red500">#B3261E</color>
+ <color name="settingslib_color_red400">#ee675c</color>
+ <color name="settingslib_color_red100">#fad2cf</color>
+ <color name="settingslib_color_red50">#fce8e6</color>
+ <color name="settingslib_color_yellow600">#f9ab00</color>
+ <color name="settingslib_color_yellow400">#fcc934</color>
+ <color name="settingslib_color_yellow100">#feefc3</color>
+ <color name="settingslib_color_yellow50">#fef7e0</color>
+ <color name="settingslib_color_grey900">#202124</color>
+ <color name="settingslib_color_grey800">#3c4043</color>
+ <color name="settingslib_color_grey700">#5f6368</color>
+ <color name="settingslib_color_grey600">#80868b</color>
+ <color name="settingslib_color_grey500">#9AA0A6</color>
+ <color name="settingslib_color_grey400">#bdc1c6</color>
+ <color name="settingslib_color_grey300">#dadce0</color>
+ <color name="settingslib_color_grey200">#e8eaed</color>
+ <color name="settingslib_color_grey100">#f1f3f4</color>
+ <color name="settingslib_color_grey50">#f8f9fa</color>
+ <color name="settingslib_color_orange600">#e8710a</color>
+ <color name="settingslib_color_orange400">#fa903e</color>
+ <color name="settingslib_color_orange300">#fcad70</color>
+ <color name="settingslib_color_orange100">#fedfc8</color>
+ <color name="settingslib_color_pink600">#e52592</color>
+ <color name="settingslib_color_pink400">#ff63b8</color>
+ <color name="settingslib_color_pink300">#ff8bcb</color>
+ <color name="settingslib_color_pink100">#fdcfe8</color>
+ <color name="settingslib_color_purple600">#9334e6</color>
+ <color name="settingslib_color_purple400">#af5cf7</color>
+ <color name="settingslib_color_purple300">#c58af9</color>
+ <color name="settingslib_color_purple100">#e9d2fd</color>
+ <color name="settingslib_color_cyan600">#12b5c8</color>
+ <color name="settingslib_color_cyan400">#4ecde6</color>
+ <color name="settingslib_color_cyan300">#78d9ec</color>
+ <color name="settingslib_color_cyan100">#cbf0f8</color>
+</resources>
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
index 9ddd0c6..88ba4b0 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
@@ -16,105 +16,93 @@
package com.android.settingslib.spa.framework.util
-import android.content.res.Resources
import android.graphics.Typeface
import android.text.Spanned
import android.text.style.StyleSpan
import android.text.style.URLSpan
import androidx.annotation.StringRes
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.unit.Density
+import androidx.compose.ui.text.style.TextDecoration
-const val URLSPAN_TAG = "URLSPAN_TAG"
+const val URL_SPAN_TAG = "URL_SPAN_TAG"
@Composable
-fun annotatedStringResource(@StringRes id: Int, urlSpanColor: Color): AnnotatedString {
- LocalConfiguration.current
+fun annotatedStringResource(@StringRes id: Int): AnnotatedString {
val resources = LocalContext.current.resources
- val density = LocalDensity.current
+ val urlSpanColor = MaterialTheme.colorScheme.primary
return remember(id) {
val text = resources.getText(id)
- spannableStringToAnnotatedString(text, density, urlSpanColor)
+ spannableStringToAnnotatedString(text, urlSpanColor)
}
}
-private fun spannableStringToAnnotatedString(text: CharSequence, density: Density, urlSpanColor: Color): AnnotatedString {
- return if (text is Spanned) {
- with(density) {
- buildAnnotatedString {
- append((text.toString()))
- text.getSpans(0, text.length, Any::class.java).forEach {
- val start = text.getSpanStart(it)
- val end = text.getSpanEnd(it)
- when (it) {
- is StyleSpan ->
- when (it.style) {
- Typeface.NORMAL -> addStyle(
- SpanStyle(
- fontWeight = FontWeight.Normal,
- fontStyle = FontStyle.Normal
- ),
- start,
- end
- )
- Typeface.BOLD -> addStyle(
- SpanStyle(
- fontWeight = FontWeight.Bold,
- fontStyle = FontStyle.Normal
- ),
- start,
- end
- )
- Typeface.ITALIC -> addStyle(
- SpanStyle(
- fontWeight = FontWeight.Normal,
- fontStyle = FontStyle.Italic
- ),
- start,
- end
- )
- Typeface.BOLD_ITALIC -> addStyle(
- SpanStyle(
- fontWeight = FontWeight.Bold,
- fontStyle = FontStyle.Italic
- ),
- start,
- end
- )
- }
- is URLSpan -> {
- addStyle(
- SpanStyle(
- color = urlSpanColor,
- ),
- start,
- end
- )
- if (!it.url.isNullOrEmpty()) {
- addStringAnnotation(
- URLSPAN_TAG,
- it.url,
- start,
- end
- )
- }
- }
- else -> addStyle(SpanStyle(), start, end)
- }
+private fun spannableStringToAnnotatedString(text: CharSequence, urlSpanColor: Color) =
+ if (text is Spanned) {
+ buildAnnotatedString {
+ append((text.toString()))
+ for (span in text.getSpans(0, text.length, Any::class.java)) {
+ val start = text.getSpanStart(span)
+ val end = text.getSpanEnd(span)
+ when (span) {
+ is StyleSpan -> addStyleSpan(span, start, end)
+ is URLSpan -> addUrlSpan(span, urlSpanColor, start, end)
+ else -> addStyle(SpanStyle(), start, end)
}
}
}
} else {
AnnotatedString(text.toString())
}
+
+private fun AnnotatedString.Builder.addStyleSpan(styleSpan: StyleSpan, start: Int, end: Int) {
+ when (styleSpan.style) {
+ Typeface.NORMAL -> addStyle(
+ SpanStyle(fontWeight = FontWeight.Normal, fontStyle = FontStyle.Normal),
+ start,
+ end,
+ )
+
+ Typeface.BOLD -> addStyle(
+ SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Normal),
+ start,
+ end,
+ )
+
+ Typeface.ITALIC -> addStyle(
+ SpanStyle(fontWeight = FontWeight.Normal, fontStyle = FontStyle.Italic),
+ start,
+ end,
+ )
+
+ Typeface.BOLD_ITALIC -> addStyle(
+ SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic),
+ start,
+ end,
+ )
+ }
+}
+
+private fun AnnotatedString.Builder.addUrlSpan(
+ urlSpan: URLSpan,
+ urlSpanColor: Color,
+ start: Int,
+ end: Int,
+) {
+ addStyle(
+ SpanStyle(color = urlSpanColor, textDecoration = TextDecoration.Underline),
+ start,
+ end,
+ )
+ if (!urlSpan.url.isNullOrEmpty()) {
+ addStringAnnotation(URL_SPAN_TAG, urlSpan.url, start, end)
+ }
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt
index 7cc9bf7..6a2163c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt
@@ -22,11 +22,11 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.ui.ImageBox
import com.android.settingslib.spa.widget.ui.Lottie
@@ -81,7 +81,7 @@
maxHeight = SettingsDimension.illustrationMaxHeight,
)
.clip(RoundedCornerShape(SettingsDimension.illustrationCornerRadius))
- .background(color = MaterialTheme.colorScheme.surface)
+ .background(color = Color.Transparent)
when (resourceType) {
ResourceType.LOTTIE -> {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/AnnotatedText.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/AnnotatedText.kt
new file mode 100644
index 0000000..82ac7e3
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/AnnotatedText.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.settingslib.spa.widget.ui
+
+import androidx.annotation.StringRes
+import androidx.compose.foundation.text.ClickableText
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalUriHandler
+import com.android.settingslib.spa.framework.util.URL_SPAN_TAG
+import com.android.settingslib.spa.framework.util.annotatedStringResource
+
+@Composable
+fun AnnotatedText(@StringRes id: Int) {
+ val uriHandler = LocalUriHandler.current
+ val annotatedString = annotatedStringResource(id)
+ ClickableText(
+ text = annotatedString,
+ style = MaterialTheme.typography.bodyMedium.copy(
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ ),
+ ) { offset ->
+ // Gets the url at the clicked position.
+ annotatedString.getStringAnnotations(URL_SPAN_TAG, offset, offset)
+ .firstOrNull()
+ ?.let { uriHandler.openUri(it.item) }
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt
index 915c6e2..5f7fe85 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt
@@ -16,15 +16,24 @@
package com.android.settingslib.spa.widget.ui
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.res.colorResource
+import com.airbnb.lottie.LottieProperty
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.animateLottieCompositionAsState
import com.airbnb.lottie.compose.rememberLottieComposition
+import com.airbnb.lottie.compose.rememberLottieDynamicProperties
+import com.airbnb.lottie.compose.rememberLottieDynamicProperty
+import com.android.settingslib.spa.R
@Composable
fun Lottie(
@@ -38,6 +47,34 @@
}
}
+object LottieColorUtils {
+ private val DARK_TO_LIGHT_THEME_COLOR_MAP = mapOf(
+ ".grey600" to R.color.settingslib_color_grey400,
+ ".grey800" to R.color.settingslib_color_grey300,
+ ".grey900" to R.color.settingslib_color_grey50,
+ ".red400" to R.color.settingslib_color_red600,
+ ".black" to android.R.color.white,
+ ".blue400" to R.color.settingslib_color_blue600,
+ ".green400" to R.color.settingslib_color_green600,
+ ".green200" to R.color.settingslib_color_green500,
+ ".red200" to R.color.settingslib_color_red500,
+ )
+
+ @Composable
+ private fun getDefaultPropertiesList() =
+ DARK_TO_LIGHT_THEME_COLOR_MAP.map { (key, colorRes) ->
+ val color = colorResource(colorRes).toArgb()
+ rememberLottieDynamicProperty(
+ property = LottieProperty.COLOR_FILTER,
+ keyPath = arrayOf("**", key, "**")
+ ){ PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP) }
+ }
+
+ @Composable
+ fun getDefaultDynamicProperties() =
+ rememberLottieDynamicProperties(*getDefaultPropertiesList().toTypedArray())
+}
+
@Composable
private fun BaseLottie(resId: Int) {
val composition by rememberLottieComposition(
@@ -47,8 +84,10 @@
composition,
iterations = LottieConstants.IterateForever,
)
+ val isLightMode = !isSystemInDarkTheme()
LottieAnimation(
composition = composition,
+ dynamicProperties = LottieColorUtils.getDefaultDynamicProperties().takeIf { isLightMode },
progress = { progress },
)
}
diff --git a/packages/SettingsLib/Spa/tests/res/values/strings.xml b/packages/SettingsLib/Spa/tests/res/values/strings.xml
index cbfea06..fb8f878 100644
--- a/packages/SettingsLib/Spa/tests/res/values/strings.xml
+++ b/packages/SettingsLib/Spa/tests/res/values/strings.xml
@@ -26,5 +26,7 @@
other {There are # songs found in {place}.}
}</string>
- <string name="test_annotated_string_resource">Annotated string with <b>bold</b> and <a href="https://www.google.com/">link</a>.</string>
+ <string name="test_annotated_string_resource">Annotated string with <b>bold</b> and <a href="https://www.android.com/">link</a>.</string>
+
+ <string name="test_link"><a href="https://www.android.com/">link</a></string>
</resources>
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
index b65be42..9928355 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
@@ -16,14 +16,14 @@
package com.android.settingslib.spa.framework.util
-import androidx.compose.ui.graphics.Color
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.text.style.TextDecoration
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.framework.util.URLSPAN_TAG
-import com.android.settingslib.spa.framework.util.annotatedStringResource
import com.android.settingslib.spa.test.R
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
@@ -38,24 +38,34 @@
@Test
fun testAnnotatedStringResource() {
composeTestRule.setContent {
- val annotatedString = annotatedStringResource(R.string.test_annotated_string_resource, Color.Blue)
+ val annotatedString =
+ annotatedStringResource(R.string.test_annotated_string_resource)
val annotations = annotatedString.getStringAnnotations(0, annotatedString.length)
- assertThat(annotations).hasSize(1)
- assertThat(annotations[0].start).isEqualTo(31)
- assertThat(annotations[0].end).isEqualTo(35)
- assertThat(annotations[0].tag).isEqualTo(URLSPAN_TAG)
- assertThat(annotations[0].item).isEqualTo("https://www.google.com/")
+ assertThat(annotations).containsExactly(
+ AnnotatedString.Range(
+ item = "https://www.android.com/",
+ start = 31,
+ end = 35,
+ tag = URL_SPAN_TAG,
+ )
+ )
- assertThat(annotatedString.spanStyles).hasSize(2)
- assertThat(annotatedString.spanStyles[0].start).isEqualTo(22)
- assertThat(annotatedString.spanStyles[0].end).isEqualTo(26)
- assertThat(annotatedString.spanStyles[0].item).isEqualTo(
- SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Normal))
-
- assertThat(annotatedString.spanStyles[1].start).isEqualTo(31)
- assertThat(annotatedString.spanStyles[1].end).isEqualTo(35)
- assertThat(annotatedString.spanStyles[1].item).isEqualTo(SpanStyle(color = Color.Blue))
+ assertThat(annotatedString.spanStyles).containsExactly(
+ AnnotatedString.Range(
+ item = SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Normal),
+ start = 22,
+ end = 26,
+ ),
+ AnnotatedString.Range(
+ item = SpanStyle(
+ color = MaterialTheme.colorScheme.primary,
+ textDecoration = TextDecoration.Underline,
+ ),
+ start = 31,
+ end = 35,
+ ),
+ )
}
}
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedTextTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedTextTest.kt
new file mode 100644
index 0000000..2c218e3
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedTextTest.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.settingslib.spa.framework.util
+
+import android.content.Context
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalUriHandler
+import androidx.compose.ui.platform.UriHandler
+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.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.test.R
+import com.android.settingslib.spa.widget.ui.AnnotatedText
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@RunWith(AndroidJUnit4::class)
+class AnnotatedTextTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @get:Rule
+ val mockito: MockitoRule = MockitoJUnit.rule()
+
+ @Mock
+ private lateinit var uriHandler: UriHandler
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Test
+ fun text_isDisplayed() {
+ composeTestRule.setContent {
+ AnnotatedText(R.string.test_annotated_string_resource)
+ }
+
+ composeTestRule.onNodeWithText(context.getString(R.string.test_annotated_string_resource))
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun onUriClick_openUri() {
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalUriHandler provides uriHandler) {
+ AnnotatedText(R.string.test_link)
+ }
+ }
+
+ composeTestRule.onNodeWithText(context.getString(R.string.test_link)).performClick()
+
+ verify(uriHandler).openUri("https://www.android.com/")
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
index 21c9e34..945f2e2 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
@@ -28,8 +28,7 @@
title: String,
packageName: String,
userId: Int,
- footerText: String,
- footerContent: (@Composable () -> Unit)?,
+ footerContent: @Composable () -> Unit,
packageManagers: IPackageManagers,
content: @Composable PackageInfo.() -> Unit,
) {
@@ -41,10 +40,6 @@
packageInfo.content()
- if (footerContent != null) {
- Footer(footerContent)
- } else {
- Footer(footerText)
- }
+ Footer(footerContent)
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index 7c689c6..7f82be4 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -38,6 +38,7 @@
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spa.widget.ui.AnnotatedText
import com.android.settingslib.spaprivileged.model.app.AppRecord
import com.android.settingslib.spaprivileged.model.app.IPackageManagers
import com.android.settingslib.spaprivileged.model.app.PackageManagers
@@ -140,8 +141,7 @@
title = stringResource(pageTitleResId),
packageName = packageName,
userId = userId,
- footerText = stringResource(footerResId),
- footerContent = footerContent(),
+ footerContent = { AnnotatedText(footerResId) },
packageManagers = packageManagers,
) {
val model = createSwitchModel(applicationInfo)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
index f4b3204..1ab6230 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
@@ -20,7 +20,6 @@
import android.content.pm.ApplicationInfo
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
-import androidx.compose.ui.text.AnnotatedString
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.compose.rememberContext
@@ -37,10 +36,7 @@
val footerResId: Int
val switchRestrictionKeys: List<String>
get() = emptyList()
- @Composable
- fun footerContent(): (@Composable () -> Unit)? {
- return null
- }
+
/**
* Loads the extra info for the App List, and generates the [AppRecord] List.
*
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index f1efe9e..bbfdc38 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -385,5 +385,6 @@
VALIDATORS.put(Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED,
BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.DND_CONFIGS_MIGRATED, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 9da1ab8..27a45df 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -44,6 +44,7 @@
import com.android.internal.app.LocalePicker;
import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
+import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
@@ -332,21 +333,30 @@
* @param value can be a canonicalized uri or "_silent" to indicate a silent (null) ringtone.
*/
private void setRingtone(String name, String value) {
- // If it's null, don't change the default
- if (value == null) return;
- final Uri ringtoneUri;
- if (SILENT_RINGTONE.equals(value)) {
- ringtoneUri = null;
- } else {
- Uri canonicalUri = Uri.parse(value);
- ringtoneUri = mContext.getContentResolver().uncanonicalize(canonicalUri);
- if (ringtoneUri == null) {
- // Unrecognized or invalid Uri, don't restore
- return;
- }
- }
- final int ringtoneType = getRingtoneType(name);
+ Log.v(TAG, "Set ringtone for name: " + name + " value: " + value);
+ // If it's null, don't change the default.
+ if (value == null) return;
+ final int ringtoneType = getRingtoneType(name);
+ if (SILENT_RINGTONE.equals(value)) {
+ // SILENT_RINGTONE is a special constant generated by onBackupValue in the source
+ // device.
+ RingtoneManager.setActualDefaultRingtoneUri(mContext, ringtoneType, null);
+ return;
+ }
+
+ Uri ringtoneUri = null;
+ try {
+ ringtoneUri =
+ RingtoneManager.getRingtoneUriForRestore(
+ mContext.getContentResolver(), value, ringtoneType);
+ } catch (FileNotFoundException | IllegalArgumentException e) {
+ Log.w(TAG, "Failed to resolve " + value + ": " + e);
+ // Unrecognized or invalid Uri, don't restore
+ return;
+ }
+
+ Log.v(TAG, "setActualDefaultRingtoneUri type: " + ringtoneType + ", uri: " + ringtoneUri);
RingtoneManager.setActualDefaultRingtoneUri(mContext, ringtoneType, ringtoneUri);
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 61afb5b..5475fad 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -858,7 +858,8 @@
Settings.Secure.UI_TRANSLATION_ENABLED,
Settings.Secure.CREDENTIAL_SERVICE,
Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED);
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED,
+ Settings.Secure.DND_CONFIGS_MIGRATED);
@Test
public void systemSettingsBackedUpOrDenied() {
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
index bc81c44..ef062df 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
@@ -26,15 +26,24 @@
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.MatrixCursor;
import android.media.AudioManager;
import android.net.Uri;
+import android.os.Bundle;
import android.os.LocaleList;
+import android.provider.BaseColumns;
+import android.provider.MediaStore;
import android.provider.Settings;
import android.telephony.TelephonyManager;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockContentResolver;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -57,6 +66,13 @@
private static final String SETTING_VALUE = "setting_value";
private static final String SETTING_REAL_VALUE = "setting_real_value";
+ private static final String DEFAULT_RINGTONE_VALUE =
+ "content://media/internal/audio/media/10?title=DefaultRingtone&canonical=1";
+ private static final String DEFAULT_NOTIFICATION_VALUE =
+ "content://media/internal/audio/media/20?title=DefaultNotification&canonical=1";
+ private static final String DEFAULT_ALARM_VALUE =
+ "content://media/internal/audio/media/30?title=DefaultAlarm&canonical=1";
+
private SettingsHelper mSettingsHelper;
@Mock private Context mContext;
@@ -74,6 +90,7 @@
mTelephonyManager);
when(mContext.getResources()).thenReturn(mResources);
when(mContext.getApplicationContext()).thenReturn(mContext);
+ when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
when(mContext.getContentResolver()).thenReturn(getContentResolver());
mSettingsHelper = spy(new SettingsHelper(mContext));
@@ -338,6 +355,377 @@
}
@Test
+ public void testRestoreValue_customRingtone_regularUncanonicalize_Success() {
+ final String sourceRingtoneValue =
+ "content://media/internal/audio/media/1?title=Song&canonical=1";
+ final String newRingtoneValueUncanonicalized =
+ "content://media/internal/audio/media/100";
+ final String newRingtoneValueCanonicalized =
+ "content://media/internal/audio/media/100?title=Song&canonical=1";
+
+ MockContentResolver mMockContentResolver = new MockContentResolver();
+ when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+ ContentProvider mockMediaContentProvider =
+ new MockContentProvider(mContext) {
+ @Override
+ public Uri uncanonicalize(Uri url) {
+ assertThat(url).isEqualTo(Uri.parse(sourceRingtoneValue));
+ return Uri.parse(newRingtoneValueUncanonicalized);
+ }
+
+ @Override
+ public Uri canonicalize(Uri url) {
+ assertThat(url).isEqualTo(Uri.parse(newRingtoneValueUncanonicalized));
+ return Uri.parse(newRingtoneValueCanonicalized);
+ }
+
+ @Override
+ public String getType(Uri url) {
+ return "audio/ogg";
+ }
+ };
+
+ ContentProvider mockSettingsContentProvider =
+ new MockSettingsProvider(mContext, getContentResolver());
+ mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+ resetRingtoneSettingsToDefault(mMockContentResolver);
+ assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+ .isEqualTo(DEFAULT_RINGTONE_VALUE);
+
+ mSettingsHelper.restoreValue(
+ mContext,
+ mMockContentResolver,
+ new ContentValues(),
+ Uri.EMPTY,
+ Settings.System.RINGTONE,
+ sourceRingtoneValue,
+ 0);
+
+ assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+ .isEqualTo(newRingtoneValueCanonicalized);
+ }
+
+ @Test
+ public void testRestoreValue_customRingtone_useCustomLookup_success() {
+ final String sourceRingtoneValue =
+ "content://0@media/external/audio/media/1?title=Song&canonical=1";
+ final String newRingtoneValueUncanonicalized =
+ "content://0@media/external/audio/media/100";
+ final String newRingtoneValueCanonicalized =
+ "content://0@media/external/audio/media/100?title=Song&canonical=1";
+
+ MockContentResolver mMockContentResolver = new MockContentResolver();
+ when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+ MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
+ cursor.addRow(new Object[] {100L});
+
+ ContentProvider mockMediaContentProvider =
+ new MockContentProvider(mContext) {
+ @Override
+ public Uri uncanonicalize(Uri url) {
+ // mock the lookup failure in regular MediaProvider.uncanonicalize.
+ return null;
+ }
+
+ @Override
+ public Uri canonicalize(Uri url) {
+ assertThat(url).isEqualTo(Uri.parse(newRingtoneValueUncanonicalized));
+ return Uri.parse(newRingtoneValueCanonicalized);
+ }
+
+ @Override
+ public String getType(Uri url) {
+ return "audio/ogg";
+ }
+
+ @Override
+ public Cursor query(
+ Uri uri,
+ String[] projection,
+ String selection,
+ String[] selectionArgs,
+ String sortOrder) {
+ assertThat(uri)
+ .isEqualTo(Uri.parse("content://0@media/external/audio/media"));
+ assertThat(projection).isEqualTo(new String[] {"_id"});
+ assertThat(selection).isEqualTo("is_ringtone=1 AND title=?");
+ assertThat(selectionArgs).isEqualTo(new String[] {"Song"});
+ return cursor;
+ }
+ };
+
+ ContentProvider mockSettingsContentProvider =
+ new MockSettingsProvider(mContext, getContentResolver());
+ mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+ resetRingtoneSettingsToDefault(mMockContentResolver);
+
+ mSettingsHelper.restoreValue(
+ mContext,
+ mMockContentResolver,
+ new ContentValues(),
+ Uri.EMPTY,
+ Settings.System.RINGTONE,
+ sourceRingtoneValue,
+ 0);
+
+ assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+ .isEqualTo(newRingtoneValueCanonicalized);
+ }
+
+ @Test
+ public void testRestoreValue_customRingtone_notificationSound_useCustomLookup_success() {
+ final String sourceRingtoneValue =
+ "content://0@media/external/audio/media/2?title=notificationPing&canonical=1";
+ final String newRingtoneValueUncanonicalized =
+ "content://0@media/external/audio/media/200";
+ final String newRingtoneValueCanonicalized =
+ "content://0@media/external/audio/media/200?title=notificationPing&canonicalize=1";
+
+ MockContentResolver mMockContentResolver = new MockContentResolver();
+ when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+ MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
+ cursor.addRow(new Object[] {200L});
+
+ ContentProvider mockMediaContentProvider =
+ new MockContentProvider(mContext) {
+ @Override
+ public Uri uncanonicalize(Uri url) {
+ // mock the lookup failure in regular MediaProvider.uncanonicalize.
+ return null;
+ }
+
+ @Override
+ public Uri canonicalize(Uri url) {
+ assertThat(url).isEqualTo(Uri.parse(newRingtoneValueUncanonicalized));
+ return Uri.parse(newRingtoneValueCanonicalized);
+ }
+
+ @Override
+ public String getType(Uri url) {
+ return "audio/ogg";
+ }
+
+ @Override
+ public Cursor query(
+ Uri uri,
+ String[] projection,
+ String selection,
+ String[] selectionArgs,
+ String sortOrder) {
+ assertThat(uri)
+ .isEqualTo(Uri.parse("content://0@media/external/audio/media"));
+ assertThat(projection).isEqualTo(new String[] {"_id"});
+ assertThat(selection).isEqualTo("is_notification=1 AND title=?");
+ assertThat(selectionArgs).isEqualTo(new String[] {"notificationPing"});
+ return cursor;
+ }
+ };
+
+ ContentProvider mockSettingsContentProvider =
+ new MockSettingsProvider(mContext, getContentResolver());
+ mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+ resetRingtoneSettingsToDefault(mMockContentResolver);
+
+ mSettingsHelper.restoreValue(
+ mContext,
+ mMockContentResolver,
+ new ContentValues(),
+ Uri.EMPTY,
+ Settings.System.NOTIFICATION_SOUND,
+ sourceRingtoneValue,
+ 0);
+
+ assertThat(
+ Settings.System.getString(
+ mMockContentResolver, Settings.System.NOTIFICATION_SOUND))
+ .isEqualTo(newRingtoneValueCanonicalized);
+ }
+
+ @Test
+ public void testRestoreValue_customRingtone_alarmSound_useCustomLookup_success() {
+ final String sourceRingtoneValue =
+ "content://0@media/external/audio/media/3?title=alarmSound&canonical=1";
+ final String newRingtoneValueUncanonicalized =
+ "content://0@media/external/audio/media/300";
+ final String newRingtoneValueCanonicalized =
+ "content://0@media/external/audio/media/300?title=alarmSound&canonical=1";
+
+ MockContentResolver mMockContentResolver = new MockContentResolver();
+ when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+ MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
+ cursor.addRow(new Object[] {300L});
+
+ ContentProvider mockMediaContentProvider =
+ new MockContentProvider(mContext) {
+ @Override
+ public Uri uncanonicalize(Uri url) {
+ // mock the lookup failure in regular MediaProvider.uncanonicalize.
+ return null;
+ }
+
+ @Override
+ public Uri canonicalize(Uri url) {
+ assertThat(url).isEqualTo(Uri.parse(newRingtoneValueUncanonicalized));
+ return Uri.parse(newRingtoneValueCanonicalized);
+ }
+
+ @Override
+ public String getType(Uri url) {
+ return "audio/ogg";
+ }
+
+ @Override
+ public Cursor query(
+ Uri uri,
+ String[] projection,
+ String selection,
+ String[] selectionArgs,
+ String sortOrder) {
+ assertThat(uri)
+ .isEqualTo(Uri.parse("content://0@media/external/audio/media"));
+ assertThat(projection).isEqualTo(new String[] {"_id"});
+ assertThat(selection).isEqualTo("is_alarm=1 AND title=?");
+ assertThat(selectionArgs).isEqualTo(new String[] {"alarmSound"});
+ return cursor;
+ }
+ };
+
+ ContentProvider mockSettingsContentProvider =
+ new MockSettingsProvider(mContext, getContentResolver());
+ mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+ resetRingtoneSettingsToDefault(mMockContentResolver);
+
+ mSettingsHelper.restoreValue(
+ mContext,
+ mMockContentResolver,
+ new ContentValues(),
+ Uri.EMPTY,
+ Settings.System.ALARM_ALERT,
+ sourceRingtoneValue,
+ 0);
+
+ assertThat(Settings.System.getString(mMockContentResolver, Settings.System.ALARM_ALERT))
+ .isEqualTo(newRingtoneValueCanonicalized);
+ }
+
+ @Test
+ public void testRestoreValue_customRingtone_useCustomLookup_multipleResults_notRestore() {
+ final String sourceRingtoneValue =
+ "content://0@media/external/audio/media/1?title=Song&canonical=1";
+
+ MockContentResolver mMockContentResolver = new MockContentResolver();
+ when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+ // This is to mock the case that there are multiple results by querying title +
+ // ringtone_type.
+ MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
+ cursor.addRow(new Object[] {100L});
+ cursor.addRow(new Object[] {110L});
+
+ ContentProvider mockMediaContentProvider =
+ new MockContentProvider(mContext) {
+ @Override
+ public Uri uncanonicalize(Uri url) {
+ // mock the lookup failure in regular MediaProvider.uncanonicalize.
+ return null;
+ }
+
+ @Override
+ public String getType(Uri url) {
+ return "audio/ogg";
+ }
+ };
+
+ ContentProvider mockSettingsContentProvider =
+ new MockSettingsProvider(mContext, getContentResolver());
+ mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+ resetRingtoneSettingsToDefault(mMockContentResolver);
+
+ mSettingsHelper.restoreValue(
+ mContext,
+ mMockContentResolver,
+ new ContentValues(),
+ Uri.EMPTY,
+ Settings.System.RINGTONE,
+ sourceRingtoneValue,
+ 0);
+
+ assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+ .isEqualTo(DEFAULT_RINGTONE_VALUE);
+ }
+
+ @Test
+ public void testRestoreValue_customRingtone_restoreSilentValue() {
+ MockContentResolver mMockContentResolver = new MockContentResolver();
+ when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+ ContentProvider mockMediaContentProvider =
+ new MockContentProvider(mContext) {
+ @Override
+ public Uri uncanonicalize(Uri url) {
+ // mock the lookup failure in regular MediaProvider.uncanonicalize.
+ return null;
+ }
+
+ @Override
+ public String getType(Uri url) {
+ return "audio/ogg";
+ }
+ };
+
+ ContentProvider mockSettingsContentProvider =
+ new MockSettingsProvider(mContext, getContentResolver());
+ mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+ mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+ resetRingtoneSettingsToDefault(mMockContentResolver);
+
+ mSettingsHelper.restoreValue(
+ mContext,
+ mMockContentResolver,
+ new ContentValues(),
+ Uri.EMPTY,
+ Settings.System.RINGTONE,
+ "_silent",
+ 0);
+
+ assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+ .isEqualTo(null);
+ }
+
+ public static class MockSettingsProvider extends MockContentProvider {
+ ContentResolver mBaseContentResolver;
+
+ public MockSettingsProvider(Context context, ContentResolver baseContentResolver) {
+ super(context);
+ this.mBaseContentResolver = baseContentResolver;
+ }
+
+ @Override
+ public Bundle call(String method, String request, Bundle args) {
+ return mBaseContentResolver.call(Settings.AUTHORITY, method, request, args);
+ }
+ }
+
+ @Test
public void restoreValue_autoRotation_deviceStateAutoRotationDisabled_restoresValue() {
when(mResources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults))
.thenReturn(new String[]{});
@@ -400,4 +788,20 @@
Settings.Global.putString(cr, Settings.Global.POWER_BUTTON_LONG_PRESS, null);
Settings.Global.putString(cr, Settings.Global.KEY_CHORD_POWER_VOLUME_UP, null);
}
+
+ private void resetRingtoneSettingsToDefault(ContentResolver contentResolver) {
+ Settings.System.putString(
+ contentResolver, Settings.System.RINGTONE, DEFAULT_RINGTONE_VALUE);
+ Settings.System.putString(
+ contentResolver, Settings.System.NOTIFICATION_SOUND, DEFAULT_NOTIFICATION_VALUE);
+ Settings.System.putString(
+ contentResolver, Settings.System.ALARM_ALERT, DEFAULT_ALARM_VALUE);
+
+ assertThat(Settings.System.getString(contentResolver, Settings.System.RINGTONE))
+ .isEqualTo(DEFAULT_RINGTONE_VALUE);
+ assertThat(Settings.System.getString(contentResolver, Settings.System.NOTIFICATION_SOUND))
+ .isEqualTo(DEFAULT_NOTIFICATION_VALUE);
+ assertThat(Settings.System.getString(contentResolver, Settings.System.ALARM_ALERT))
+ .isEqualTo(DEFAULT_ALARM_VALUE);
+ }
}
diff --git a/packages/SystemUI/res-keyguard/layout-land/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout-land/keyguard_pin_view.xml
new file mode 100644
index 0000000..cd7ab98
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout-land/keyguard_pin_view.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+**
+** 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.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <include layout="@layout/keyguard_pin_view_landscape" />
+
+</FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/layout-sw600dp-land/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout-sw600dp-land/keyguard_pin_view.xml
new file mode 100644
index 0000000..80cc8c0
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout-sw600dp-land/keyguard_pin_view.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+**
+** 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.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <include layout="@layout/keyguard_pin_view_portrait" />
+
+</FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view_landscape.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view_landscape.xml
new file mode 100644
index 0000000..e00742d
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view_landscape.xml
@@ -0,0 +1,231 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+**
+** 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.
+*/
+-->
+
+<com.android.keyguard.KeyguardPINView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/keyguard_pin_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_horizontal|bottom"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="horizontal">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="2"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layoutDirection="ltr"
+ android:orientation="vertical">
+
+ <include layout="@layout/keyguard_bouncer_message_area" />
+
+ <com.android.systemui.bouncer.ui.BouncerMessageView
+ android:id="@+id/bouncer_message_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ androidprv:layout_constraintBottom_toTopOf="@+id/row0"
+ androidprv:layout_constraintTop_toTopOf="parent"
+ androidprv:layout_constraintVertical_chainStyle="packed" />
+
+ <!-- Set this to be just above key1. It would be better to introduce a barrier above
+ key1/key2/key3, then place this View above that. Sadly, that doesn't work (the Barrier
+ drops to the bottom of the page, and key1/2/3 all shoot up to the top-left). In any
+ case, the Flow should ensure that key1/2/3 all have the same top, so this should be
+ fine. -->
+ <com.android.keyguard.AlphaOptimizedRelativeLayout
+ android:id="@+id/row0"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom"
+ androidprv:layout_constraintBottom_toTopOf="@+id/keyguard_selector_fade_container"
+ androidprv:layout_constraintTop_toBottomOf="@+id/bouncer_message_view"
+ tools:layout_editor_absoluteX="-16dp">
+
+ <com.android.keyguard.PasswordTextView
+ android:id="@+id/pinEntry"
+ style="@style/Widget.TextView.Password"
+ android:layout_width="@dimen/keyguard_security_width"
+ android:layout_height="@dimen/keyguard_password_height"
+ android:layout_centerHorizontal="true"
+ android:layout_marginRight="72dp"
+ android:contentDescription="@string/keyguard_accessibility_pin_area"
+ androidprv:scaledTextSize="@integer/scaled_password_text_size" />
+ </com.android.keyguard.AlphaOptimizedRelativeLayout>
+
+ <include
+ android:id="@+id/keyguard_selector_fade_container"
+ layout="@layout/keyguard_eca"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|center_horizontal"
+ android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
+ android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+ android:gravity="center_horizontal"
+ android:orientation="vertical"
+ androidprv:layout_constraintBottom_toBottomOf="parent" />
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/pin_container"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="3"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layoutDirection="ltr"
+ android:orientation="vertical">
+
+ <!-- Guideline used to place the top row of keys relative to the screen height. This will be
+ updated in KeyguardPINView to reduce the height of the PIN pad. -->
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/pin_pad_top_guideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ androidprv:layout_constraintGuide_percent="0" />
+
+ <com.android.keyguard.KeyguardPinFlowView
+ android:id="@+id/flow1"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="horizontal"
+
+ androidprv:constraint_referenced_ids="key1,key2,key3,key4,key5,key6,key7,key8,key9,delete_button,key0,key_enter"
+
+ androidprv:flow_horizontalGap="@dimen/num_pad_key_margin_end"
+
+ androidprv:flow_horizontalStyle="packed"
+ androidprv:flow_maxElementsWrap="3"
+
+ androidprv:flow_verticalBias="0.5"
+ androidprv:flow_verticalGap="@dimen/num_pad_entry_row_margin_bottom"
+ androidprv:flow_verticalStyle="packed"
+
+ androidprv:flow_wrapMode="aligned"
+ androidprv:layout_constraintBottom_toBottomOf="parent"
+ androidprv:layout_constraintEnd_toEndOf="parent"
+ androidprv:layout_constraintStart_toStartOf="parent"
+ androidprv:layout_constraintTop_toBottomOf="@id/pin_pad_top_guideline" />
+
+ <com.android.keyguard.NumPadButton
+ android:id="@+id/delete_button"
+ style="@style/NumPadKey.Delete"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key0"
+ android:contentDescription="@string/keyboardview_keycode_delete" />
+
+ <com.android.keyguard.NumPadButton
+ android:id="@+id/key_enter"
+ style="@style/NumPadKey.Enter"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:contentDescription="@string/keyboardview_keycode_enter" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key1"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key2"
+ androidprv:digit="1"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key2"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key3"
+ androidprv:digit="2"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key3"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key4"
+ androidprv:digit="3"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key4"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key5"
+ androidprv:digit="4"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key5"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key6"
+ androidprv:digit="5"
+ androidprv:textView="@+id/pinEntry" />
+
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key6"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key7"
+ androidprv:digit="6"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key7"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key8"
+ androidprv:digit="7"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key8"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key9"
+ androidprv:digit="8"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key9"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/delete_button"
+ androidprv:digit="9"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key0"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key_enter"
+ androidprv:digit="0"
+ androidprv:textView="@+id/pinEntry" />
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+</com.android.keyguard.KeyguardPINView>
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 36f7b96..66c57fc 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -22,6 +22,42 @@
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/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index c2dba6c..261b08d 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -40,4 +40,7 @@
<!-- Whether face auth will immediately stop when the display state is OFF -->
<bool name="flag_stop_face_auth_on_display_off">false</bool>
+
+ <!-- Whether we want to stop pulsing while running the face scanning animation -->
+ <bool name="flag_stop_pulsing_face_scanning_animation">true</bool>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 0befb3b..6ea8df8 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -747,6 +747,9 @@
off. This means a separate profile on a user's phone that's specifically for their
work apps and managed by their company. "Work" is used as an adjective. [CHAR LIMIT=NONE] -->
<string name="quick_settings_work_mode_label">Work apps</string>
+ <!-- QuickSettings: Subtitle for the Work profile Quick Settings tile when it's in the off
+ state. This corresponds to the work profile not being currently accessible. [CHAR LIMIT=20] -->
+ <string name="quick_settings_work_mode_paused_state">Paused</string>
<!-- QuickSettings: Label for the toggle to activate Night display (renamed "Night Light" with title caps). [CHAR LIMIT=20] -->
<string name="quick_settings_night_display_label">Night Light</string>
<!-- QuickSettings: Secondary text for when the Night Light will be enabled at sunset. [CHAR LIMIT=20] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 1f6b09b..5dbd014 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -265,7 +265,8 @@
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/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 4e1cbc7..4553a56 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -33,7 +33,6 @@
import android.app.admin.DevicePolicyManager;
import android.content.Intent;
import android.content.res.ColorStateList;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.hardware.biometrics.BiometricOverlayConstants;
import android.media.AudioManager;
@@ -69,6 +68,7 @@
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor;
import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate;
import com.android.systemui.biometrics.SideFpsController;
import com.android.systemui.biometrics.SideFpsUiRequestSource;
@@ -78,11 +78,10 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.model.SceneKey;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -128,6 +127,7 @@
private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
private final BouncerMessageInteractor mBouncerMessageInteractor;
private int mTranslationY;
+ private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
// Whether the volume keys should be handled by keyguard. If true, then
// they will be handled here for specific media types such as music, otherwise
// the audio service will bring up the volume dialog.
@@ -143,7 +143,7 @@
private Runnable mCancelAction;
private boolean mWillRunDismissFromKeyguard;
- private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
+ private int mLastOrientation;
private SecurityMode mCurrentSecurityMode = SecurityMode.Invalid;
private UserSwitcherController.UserSwitchCallback mUserSwitchCallback =
@@ -301,6 +301,10 @@
mViewMediatorCallback.keyguardDone(fromPrimaryAuth, targetUserId);
}
}
+
+ if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ mKeyguardTransitionInteractor.startDismissKeyguardTransition();
+ }
}
@Override
@@ -349,7 +353,14 @@
@Override
public void onDensityOrFontScaleChanged() {
- KeyguardSecurityContainerController.this.onDensityOrFontScaleChanged();
+ KeyguardSecurityContainerController.this
+ .onDensityOrFontScaleOrOrientationChanged();
+ }
+
+ @Override
+ public void onOrientationChanged(int orientation) {
+ KeyguardSecurityContainerController.this
+ .onDensityOrFontScaleOrOrientationChanged();
}
};
private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
@@ -388,7 +399,7 @@
}
};
private final UserInteractor mUserInteractor;
- private final Provider<SceneInteractor> mSceneInteractor;
+ private final Provider<AuthenticationInteractor> mAuthenticationInteractor;
private final Provider<JavaAdapter> mJavaAdapter;
@Nullable private Job mSceneTransitionCollectionJob;
@@ -419,7 +430,8 @@
Provider<JavaAdapter> javaAdapter,
UserInteractor userInteractor,
FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
- Provider<SceneInteractor> sceneInteractor
+ KeyguardTransitionInteractor keyguardTransitionInteractor,
+ Provider<AuthenticationInteractor> authenticationInteractor
) {
super(view);
view.setAccessibilityDelegate(faceAuthAccessibilityDelegate);
@@ -448,8 +460,9 @@
mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
mBouncerMessageInteractor = bouncerMessageInteractor;
mUserInteractor = userInteractor;
- mSceneInteractor = sceneInteractor;
+ mAuthenticationInteractor = authenticationInteractor;
mJavaAdapter = javaAdapter;
+ mKeyguardTransitionInteractor = keyguardTransitionInteractor;
}
@Override
@@ -474,19 +487,21 @@
showPrimarySecurityScreen(false);
if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
- // When the scene framework transitions from bouncer to gone, we dismiss the keyguard.
+ // When the scene framework says that the lockscreen has been dismissed, dismiss the
+ // keyguard here, revealing the underlying app or launcher:
mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
- mSceneInteractor.get().finishedSceneTransitions(
- /* from= */ SceneKey.Bouncer.INSTANCE,
- /* to= */ SceneKey.Gone.INSTANCE),
- unused -> {
- final int selectedUserId = mUserInteractor.getSelectedUserId();
- showNextSecurityScreenOrFinish(
+ mAuthenticationInteractor.get().isLockscreenDismissed(),
+ isLockscreenDismissed -> {
+ if (isLockscreenDismissed) {
+ final int selectedUserId = mUserInteractor.getSelectedUserId();
+ showNextSecurityScreenOrFinish(
/* authenticated= */ true,
selectedUserId,
/* bypassSecondaryLockScreen= */ true,
mSecurityModel.getSecurityMode(selectedUserId));
- });
+ }
+ }
+ );
}
}
@@ -1154,7 +1169,7 @@
}
/** Handles density or font scale changes. */
- private void onDensityOrFontScaleChanged() {
+ private void onDensityOrFontScaleOrOrientationChanged() {
reinflateViewFlipper(controller -> mView.onDensityOrFontScaleChanged());
}
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index 1a0c7f9..8611dbbb 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -22,6 +22,7 @@
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
@@ -154,11 +155,16 @@
}
float getLocationTop() {
- return mLockIconCenter.y - mRadius;
+ Rect r = new Rect();
+ mLockIcon.getGlobalVisibleRect(r);
+ return r.top;
}
float getLocationBottom() {
- return mLockIconCenter.y + mRadius;
+ Rect r = new Rect();
+ mLockIcon.getGlobalVisibleRect(r);
+ return r.bottom;
+
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
index a04a48d..e773416 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -58,6 +58,7 @@
private float mStartRadius;
private float mEndRadius;
private int mHeight;
+ private int mWidth;
private static final int EXPAND_ANIMATION_MS = 100;
private static final int EXPAND_COLOR_ANIMATION_MS = 50;
@@ -95,11 +96,17 @@
mBackground.setCornerRadius(mEndRadius + (mStartRadius - mEndRadius) * progress);
int height = (int) (mHeight * 0.7f + mHeight * 0.3 * progress);
int difference = mHeight - height;
- mBackground.setBounds(0, difference / 2, mHeight, mHeight - difference / 2);
+
+ int left = 0;
+ int top = difference / 2;
+ int right = mWidth;
+ int bottom = mHeight - difference / 2;
+ mBackground.setBounds(left, top, right, bottom);
}
- void onLayout(int height) {
+ void onLayout(int width, int height) {
boolean shouldUpdateHeight = height != mHeight;
+ mWidth = width;
mHeight = height;
mStartRadius = height / 2f;
mEndRadius = height / 4f;
@@ -121,7 +128,7 @@
ContextThemeWrapper ctw = new ContextThemeWrapper(context, mStyle);
@SuppressLint("ResourceType") TypedArray a = ctw.obtainStyledAttributes(customAttrs);
mNormalBackgroundColor = getPrivateAttrColorIfUnset(ctw, a, 0, 0,
- NUM_PAD_BACKGROUND);
+ NUM_PAD_BACKGROUND);
a.recycle();
mPressedBackgroundColor = getColorAttrDefaultColor(context, NUM_PAD_BACKGROUND_PRESSED);
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index 3f1741a6..5c2f3b3 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -74,8 +74,9 @@
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
-
- if (mAnimator != null) mAnimator.onLayout(b - t);
+ int width = r - l;
+ int height = b - t;
+ if (mAnimator != null) mAnimator.onLayout(width, height);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index edc298c..466d154 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -211,7 +211,9 @@
left = centerX - mKlondikeText.getMeasuredWidth() / 2;
mKlondikeText.layout(left, top, left + mKlondikeText.getMeasuredWidth(), bottom);
- if (mAnimator != null) mAnimator.onLayout(b - t);
+ int width = r - l;
+ int height = b - t;
+ if (mAnimator != null) mAnimator.onLayout(width, height);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
index 403c809..95e2dba 100644
--- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -36,6 +36,8 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.settingslib.Utils
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.log.ScreenDecorationsLogger
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.util.asIndenting
@@ -54,6 +56,7 @@
val mainExecutor: Executor,
val logger: ScreenDecorationsLogger,
val authController: AuthController,
+ val featureFlags: FeatureFlags,
) : ScreenDecorations.DisplayCutoutView(context, pos) {
private var showScanningAnim = false
private val rimPaint = Paint()
@@ -294,6 +297,15 @@
}
private fun createFaceScanningRimAnimator(): AnimatorSet {
+ val dontPulse = featureFlags.isEnabled(Flags.STOP_PULSING_FACE_SCANNING_ANIMATION)
+ if (dontPulse) {
+ return AnimatorSet().apply {
+ playSequentially(
+ cameraProtectionAnimator,
+ createRimAppearAnimator(),
+ )
+ }
+ }
return AnimatorSet().apply {
playSequentially(
cameraProtectionAnimator,
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 7519202..5dec485 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
@@ -27,6 +27,8 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
@@ -42,6 +44,7 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -57,6 +60,7 @@
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val userRepository: UserRepository,
private val keyguardRepository: KeyguardRepository,
+ sceneInteractor: SceneInteractor,
private val clock: SystemClock,
) {
/**
@@ -93,7 +97,7 @@
repository.isUnlocked,
authenticationMethod,
) { isUnlocked, authenticationMethod ->
- authenticationMethod is DomainLayerAuthenticationMethodModel.None || isUnlocked
+ !authenticationMethod.isSecure || isUnlocked
}
.stateIn(
scope = applicationScope,
@@ -101,6 +105,48 @@
initialValue = true,
)
+ /**
+ * Whether the lockscreen has been dismissed (by any method). This can be false even when the
+ * device is unlocked, e.g. when swipe to unlock is enabled.
+ *
+ * Note:
+ * - `false` doesn't mean the lockscreen is visible (it may be occluded or covered by other UI).
+ * - `true` doesn't mean the lockscreen is invisible (since this state changes before the
+ * transition occurs).
+ */
+ val isLockscreenDismissed: StateFlow<Boolean> =
+ sceneInteractor.desiredScene
+ .map { it.key }
+ .filter { currentScene ->
+ currentScene == SceneKey.Gone || currentScene == SceneKey.Lockscreen
+ }
+ .map { it == SceneKey.Gone }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
+ /**
+ * Whether it's currently possible to swipe up to dismiss the lockscreen without requiring
+ * authentication. This returns false whenever the lockscreen has been dismissed.
+ *
+ * Note: `true` doesn't mean the lockscreen is visible. It may be occluded or covered by other
+ * UI.
+ */
+ val canSwipeToDismiss =
+ combine(authenticationMethod, isLockscreenDismissed) {
+ authenticationMethod,
+ isLockscreenDismissed ->
+ authenticationMethod is DomainLayerAuthenticationMethodModel.Swipe &&
+ !isLockscreenDismissed
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
/** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */
val throttling: StateFlow<AuthenticationThrottlingModel> = repository.throttling
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
index b538085..1ca57e7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
@@ -60,6 +60,13 @@
return dp * (density / DisplayMetrics.DENSITY_DEFAULT)
}
+ /**
+ * Note: Talkback 14.0 has new rate-limitation design to reduce frequency
+ * of TYPE_WINDOW_CONTENT_CHANGED events to once every 30 seconds.
+ * (context: b/281765653#comment18)
+ * Using {@link View#announceForAccessibility} instead as workaround when sending events
+ * exceeding this frequency is required.
+ */
@JvmStatic
fun notifyAccessibilityContentChanged(am: AccessibilityManager, view: ViewGroup) {
if (!am.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 7b78761..a4cd3fa 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -45,7 +45,6 @@
import com.android.systemui.biometrics.AuthBiometricViewAdapter
import com.android.systemui.biometrics.AuthIconController
import com.android.systemui.biometrics.AuthPanelController
-import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.domain.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.PromptKind
@@ -80,9 +79,6 @@
applicationScope: CoroutineScope,
): AuthBiometricViewAdapter {
val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!!
- fun notifyAccessibilityChanged() {
- Utils.notifyAccessibilityContentChanged(accessibilityManager, view)
- }
val textColorError =
view.resources.getColor(R.color.biometric_dialog_error, view.context.theme)
@@ -326,21 +322,14 @@
}
}
- // not sure why this is here, but the legacy code did it probably needed?
- launch {
- viewModel.isAuthenticating.collect { isAuthenticating ->
- if (isAuthenticating) {
- notifyAccessibilityChanged()
- }
- }
- }
-
// dismiss prompt when authenticated and confirmed
launch {
viewModel.isAuthenticated.collect { authState ->
// Disable background view for cancelling authentication once authenticated,
// and remove from talkback
if (authState.isAuthenticated) {
+ // Prevents Talkback from speaking subtitle after already authenticated
+ subtitleView.importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
backgroundView.setOnClickListener(null)
backgroundView.importantForAccessibility =
IMPORTANT_FOR_ACCESSIBILITY_NO
@@ -349,7 +338,6 @@
view.announceForAccessibility(
view.resources.getString(R.string.biometric_dialog_authenticated)
)
- notifyAccessibilityChanged()
launch {
delay(authState.delay)
@@ -381,7 +369,18 @@
!accessibilityManager.isEnabled ||
!accessibilityManager.isTouchExplorationEnabled
- notifyAccessibilityChanged()
+ /**
+ * Note: Talkback 14.0 has new rate-limitation design to reduce frequency of
+ * TYPE_WINDOW_CONTENT_CHANGED events to once every 30 seconds. (context:
+ * b/281765653#comment18) Using {@link View#announceForAccessibility}
+ * instead as workaround since sending events exceeding this frequency is
+ * required.
+ */
+ indicatorMessageView?.text?.let {
+ if (it.isNotBlank()) {
+ view.announceForAccessibility(it)
+ }
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/AndroidInternalsModule.java b/packages/SystemUI/src/com/android/systemui/dagger/AndroidInternalsModule.java
index 0992882..585c390 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/AndroidInternalsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/AndroidInternalsModule.java
@@ -24,11 +24,11 @@
import com.android.internal.util.NotificationMessagingUtil;
import com.android.internal.widget.LockPatternUtils;
-import javax.inject.Singleton;
-
import dagger.Module;
import dagger.Provides;
+import javax.inject.Singleton;
+
/**
* Provides items imported from com.android.internal.
*/
@@ -51,7 +51,7 @@
/** */
@Provides
public NotificationMessagingUtil provideNotificationMessagingUtil(Context context) {
- return new NotificationMessagingUtil(context);
+ return new NotificationMessagingUtil(context, null);
}
/** Provides an instance of {@link com.android.internal.logging.UiEventLogger} */
diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
index 4e62104..ac0d3c8 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -34,6 +34,7 @@
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
import com.android.systemui.log.ScreenDecorationsLogger
import com.android.systemui.plugins.statusbar.StatusBarStateController
import java.util.concurrent.Executor
@@ -47,6 +48,7 @@
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@Main private val mainExecutor: Executor,
private val logger: ScreenDecorationsLogger,
+ private val featureFlags: FeatureFlags,
) : DecorProviderFactory() {
private val display = context.display
private val displayInfo = DisplayInfo()
@@ -86,6 +88,7 @@
keyguardUpdateMonitor,
mainExecutor,
logger,
+ featureFlags,
)
)
}
@@ -110,6 +113,7 @@
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val mainExecutor: Executor,
private val logger: ScreenDecorationsLogger,
+ private val featureFlags: FeatureFlags,
) : BoundDecorProvider() {
override val viewId: Int = com.android.systemui.R.id.face_scanning_anim
@@ -144,6 +148,7 @@
mainExecutor,
logger,
authController,
+ featureFlags
)
view.id = viewId
view.setColor(tintColor)
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 7c816ce..34a80e8 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -26,9 +26,9 @@
import android.text.format.Formatter;
import android.util.Log;
+import com.android.systemui.DejankUtils;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.dagger.DozeScope;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.util.AlarmTimeout;
import com.android.systemui.util.wakelock.WakeLock;
@@ -52,15 +52,21 @@
private final boolean mCanAnimateTransition;
private final DozeParameters mDozeParameters;
private final DozeLog mDozeLog;
- private final StatusBarStateController mStatusBarStateController;
private long mLastTimeTickElapsed = 0;
+ // If time tick is scheduled and there's not a pending runnable to cancel:
+ private boolean mTimeTickScheduled;
+ private final Runnable mCancelTimeTickerRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mTimeTicker.cancel();
+ }
+ };
@Inject
public DozeUi(Context context, AlarmManager alarmManager,
WakeLock wakeLock, DozeHost host, @Main Handler handler,
DozeParameters params,
- StatusBarStateController statusBarStateController,
DozeLog dozeLog) {
mContext = context;
mWakeLock = wakeLock;
@@ -70,7 +76,6 @@
mDozeParameters = params;
mTimeTicker = new AlarmTimeout(alarmManager, this::onTimeTick, "doze_time_tick", handler);
mDozeLog = dozeLog;
- mStatusBarStateController = statusBarStateController;
}
@Override
@@ -157,13 +162,15 @@
}
private void scheduleTimeTick() {
- if (mTimeTicker.isScheduled()) {
+ if (mTimeTickScheduled) {
return;
}
+ mTimeTickScheduled = true;
+ DejankUtils.removeCallbacks(mCancelTimeTickerRunnable);
long time = System.currentTimeMillis();
long delta = roundToNextMinute(time) - System.currentTimeMillis();
- boolean scheduled = mTimeTicker.schedule(delta, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
+ boolean scheduled = mTimeTicker.schedule(delta, AlarmTimeout.MODE_RESCHEDULE_IF_SCHEDULED);
if (scheduled) {
mDozeLog.traceTimeTickScheduled(time, time + delta);
}
@@ -171,11 +178,11 @@
}
private void unscheduleTimeTick() {
- if (!mTimeTicker.isScheduled()) {
+ if (!mTimeTickScheduled) {
return;
}
- verifyLastTimeTick();
- mTimeTicker.cancel();
+ mTimeTickScheduled = false;
+ DejankUtils.postAfterTraversal(mCancelTimeTickerRunnable);
}
private void verifyLastTimeTick() {
@@ -205,6 +212,7 @@
// Keep wakelock until a frame has been pushed.
mHandler.post(mWakeLock.wrap(() -> {}));
+ mTimeTickScheduled = false;
scheduleTimeTick();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
index ae40f7e8..7150d69e 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
@@ -432,9 +432,11 @@
}
private inline fun PrintWriter.wrapSection(entry: DumpsysEntry, block: () -> Unit) {
+ Trace.beginSection(entry.name)
preamble(entry)
val dumpTime = measureTimeMillis(block)
footer(entry, dumpTime)
+ Trace.endSection()
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt
index f7e6b98..2e9d04b 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt
@@ -16,6 +16,7 @@
package com.android.systemui.dump
+import android.os.Trace
import java.io.PrintWriter
/**
@@ -83,31 +84,33 @@
) {
fun printTableData(pw: PrintWriter) {
+ Trace.beginSection("DumpsysTableLogger#printTableData")
printSectionStart(pw)
printSchema(pw)
printData(pw)
printSectionEnd(pw)
+ Trace.endSection()
}
private fun printSectionStart(pw: PrintWriter) {
- pw.println(HEADER_PREFIX + sectionName)
- pw.println("version $VERSION")
+ pw.append(HEADER_PREFIX).println(sectionName)
+ pw.append("version ").println(VERSION)
}
private fun printSectionEnd(pw: PrintWriter) {
- pw.println(FOOTER_PREFIX + sectionName)
+ pw.append(FOOTER_PREFIX).println(sectionName)
}
private fun printSchema(pw: PrintWriter) {
- pw.println(columns.joinToString(separator = SEPARATOR))
+ columns.joinTo(pw, separator = SEPARATOR).println()
}
private fun printData(pw: PrintWriter) {
val count = columns.size
- rows
- .filter { it.size == count }
- .forEach { dataLine ->
- pw.println(dataLine.joinToString(separator = SEPARATOR))
+ rows.forEach { dataLine ->
+ if (dataLine.size == count) {
+ dataLine.joinTo(pw, separator = SEPARATOR).println()
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index e77d355..14563b7 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -252,8 +252,7 @@
/** Whether to listen for fingerprint authentication over keyguard occluding activities. */
// TODO(b/283260512): Tracking bug.
- @JvmField val FP_LISTEN_OCCLUDING_APPS = unreleasedFlag("fp_listen_occluding_apps",
- teamfood = true)
+ @JvmField val FP_LISTEN_OCCLUDING_APPS = releasedFlag("fp_listen_occluding_apps")
/** Flag meant to guard the talkback fix for the KeyguardIndicationTextView */
// TODO(b/286563884): Tracking bug
@@ -286,11 +285,30 @@
teamfood = true
)
+ /**
+ * TODO(b/278086361): Tracking bug
+ * Complete rewrite of the interactions between System UI and Window Manager involving keyguard
+ * state. When enabled, calls to ActivityTaskManagerService from System UI will exclusively
+ * occur from [WmLockscreenVisibilityManager] rather than the legacy KeyguardViewMediator.
+ *
+ * This flag is under development; some types of unlock may not animate properly if you enable
+ * it.
+ */
+ @JvmField
+ val KEYGUARD_WM_STATE_REFACTOR: UnreleasedFlag =
+ unreleasedFlag("keyguard_wm_state_refactor")
+
/** 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")
+ /** Flag to disable the face scanning animation pulsing. */
+ // TODO(b/295245791): Tracking bug.
+ @JvmField val STOP_PULSING_FACE_SCANNING_ANIMATION = resourceBooleanFlag(
+ R.bool.flag_stop_pulsing_face_scanning_animation,
+ "stop_pulsing_face_scanning_animation")
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag("power_menu_lite")
@@ -363,7 +381,7 @@
// TODO(b/292533677): Tracking Bug
val WIFI_TRACKER_LIB_FOR_WIFI_ICON =
- unreleasedFlag("wifi_tracker_lib_for_wifi_icon")
+ unreleasedFlag("wifi_tracker_lib_for_wifi_icon", teamfood = true)
// TODO(b/293863612): Tracking Bug
@JvmField val INCOMPATIBLE_CHARGING_BATTERY_ICON =
@@ -372,6 +390,9 @@
// TODO(b/293585143): Tracking Bug
val INSTANT_TETHER = unreleasedFlag("instant_tether")
+ // TODO(b/294588085): Tracking Bug
+ val WIFI_SECONDARY_NETWORKS = unreleasedFlag("wifi_secondary_networks")
+
// 700 - dialer/calls
// TODO(b/254512734): Tracking Bug
val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag("ongoing_call_status_bar_chip")
@@ -524,6 +545,12 @@
val ENABLE_PIP_APP_ICON_OVERLAY =
sysPropBooleanFlag("persist.wm.debug.enable_pip_app_icon_overlay", default = true)
+
+ // TODO(b/293252410) : Tracking Bug
+ @JvmField
+ val LOCKSCREEN_ENABLE_LANDSCAPE =
+ unreleasedFlag("lockscreen.enable_landscape")
+
// TODO(b/273443374): Tracking Bug
@Keep
@JvmField
@@ -750,4 +777,8 @@
/** Enable the Compose implementation of the Quick Settings footer actions. */
@JvmField
val COMPOSE_QS_FOOTER_ACTIONS = unreleasedFlag("compose_qs_footer_actions")
+
+ /** Enable the share wifi button in Quick Settings internet dialog. */
+ @JvmField
+ val SHARE_WIFI_QS_BUTTON = unreleasedFlag("share_wifi_qs_button")
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index e6053fb..9d2771e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -73,6 +73,14 @@
import com.android.internal.policy.IKeyguardStateCallback;
import com.android.keyguard.mediator.ScreenOnCoordinator;
import com.android.systemui.SystemUIApplication;
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier;
+import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindViewBinder;
+import com.android.systemui.keyguard.ui.binder.WindowManagerLockscreenVisibilityViewBinder;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSurfaceBehindViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibilityViewModel;
import com.android.systemui.settings.DisplayTracker;
import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
@@ -85,10 +93,13 @@
import javax.inject.Inject;
+import kotlinx.coroutines.CoroutineScope;
+
public class KeyguardService extends Service {
static final String TAG = "KeyguardService";
static final String PERMISSION = android.Manifest.permission.CONTROL_KEYGUARD;
+ private final FeatureFlags mFlags;
private final KeyguardViewMediator mKeyguardViewMediator;
private final KeyguardLifecyclesDispatcher mKeyguardLifecyclesDispatcher;
private final ScreenOnCoordinator mScreenOnCoordinator;
@@ -291,13 +302,33 @@
KeyguardLifecyclesDispatcher keyguardLifecyclesDispatcher,
ScreenOnCoordinator screenOnCoordinator,
ShellTransitions shellTransitions,
- DisplayTracker displayTracker) {
+ DisplayTracker displayTracker,
+ WindowManagerLockscreenVisibilityViewModel
+ wmLockscreenVisibilityViewModel,
+ WindowManagerLockscreenVisibilityManager wmLockscreenVisibilityManager,
+ KeyguardSurfaceBehindViewModel keyguardSurfaceBehindViewModel,
+ KeyguardSurfaceBehindParamsApplier keyguardSurfaceBehindAnimator,
+ @Application CoroutineScope scope,
+ FeatureFlags featureFlags) {
super();
mKeyguardViewMediator = keyguardViewMediator;
mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
mScreenOnCoordinator = screenOnCoordinator;
mShellTransitions = shellTransitions;
mDisplayTracker = displayTracker;
+ mFlags = featureFlags;
+
+ if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ WindowManagerLockscreenVisibilityViewBinder.bind(
+ wmLockscreenVisibilityViewModel,
+ wmLockscreenVisibilityManager,
+ scope);
+
+ KeyguardSurfaceBehindViewBinder.bind(
+ keyguardSurfaceBehindViewModel,
+ keyguardSurfaceBehindAnimator,
+ scope);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 489d2ab..ff74050 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -403,7 +403,9 @@
* the device.
*/
fun canPerformInWindowLauncherAnimations(): Boolean {
- return isNexusLauncherUnderneath() &&
+ // TODO(b/278086361): Refactor in-window animations.
+ return !featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR) &&
+ isNexusLauncherUnderneath() &&
// If the launcher is underneath, but we're about to launch an activity, don't do
// the animations since they won't be visible.
!notificationShadeWindowController.isLaunchingActivity &&
@@ -847,54 +849,57 @@
}
surfaceBehindRemoteAnimationTargets?.forEach { surfaceBehindRemoteAnimationTarget ->
- val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
+ if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ val surfaceHeight: Int =
+ surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
- var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
- (1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
- MathUtils.clamp(amount, 0f, 1f))
+ var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
+ (1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
+ MathUtils.clamp(amount, 0f, 1f))
- // If we're dismissing via swipe to the Launcher, we'll play in-window scale animations,
- // so don't also scale the window.
- if (keyguardStateController.isDismissingFromSwipe &&
- willUnlockWithInWindowLauncherAnimations) {
- scaleFactor = 1f
- }
-
- // Translate up from the bottom.
- surfaceBehindMatrix.setTranslate(
- surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
- surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
- surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
- )
-
- // Scale up from a point at the center-bottom of the surface.
- surfaceBehindMatrix.postScale(
- scaleFactor,
- scaleFactor,
- keyguardViewController.viewRootImpl.width / 2f,
- surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
- )
-
- // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
- // unable to draw
- val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget.leash
- if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
- sc?.isValid == true) {
- with(SurfaceControl.Transaction()) {
- setMatrix(sc, surfaceBehindMatrix, tmpFloat)
- setCornerRadius(sc, roundedCornerRadius)
- setAlpha(sc, animationAlpha)
- apply()
+ // If we're dismissing via swipe to the Launcher, we'll play in-window scale
+ // animations, so don't also scale the window.
+ if (keyguardStateController.isDismissingFromSwipe &&
+ willUnlockWithInWindowLauncherAnimations) {
+ scaleFactor = 1f
}
- } else {
- applyParamsToSurface(
- SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
- surfaceBehindRemoteAnimationTarget.leash)
- .withMatrix(surfaceBehindMatrix)
- .withCornerRadius(roundedCornerRadius)
- .withAlpha(animationAlpha)
- .build()
+
+ // Translate up from the bottom.
+ surfaceBehindMatrix.setTranslate(
+ surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
+ surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
+ surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
)
+
+ // Scale up from a point at the center-bottom of the surface.
+ surfaceBehindMatrix.postScale(
+ scaleFactor,
+ scaleFactor,
+ keyguardViewController.viewRootImpl.width / 2f,
+ surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
+ )
+
+ // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
+ // unable to draw
+ val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget.leash
+ if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+ sc?.isValid == true) {
+ with(SurfaceControl.Transaction()) {
+ setMatrix(sc, surfaceBehindMatrix, tmpFloat)
+ setCornerRadius(sc, roundedCornerRadius)
+ setAlpha(sc, animationAlpha)
+ apply()
+ }
+ } else {
+ applyParamsToSurface(
+ SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
+ surfaceBehindRemoteAnimationTarget.leash)
+ .withMatrix(surfaceBehindMatrix)
+ .withCornerRadius(roundedCornerRadius)
+ .withAlpha(animationAlpha)
+ .build()
+ )
+ }
}
}
@@ -929,28 +934,41 @@
/**
* 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 +978,6 @@
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() }
}
/**
@@ -977,10 +988,12 @@
if (keyguardStateController.isShowing) {
// Hide the keyguard, with no fade out since we animated it away during the unlock.
- keyguardViewController.hide(
- surfaceBehindRemoteAnimationStartTime,
- 0 /* fadeOutDuration */
- )
+ if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ keyguardViewController.hide(
+ surfaceBehindRemoteAnimationStartTime,
+ 0 /* fadeOutDuration */
+ )
+ }
} else {
Log.i(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
"showing. Ignoring...")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 2b6f77d..8e323d8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -137,6 +137,12 @@
fun bindIndicationArea() {
indicationAreaHandle?.dispose()
+ if (!featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ keyguardRootView.findViewById<View?>(R.id.keyguard_indication_area)?.let {
+ keyguardRootView.removeView(it)
+ }
+ }
+
indicationAreaHandle =
KeyguardIndicationAreaBinder.bind(
notificationShadeWindowView,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 66de371..fd15853 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -171,8 +171,6 @@
import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
-import dagger.Lazy;
-
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -182,6 +180,7 @@
import java.util.concurrent.Executor;
import java.util.function.Consumer;
+import dagger.Lazy;
import kotlinx.coroutines.CoroutineDispatcher;
/**
@@ -1035,12 +1034,19 @@
IRemoteAnimationFinishedCallback finishedCallback) {
Trace.beginSection("mExitAnimationRunner.onAnimationStart#startKeyguardExitAnimation");
startKeyguardExitAnimation(transit, apps, wallpapers, nonApps, finishedCallback);
+ if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ mWmLockscreenVisibilityManager.get().onKeyguardGoingAwayRemoteAnimationStart(
+ transit, apps, wallpapers, nonApps, finishedCallback);
+ }
Trace.endSection();
}
@Override // Binder interface
public void onAnimationCancelled() {
cancelKeyguardExitAnimation();
+ if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ mWmLockscreenVisibilityManager.get().onKeyguardGoingAwayRemoteAnimationCancelled();
+ }
}
};
@@ -1106,7 +1112,7 @@
mOccludeByDreamAnimator = ValueAnimator.ofFloat(0f, 1f);
mOccludeByDreamAnimator.setDuration(mDreamOpenAnimationDuration);
- mOccludeByDreamAnimator.setInterpolator(Interpolators.LINEAR);
+ //mOccludeByDreamAnimator.setInterpolator(Interpolators.LINEAR);
mOccludeByDreamAnimator.addUpdateListener(
animation -> {
SyncRtSurfaceTransactionApplier.SurfaceParams.Builder
@@ -1336,6 +1342,8 @@
mDreamingToLockscreenTransitionViewModel;
private RemoteAnimationTarget mRemoteAnimationTarget;
+ private Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
+
/**
* Injected constructor. See {@link KeyguardModule}.
*/
@@ -1379,7 +1387,8 @@
SystemClock systemClock,
@Main CoroutineDispatcher mainDispatcher,
Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
- SystemPropertiesHelper systemPropertiesHelper) {
+ SystemPropertiesHelper systemPropertiesHelper,
+ Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager) {
mContext = context;
mUserTracker = userTracker;
mFalsingCollector = falsingCollector;
@@ -1446,8 +1455,9 @@
mUiEventLogger = uiEventLogger;
mSessionTracker = sessionTracker;
- mMainDispatcher = mainDispatcher;
mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel;
+ mWmLockscreenVisibilityManager = wmLockscreenVisibilityManager;
+ mMainDispatcher = mainDispatcher;
}
public void userActivity() {
@@ -2561,7 +2571,7 @@
} 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;
@@ -2685,6 +2695,12 @@
if (DEBUG) {
Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ")");
}
+
+ if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ // Handled in WmLockscreenVisibilityManager if flag is enabled.
+ return;
+ }
+
try {
mActivityTaskManagerService.setLockScreenShown(showing, aodShowing);
} catch (RemoteException e) {
@@ -2724,7 +2740,11 @@
}
mHiding = false;
- mKeyguardViewControllerLazy.get().show(options);
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ // Handled directly in StatusBarKeyguardViewManager if enabled.
+ mKeyguardViewControllerLazy.get().show(options);
+ }
+
resetKeyguardDonePendingLocked();
mHideAnimationRun = false;
adjustStatusBarLocked();
@@ -2795,19 +2815,22 @@
mUpdateMonitor.setKeyguardGoingAway(true);
mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(true);
- // Don't actually hide the Keyguard at the moment, wait for window
- // manager until it tells us it's safe to do so with
- // startKeyguardExitAnimation.
- // Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager will be in
- // order.
- final int keyguardFlag = flags;
- mUiBgExecutor.execute(() -> {
- try {
- mActivityTaskManagerService.keyguardGoingAway(keyguardFlag);
- } catch (RemoteException e) {
- Log.e(TAG, "Error while calling WindowManager", e);
- }
- });
+ // Handled in WmLockscreenVisibilityManager if flag is enabled.
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ // Don't actually hide the Keyguard at the moment, wait for window manager
+ // until it tells us it's safe to do so with startKeyguardExitAnimation.
+ // Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager
+ // will be in order.
+ final int keyguardFlag = flags;
+ mUiBgExecutor.execute(() -> {
+ try {
+ mActivityTaskManagerService.keyguardGoingAway(keyguardFlag);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error while calling WindowManager", e);
+ }
+ });
+ }
+
Trace.endSection();
}
};
@@ -2919,7 +2942,10 @@
if (!mHiding
&& !mSurfaceBehindRemoteAnimationRequested
&& !mKeyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture()) {
- if (finishedCallback != null) {
+ // If the flag is enabled, remote animation state is handled in
+ // WmLockscreenVisibilityManager.
+ if (finishedCallback != null
+ && !mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
// There will not execute animation, send a finish callback to ensure the remote
// animation won't hang there.
try {
@@ -2945,10 +2971,12 @@
new IRemoteAnimationFinishedCallback() {
@Override
public void onAnimationFinished() throws RemoteException {
- try {
- finishedCallback.onAnimationFinished();
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to call onAnimationFinished", e);
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to call onAnimationFinished", e);
+ }
}
onKeyguardExitFinished();
mKeyguardViewControllerLazy.get().hide(0 /* startTime */,
@@ -2975,7 +3003,11 @@
// it will dismiss the panel in that case.
} else if (!mStatusBarStateController.leaveOpenOnKeyguardHide()
&& apps != null && apps.length > 0) {
- mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ // Handled in WmLockscreenVisibilityManager. Other logic in this class will
+ // short circuit when this is null.
+ mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
+ }
mSurfaceBehindRemoteAnimationRunning = true;
mInteractionJankMonitor.begin(
@@ -2995,7 +3027,10 @@
createInteractionJankMonitorConf(
CUJ_LOCKSCREEN_UNLOCK_ANIMATION, "RemoteAnimationDisabled"));
- mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ // Handled directly in StatusBarKeyguardViewManager if enabled.
+ mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
+ }
// TODO(bc-animation): When remote animation is enabled for keyguard exit animation,
// apps, wallpapers and finishedCallback are set to non-null. nonApps is not yet
@@ -3003,19 +3038,23 @@
mContext.getMainExecutor().execute(() -> {
if (finishedCallback == null) {
mKeyguardUnlockAnimationControllerLazy.get()
- .notifyFinishedKeyguardExitAnimation(false /* cancelled */);
+ .notifyFinishedKeyguardExitAnimation(false /* showKeyguard */);
mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
return;
}
if (apps == null || apps.length == 0) {
Slog.e(TAG, "Keyguard exit without a corresponding app to show.");
+
try {
- finishedCallback.onAnimationFinished();
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ finishedCallback.onAnimationFinished();
+ }
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException");
} finally {
mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
}
+
return;
}
@@ -3039,7 +3078,9 @@
@Override
public void onAnimationEnd(Animator animation) {
try {
- finishedCallback.onAnimationFinished();
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ finishedCallback.onAnimationFinished();
+ }
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException");
} finally {
@@ -3050,7 +3091,9 @@
@Override
public void onAnimationCancel(Animator animation) {
try {
- finishedCallback.onAnimationFinished();
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ finishedCallback.onAnimationFinished();
+ }
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException");
} finally {
@@ -3120,7 +3163,7 @@
// 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 +3172,7 @@
// 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 +3183,13 @@
* 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 +3214,7 @@
+ " wasShowing=" + wasShowing);
}
- mKeyguardUnlockAnimationControllerLazy.get()
- .notifyFinishedKeyguardExitAnimation(cancelled);
- finishSurfaceBehindRemoteAnimation(cancelled);
+ finishSurfaceBehindRemoteAnimation(showKeyguard);
// Dispatch the callback on animation finishes.
mUpdateMonitor.dispatchKeyguardDismissAnimationFinished();
@@ -3200,7 +3242,11 @@
flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
}
- mActivityTaskManagerService.keyguardGoingAway(flags);
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ // Handled in WmLockscreenVisibilityManager.
+ mActivityTaskManagerService.keyguardGoingAway(flags);
+ }
+
mKeyguardStateController.notifyKeyguardGoingAway(true);
} catch (RemoteException e) {
mSurfaceBehindRemoteAnimationRequested = false;
@@ -3236,7 +3282,10 @@
* 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/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
new file mode 100644
index 0000000..75677f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -0,0 +1,206 @@
+/*
+ * 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
+
+import android.app.IActivityTaskManager
+import android.util.Log
+import android.view.IRemoteAnimationFinishedCallback
+import android.view.RemoteAnimationTarget
+import android.view.WindowManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Manages lockscreen and AOD visibility state via the [IActivityTaskManager], and keeps track of
+ * remote animations related to changes in lockscreen visibility.
+ */
+@SysUISingleton
+class WindowManagerLockscreenVisibilityManager
+@Inject
+constructor(
+ @Main private val executor: Executor,
+ private val activityTaskManagerService: IActivityTaskManager,
+ private val keyguardStateController: KeyguardStateController,
+ private val keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier,
+) {
+
+ /**
+ * Whether the lockscreen is showing, which we pass to [IActivityTaskManager.setLockScreenShown]
+ * in order to show the lockscreen and hide the surface behind the keyguard (or the inverse).
+ */
+ private var isLockscreenShowing = true
+
+ /**
+ * Whether AOD is showing, which we pass to [IActivityTaskManager.setLockScreenShown] in order
+ * to show AOD when the lockscreen is visible.
+ */
+ private var isAodVisible = false
+
+ /**
+ * Whether the keyguard is currently "going away", which we triggered via a call to
+ * [IActivityTaskManager.keyguardGoingAway]. When we tell WM that the keyguard is going away,
+ * the app/launcher surface behind the keyguard is made visible, and WM calls
+ * [onKeyguardGoingAwayRemoteAnimationStart] with a RemoteAnimationTarget so that we can animate
+ * it.
+ *
+ * Going away does not inherently result in [isLockscreenShowing] being set to false; we need to
+ * do that ourselves once we are done animating the surface.
+ *
+ * THIS IS THE ONLY PLACE 'GOING AWAY' TERMINOLOGY SHOULD BE USED. 'Going away' is a WM concept
+ * and we have gotten into trouble using it to mean various different things in the past. Unlock
+ * animations may still be visible when the keyguard is NOT 'going away', for example, when we
+ * play in-window animations, we set the surface to alpha=1f and end the animation immediately.
+ * The remainder of the animation occurs in-window, so while you might expect that the keyguard
+ * is still 'going away' because unlock animations are playing, it's actually not.
+ *
+ * If you want to know if the keyguard is 'going away', you probably want to check if we have
+ * STARTED but not FINISHED a transition to GONE.
+ *
+ * The going away animation will run until:
+ * - We manually call [endKeyguardGoingAwayAnimation] after we're done animating.
+ * - We call [setLockscreenShown] = true, which cancels the going away animation.
+ * - WM calls [onKeyguardGoingAwayRemoteAnimationCancelled] for another reason (such as the 10
+ * second timeout).
+ */
+ private var isKeyguardGoingAway = false
+ private set(value) {
+ // TODO(b/278086361): Extricate the keyguard state controller.
+ keyguardStateController.notifyKeyguardGoingAway(value)
+ field = value
+ }
+
+ /** Callback provided by WM to call once we're done with the going away animation. */
+ private var goingAwayRemoteAnimationFinishedCallback: IRemoteAnimationFinishedCallback? = null
+
+ /**
+ * Set the visibility of the surface behind the keyguard, making the appropriate calls to Window
+ * Manager to effect the change.
+ */
+ fun setSurfaceBehindVisibility(visible: Boolean) {
+ if (isKeyguardGoingAway == visible) {
+ Log.d(TAG, "WmLockscreenVisibilityManager#setVisibility -> already visible=$visible")
+ return
+ }
+
+ // The surface behind is always visible if the lockscreen is not showing, so we're already
+ // visible.
+ if (visible && !isLockscreenShowing) {
+ Log.d(TAG, "#setVisibility -> already visible since the lockscreen isn't showing")
+ return
+ }
+
+ if (visible) {
+ // Make the surface visible behind the keyguard by calling keyguardGoingAway. The
+ // lockscreen is still showing as well, allowing us to animate unlocked.
+ Log.d(TAG, "ActivityTaskManagerService#keyguardGoingAway()")
+ activityTaskManagerService.keyguardGoingAway(0)
+ isKeyguardGoingAway = true
+ } else {
+ // Hide the surface by setting the lockscreen showing.
+ setLockscreenShown(true)
+ }
+ }
+
+ fun setAodVisible(aodVisible: Boolean) {
+ setWmLockscreenState(aodVisible = aodVisible)
+ }
+
+ /** Sets the visibility of the lockscreen. */
+ fun setLockscreenShown(lockscreenShown: Boolean) {
+ setWmLockscreenState(lockscreenShowing = lockscreenShown)
+ }
+
+ fun onKeyguardGoingAwayRemoteAnimationStart(
+ @WindowManager.TransitionOldType transit: Int,
+ apps: Array<RemoteAnimationTarget>,
+ wallpapers: Array<RemoteAnimationTarget>,
+ nonApps: Array<RemoteAnimationTarget>,
+ finishedCallback: IRemoteAnimationFinishedCallback
+ ) {
+ goingAwayRemoteAnimationFinishedCallback = finishedCallback
+ keyguardSurfaceBehindAnimator.applyParamsToSurface(apps[0])
+ }
+
+ fun onKeyguardGoingAwayRemoteAnimationCancelled() {
+ // If WM cancelled the animation, we need to end immediately even if we're still using the
+ // animation.
+ endKeyguardGoingAwayAnimation()
+ }
+
+ /**
+ * Whether the going away remote animation target is in-use, which means we're animating it or
+ * intend to animate it.
+ *
+ * Some unlock animations (such as the translation spring animation) are non-deterministic and
+ * might end after the transition to GONE ends. In that case, we want to keep the remote
+ * animation running until the spring ends.
+ */
+ fun setUsingGoingAwayRemoteAnimation(usingTarget: Boolean) {
+ if (!usingTarget) {
+ endKeyguardGoingAwayAnimation()
+ }
+ }
+
+ private fun setWmLockscreenState(
+ lockscreenShowing: Boolean = this.isLockscreenShowing,
+ aodVisible: Boolean = this.isAodVisible
+ ) {
+ Log.d(
+ TAG,
+ "#setWmLockscreenState(" +
+ "isLockscreenShowing=$lockscreenShowing, " +
+ "aodVisible=$aodVisible)."
+ )
+
+ if (this.isLockscreenShowing == lockscreenShowing && this.isAodVisible == aodVisible) {
+ return
+ }
+
+ activityTaskManagerService.setLockScreenShown(lockscreenShowing, aodVisible)
+ this.isLockscreenShowing = lockscreenShowing
+ this.isAodVisible = aodVisible
+ }
+
+ private fun endKeyguardGoingAwayAnimation() {
+ if (!isKeyguardGoingAway) {
+ Log.d(
+ TAG,
+ "#endKeyguardGoingAwayAnimation() called when isKeyguardGoingAway=false. " +
+ "Short-circuiting."
+ )
+ return
+ }
+
+ executor.execute {
+ Log.d(TAG, "Finishing remote animation.")
+ goingAwayRemoteAnimationFinishedCallback?.onAnimationFinished()
+ goingAwayRemoteAnimationFinishedCallback = null
+
+ isKeyguardGoingAway = false
+
+ keyguardSurfaceBehindAnimator.notifySurfaceReleased()
+ }
+ }
+
+ companion object {
+ private val TAG = this::class.java.simpleName
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index a5ac7c7..9a44230 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -47,6 +47,7 @@
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager;
import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule;
import com.android.systemui.keyguard.data.repository.KeyguardFaceAuthModule;
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
@@ -74,12 +75,11 @@
import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
+import java.util.concurrent.Executor;
+
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
-
-import java.util.concurrent.Executor;
-
import kotlinx.coroutines.CoroutineDispatcher;
/**
@@ -146,7 +146,8 @@
SystemClock systemClock,
@Main CoroutineDispatcher mainDispatcher,
Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
- SystemPropertiesHelper systemPropertiesHelper) {
+ SystemPropertiesHelper systemPropertiesHelper,
+ Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager) {
return new KeyguardViewMediator(
context,
uiEventLogger,
@@ -189,7 +190,8 @@
systemClock,
mainDispatcher,
dreamingToLockscreenTransitionViewModel,
- systemPropertiesHelper);
+ systemPropertiesHelper,
+ wmLockscreenVisibilityManager);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 30f8f3e..12c309c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -46,6 +46,7 @@
import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.log.FaceAuthenticationLogger
@@ -160,7 +161,7 @@
@FaceDetectTableLog private val faceDetectLog: TableLogBuffer,
@FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
- featureFlags: FeatureFlags,
+ private val featureFlags: FeatureFlags,
facePropertyRepository: FacePropertyRepository,
dumpManager: DumpManager,
) : DeviceEntryFaceAuthRepository, Dumpable {
@@ -286,8 +287,12 @@
// starts going to sleep.
merge(
keyguardRepository.wakefulness.map { it.isStartingToSleepOrAsleep() },
- keyguardRepository.isKeyguardGoingAway,
- userRepository.userSwitchingInProgress
+ if (featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE)
+ } else {
+ keyguardRepository.isKeyguardGoingAway
+ },
+ userRepository.userSwitchingInProgress,
)
.onEach { anyOfThemIsTrue ->
if (anyOfThemIsTrue) {
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 e35c369..42cd3a5 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
@@ -99,7 +99,16 @@
/** Is an activity showing over the keyguard? */
val isKeyguardOccluded: Flow<Boolean>
- /** Observable for the signal that keyguard is about to go away. */
+ /**
+ * Observable for the signal that keyguard is about to go away.
+ *
+ * TODO(b/278086361): Remove once KEYGUARD_WM_STATE_REFACTOR flag is removed.
+ */
+ @Deprecated(
+ "Use KeyguardTransitionInteractor flows instead. The closest match for 'going " +
+ "away' is isInTransitionToState(GONE), but consider using more specific flows " +
+ "whenever possible."
+ )
val isKeyguardGoingAway: Flow<Boolean>
/** Is the always-on display available to be used? */
@@ -365,10 +374,11 @@
awaitClose { keyguardStateController.removeCallback(callback) }
}
+ .distinctUntilChanged()
.stateIn(
- scope = scope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = keyguardStateController.isUnlocked,
+ scope,
+ SharingStarted.Eagerly,
+ initialValue = false,
)
override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index 246ee33..2f80106 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -32,6 +32,11 @@
@Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository
@Binds
+ fun keyguardSurfaceBehindRepository(
+ impl: KeyguardSurfaceBehindRepositoryImpl
+ ): KeyguardSurfaceBehindRepository
+
+ @Binds
fun keyguardTransitionRepository(
impl: KeyguardTransitionRepositoryImpl
): KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepository.kt
new file mode 100644
index 0000000..014b7fa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepository.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.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * State related to SysUI's handling of the surface behind the keyguard (typically an app or the
+ * launcher). We manipulate this surface during unlock animations.
+ */
+interface KeyguardSurfaceBehindRepository {
+
+ /** Whether we're running animations on the surface. */
+ val isAnimatingSurface: Flow<Boolean>
+
+ /** Set whether we're running animations on the surface. */
+ fun setAnimatingSurface(animating: Boolean)
+}
+
+@SysUISingleton
+class KeyguardSurfaceBehindRepositoryImpl @Inject constructor() : KeyguardSurfaceBehindRepository {
+ private val _isAnimatingSurface = MutableStateFlow(false)
+ override val isAnimatingSurface = _isAnimatingSurface.asStateFlow()
+
+ override fun setAnimatingSurface(animating: Boolean) {
+ _isAnimatingSurface.value = animating
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 8f0b91b..271bc38 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
-import com.android.app.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
@@ -26,6 +25,7 @@
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.Utils.Companion.toQuint
import com.android.systemui.util.kotlin.sample
+import com.android.wm.shell.animation.Interpolators
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 6b28b27..aa771fd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -20,8 +20,11 @@
import com.android.app.animation.Interpolators
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.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -34,7 +37,11 @@
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
@SysUISingleton
@@ -45,6 +52,7 @@
override val transitionInteractor: KeyguardTransitionInteractor,
@Application private val scope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
+ private val flags: FeatureFlags,
private val shadeRepository: ShadeRepository,
) :
TransitionInteractor(
@@ -53,6 +61,7 @@
override fun start() {
listenForLockscreenToGone()
+ listenForLockscreenToGoneDragging()
listenForLockscreenToOccluded()
listenForLockscreenToCamera()
listenForLockscreenToAodOrDozing()
@@ -62,6 +71,63 @@
listenForLockscreenToAlternateBouncer()
}
+ /**
+ * Whether we want the surface behind the keyguard visible for the transition from LOCKSCREEN,
+ * or null if we don't care and should just use a reasonable default.
+ *
+ * [KeyguardSurfaceBehindInteractor] will switch to this flow whenever a transition from
+ * LOCKSCREEN is running.
+ */
+ val surfaceBehindVisibility: Flow<Boolean?> =
+ transitionInteractor.startedKeyguardTransitionStep
+ .map { startedStep ->
+ if (startedStep.to != KeyguardState.GONE) {
+ // LOCKSCREEN to anything but GONE does not require any special surface
+ // visibility handling.
+ return@map null
+ }
+
+ true // TODO(b/278086361): Implement continuous swipe to unlock.
+ }
+ .onStart {
+ // Default to null ("don't care, use a reasonable default").
+ emit(null)
+ }
+ .distinctUntilChanged()
+
+ /**
+ * The surface behind view params to use for the transition from LOCKSCREEN, or null if we don't
+ * care and should use a reasonable default.
+ */
+ val surfaceBehindModel: Flow<KeyguardSurfaceBehindModel?> =
+ combine(
+ transitionInteractor.startedKeyguardTransitionStep,
+ transitionInteractor.transitionStepsFromState(KeyguardState.LOCKSCREEN)
+ ) { startedStep, fromLockscreenStep ->
+ if (startedStep.to != KeyguardState.GONE) {
+ // Only LOCKSCREEN -> GONE has specific surface params (for the unlock
+ // animation).
+ return@combine null
+ } else if (fromLockscreenStep.value > 0.5f) {
+ // Start the animation once we're 50% transitioned to GONE.
+ KeyguardSurfaceBehindModel(
+ animateFromAlpha = 0f,
+ alpha = 1f,
+ animateFromTranslationY = 500f,
+ translationY = 0f
+ )
+ } else {
+ KeyguardSurfaceBehindModel(
+ alpha = 0f,
+ )
+ }
+ }
+ .onStart {
+ // Default to null ("don't care, use a reasonable default").
+ emit(null)
+ }
+ .distinctUntilChanged()
+
private fun listenForLockscreenToDreaming() {
val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING)
scope.launch {
@@ -169,7 +235,8 @@
}
// If canceled, just put the state back
- // TODO: This logic should happen in FromPrimaryBouncerInteractor.
+ // TODO(b/278086361): This logic should happen in
+ // FromPrimaryBouncerInteractor.
if (nextState == TransitionState.CANCELED) {
transitionRepository.startTransition(
TransitionInfo(
@@ -201,7 +268,32 @@
}
}
+ fun dismissKeyguard() {
+ startTransitionTo(KeyguardState.GONE)
+ }
+
private fun listenForLockscreenToGone() {
+ if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ return
+ }
+
+ scope.launch {
+ keyguardInteractor.isKeyguardGoingAway
+ .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .collect { pair ->
+ val (isKeyguardGoingAway, lastStartedStep) = pair
+ if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
+ startTransitionTo(KeyguardState.GONE)
+ }
+ }
+ }
+ }
+
+ private fun listenForLockscreenToGoneDragging() {
+ if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ return
+ }
+
scope.launch {
keyguardInteractor.isKeyguardGoingAway
.sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
@@ -291,7 +383,7 @@
}
companion object {
- private val DEFAULT_DURATION = 500.milliseconds
+ private val DEFAULT_DURATION = 400.milliseconds
val TO_DREAMING_DURATION = 933.milliseconds
val TO_OCCLUDED_DURATION = 450.milliseconds
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 9142d1f..c9f32da 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -17,23 +17,28 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
-import com.android.app.animation.Interpolators
import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Password
import com.android.keyguard.KeyguardUpdateMonitor
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.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.Utils.Companion.toQuint
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
+import com.android.wm.shell.animation.Interpolators
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
@SysUISingleton
@@ -44,6 +49,7 @@
override val transitionInteractor: KeyguardTransitionInteractor,
@Application private val scope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
+ private val flags: FeatureFlags,
private val keyguardSecurityModel: KeyguardSecurityModel,
) :
TransitionInteractor(
@@ -57,6 +63,57 @@
listenForPrimaryBouncerToDreamingLockscreenHosted()
}
+ val surfaceBehindVisibility: Flow<Boolean?> =
+ combine(
+ transitionInteractor.startedKeyguardTransitionStep,
+ transitionInteractor.transitionStepsFromState(KeyguardState.PRIMARY_BOUNCER)
+ ) { startedStep, fromBouncerStep ->
+ if (startedStep.to != KeyguardState.GONE) {
+ return@combine null
+ }
+
+ fromBouncerStep.value > 0.5f
+ }
+ .onStart {
+ // Default to null ("don't care, use a reasonable default").
+ emit(null)
+ }
+ .distinctUntilChanged()
+
+ val surfaceBehindModel: Flow<KeyguardSurfaceBehindModel?> =
+ combine(
+ transitionInteractor.startedKeyguardTransitionStep,
+ transitionInteractor.transitionStepsFromState(KeyguardState.PRIMARY_BOUNCER)
+ ) { startedStep, fromBouncerStep ->
+ if (startedStep.to != KeyguardState.GONE) {
+ // BOUNCER to anything but GONE does not require any special surface
+ // visibility handling.
+ return@combine null
+ }
+
+ if (fromBouncerStep.value > 0.5f) {
+ KeyguardSurfaceBehindModel(
+ animateFromAlpha = 0f,
+ alpha = 1f,
+ animateFromTranslationY = 500f,
+ translationY = 0f,
+ )
+ } else {
+ KeyguardSurfaceBehindModel(
+ alpha = 0f,
+ )
+ }
+ }
+ .onStart {
+ // Default to null ("don't care, use a reasonable default").
+ emit(null)
+ }
+ .distinctUntilChanged()
+
+ fun dismissPrimaryBouncer() {
+ startTransitionTo(KeyguardState.GONE)
+ }
+
private fun listenForPrimaryBouncerToLockscreenOrOccluded() {
scope.launch {
keyguardInteractor.primaryBouncerShowing
@@ -124,28 +181,34 @@
private fun listenForPrimaryBouncerToDreamingLockscreenHosted() {
scope.launch {
keyguardInteractor.primaryBouncerShowing
- .sample(
- combine(
- keyguardInteractor.isActiveDreamLockscreenHosted,
- transitionInteractor.startedKeyguardTransitionStep,
- ::Pair
- ),
- ::toTriple
- )
- .collect {
- (isBouncerShowing, isActiveDreamLockscreenHosted, lastStartedTransitionStep) ->
- if (
- !isBouncerShowing &&
- isActiveDreamLockscreenHosted &&
- lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER
- ) {
- startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+ .sample(
+ combine(
+ keyguardInteractor.isActiveDreamLockscreenHosted,
+ transitionInteractor.startedKeyguardTransitionStep,
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect { (isBouncerShowing, isActiveDreamLockscreenHosted, lastStartedTransitionStep) ->
+ if (
+ !isBouncerShowing &&
+ isActiveDreamLockscreenHosted &&
+ lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER
+ ) {
+ startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+ }
}
- }
}
}
private fun listenForPrimaryBouncerToGone() {
+ if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ // This is handled in KeyguardSecurityContainerController and
+ // StatusBarKeyguardViewManager, which calls the transition interactor to kick off a
+ // transition vs. listening to legacy state flags.
+ return
+ }
+
scope.launch {
keyguardInteractor.isKeyguardGoingAway
.sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
@@ -160,7 +223,7 @@
)
// IME for password requires a slightly faster animation
val duration =
- if (securityMode == Password) {
+ if (securityMode == KeyguardSecurityModel.SecurityMode.Password) {
TO_GONE_SHORT_DURATION
} else {
TO_GONE_DURATION
@@ -188,7 +251,7 @@
companion object {
private val DEFAULT_DURATION = 300.milliseconds
- val TO_GONE_DURATION = 250.milliseconds
+ val TO_GONE_DURATION = 500.milliseconds
val TO_GONE_SHORT_DURATION = 200.milliseconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 53d3c07..562c4db 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -34,12 +34,12 @@
import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.ScreenModel
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.util.kotlin.sample
-import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
@@ -53,6 +53,7 @@
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
+import javax.inject.Inject
/**
* Encapsulates business-logic related to the keyguard but not to a more specific part within it.
@@ -264,7 +265,26 @@
repository.setAnimateDozingTransitions(animate)
}
+ fun isKeyguardDismissable(): Boolean {
+ return repository.isKeyguardUnlocked.value
+ }
+
companion object {
private const val TAG = "KeyguardInteractor"
+
+ fun isKeyguardVisibleInState(state: KeyguardState): Boolean {
+ return when (state) {
+ KeyguardState.OFF -> true
+ KeyguardState.DOZING -> true
+ KeyguardState.DREAMING -> true
+ KeyguardState.AOD -> true
+ KeyguardState.ALTERNATE_BOUNCER -> true
+ KeyguardState.PRIMARY_BOUNCER -> true
+ KeyguardState.LOCKSCREEN -> true
+ KeyguardState.GONE -> false
+ KeyguardState.OCCLUDED -> true
+ KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> false
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
new file mode 100644
index 0000000..bf04f8f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardSurfaceBehindRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+@SysUISingleton
+class KeyguardSurfaceBehindInteractor
+@Inject
+constructor(
+ private val repository: KeyguardSurfaceBehindRepository,
+ private val fromLockscreenInteractor: FromLockscreenTransitionInteractor,
+ private val fromPrimaryBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
+ transitionInteractor: KeyguardTransitionInteractor,
+) {
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ val viewParams: Flow<KeyguardSurfaceBehindModel> =
+ transitionInteractor.isInTransitionToAnyState
+ .flatMapLatest { isInTransition ->
+ if (!isInTransition) {
+ defaultParams
+ } else {
+ combine(
+ transitionSpecificViewParams,
+ defaultParams,
+ ) { transitionParams, defaultParams ->
+ transitionParams ?: defaultParams
+ }
+ }
+ }
+
+ val isAnimatingSurface = repository.isAnimatingSurface
+
+ private val defaultParams =
+ transitionInteractor.finishedKeyguardState.map { state ->
+ KeyguardSurfaceBehindModel(
+ alpha =
+ if (WindowManagerLockscreenVisibilityInteractor.isSurfaceVisible(state)) 1f
+ else 0f
+ )
+ }
+
+ /**
+ * View params provided by the transition interactor for the most recently STARTED transition.
+ * This is used to run transition-specific animations on the surface.
+ *
+ * If null, there are no transition-specific view params needed for this transition and we will
+ * use a reasonable default.
+ */
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private val transitionSpecificViewParams: Flow<KeyguardSurfaceBehindModel?> =
+ transitionInteractor.startedKeyguardTransitionStep.flatMapLatest { startedStep ->
+ when (startedStep.from) {
+ KeyguardState.LOCKSCREEN -> fromLockscreenInteractor.surfaceBehindModel
+ KeyguardState.PRIMARY_BOUNCER -> fromPrimaryBouncerInteractor.surfaceBehindModel
+ // Return null for other states, where no transition specific params are needed.
+ else -> flowOf(null)
+ }
+ }
+
+ fun setAnimatingSurface(animating: Boolean) {
+ repository.setAnimatingSurface(animating)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 8c4c7ae..9382618 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -17,17 +17,20 @@
package com.android.systemui.keyguard.domain.interactor
+import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
+import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -36,6 +39,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
@@ -46,8 +50,12 @@
class KeyguardTransitionInteractor
@Inject
constructor(
- private val repository: KeyguardTransitionRepository,
@Application val scope: CoroutineScope,
+ private val repository: KeyguardTransitionRepository,
+ private val keyguardInteractor: dagger.Lazy<KeyguardInteractor>,
+ private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>,
+ private val fromPrimaryBouncerTransitionInteractor:
+ dagger.Lazy<FromPrimaryBouncerTransitionInteractor>,
) {
private val TAG = this::class.simpleName
@@ -128,12 +136,11 @@
repository.transition(PRIMARY_BOUNCER, GONE)
/** OFF->LOCKSCREEN transition information. */
- val offToLockscreenTransition: Flow<TransitionStep> =
- repository.transition(KeyguardState.OFF, LOCKSCREEN)
+ val offToLockscreenTransition: Flow<TransitionStep> = repository.transition(OFF, LOCKSCREEN)
/** DOZING->LOCKSCREEN transition information. */
val dozingToLockscreenTransition: Flow<TransitionStep> =
- repository.transition(KeyguardState.DOZING, LOCKSCREEN)
+ repository.transition(DOZING, LOCKSCREEN)
/**
* AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
@@ -157,17 +164,30 @@
val finishedKeyguardTransitionStep: Flow<TransitionStep> =
repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
- /** The destination state of the last started transition */
+ /** The destination state of the last started transition. */
val startedKeyguardState: StateFlow<KeyguardState> =
startedKeyguardTransitionStep
.map { step -> step.to }
- .stateIn(scope, SharingStarted.Eagerly, KeyguardState.OFF)
+ .stateIn(scope, SharingStarted.Eagerly, OFF)
/** The last completed [KeyguardState] transition */
val finishedKeyguardState: StateFlow<KeyguardState> =
finishedKeyguardTransitionStep
.map { step -> step.to }
.stateIn(scope, SharingStarted.Eagerly, LOCKSCREEN)
+
+ /**
+ * Whether we're currently in a transition to a new [KeyguardState] and haven't yet completed
+ * it.
+ */
+ val isInTransitionToAnyState =
+ combine(
+ startedKeyguardTransitionStep,
+ finishedKeyguardState,
+ ) { startedStep, finishedState ->
+ startedStep.to != finishedState
+ }
+
/**
* The amount of transition into or out of the given [KeyguardState].
*
@@ -187,4 +207,41 @@
}
}
}
+
+ fun transitionStepsFromState(fromState: KeyguardState): Flow<TransitionStep> {
+ return repository.transitions.filter { step -> step.from == fromState }
+ }
+
+ fun transitionStepsToState(toState: KeyguardState): Flow<TransitionStep> {
+ return repository.transitions.filter { step -> step.to == toState }
+ }
+
+ /**
+ * Called to start a transition that will ultimately dismiss the keyguard from the current
+ * state.
+ */
+ fun startDismissKeyguardTransition() {
+ when (startedKeyguardState.value) {
+ LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
+ PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer()
+ else ->
+ Log.e(
+ "KeyguardTransitionInteractor",
+ "We don't know how to dismiss keyguard from state " +
+ "${startedKeyguardState.value}"
+ )
+ }
+ }
+
+ /** Whether we're in a transition to the given [KeyguardState], but haven't yet completed it. */
+ fun isInTransitionToState(
+ state: KeyguardState,
+ ): Flow<Boolean> {
+ return combine(
+ startedKeyguardTransitionStep,
+ finishedKeyguardState,
+ ) { startedStep, finishedState ->
+ startedStep.to == state && finishedState != state
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
new file mode 100644
index 0000000..96bfdc6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -0,0 +1,162 @@
+/*
+ * 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.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+@SysUISingleton
+class WindowManagerLockscreenVisibilityInteractor
+@Inject
+constructor(
+ keyguardInteractor: KeyguardInteractor,
+ transitionInteractor: KeyguardTransitionInteractor,
+ surfaceBehindInteractor: KeyguardSurfaceBehindInteractor,
+ fromLockscreenInteractor: FromLockscreenTransitionInteractor,
+ fromBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
+) {
+ private val defaultSurfaceBehindVisibility =
+ transitionInteractor.finishedKeyguardState.map(::isSurfaceVisible)
+
+ /**
+ * Surface visibility provided by the From*TransitionInteractor responsible for the currently
+ * RUNNING transition, or null if the current transition does not require special surface
+ * visibility handling.
+ *
+ * An example of transition-specific visibility is swipe to unlock, where the surface should
+ * only be visible after swiping 20% of the way up the screen, and should become invisible again
+ * if the user swipes back down.
+ */
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private val transitionSpecificSurfaceBehindVisibility: Flow<Boolean?> =
+ transitionInteractor.startedKeyguardTransitionStep
+ .flatMapLatest { startedStep ->
+ when (startedStep.from) {
+ KeyguardState.LOCKSCREEN -> {
+ fromLockscreenInteractor.surfaceBehindVisibility
+ }
+ KeyguardState.PRIMARY_BOUNCER -> {
+ fromBouncerInteractor.surfaceBehindVisibility
+ }
+ else -> flowOf(null)
+ }
+ }
+ .distinctUntilChanged()
+
+ /**
+ * Surface visibility, which is either determined by the default visibility in the FINISHED
+ * KeyguardState, or the transition-specific visibility used during certain RUNNING transitions.
+ */
+ @OptIn(ExperimentalCoroutinesApi::class)
+ val surfaceBehindVisibility: Flow<Boolean> =
+ transitionInteractor
+ .isInTransitionToAnyState
+ .flatMapLatest { isInTransition ->
+ if (!isInTransition) {
+ defaultSurfaceBehindVisibility
+ } else {
+ combine(
+ transitionSpecificSurfaceBehindVisibility,
+ defaultSurfaceBehindVisibility,
+ ) { transitionVisibility, defaultVisibility ->
+ // Defer to the transition-specific visibility since we're RUNNING a
+ // transition, but fall back to the default visibility if the current
+ // transition's interactor did not specify a visibility.
+ transitionVisibility ?: defaultVisibility
+ }
+ }
+ }
+ .distinctUntilChanged()
+
+ /**
+ * Whether we're animating, or intend to animate, the surface behind the keyguard via remote
+ * animation. This is used to keep the RemoteAnimationTarget alive until we're done using it.
+ */
+ val usingKeyguardGoingAwayAnimation: Flow<Boolean> =
+ combine(
+ transitionInteractor.isInTransitionToState(KeyguardState.GONE),
+ transitionInteractor.finishedKeyguardState,
+ surfaceBehindInteractor.isAnimatingSurface
+ ) { isInTransitionToGone, finishedState, isAnimatingSurface ->
+ // We may still be animating the surface after the keyguard is fully GONE, since
+ // some animations (like the translation spring) are not tied directly to the
+ // transition step amount.
+ isInTransitionToGone || (finishedState == KeyguardState.GONE && isAnimatingSurface)
+ }
+ .distinctUntilChanged()
+
+ /**
+ * Whether the lockscreen is visible, from the Window Manager (WM) perspective.
+ *
+ * Note: This may briefly be true even if the lockscreen UI has animated out (alpha = 0f), as we
+ * only inform WM once we're done with the keyguard and we're fully GONE. Don't use this if you
+ * want to know if the AOD/clock/notifs/etc. are visible.
+ */
+ val lockscreenVisibility: Flow<Boolean> =
+ combine(
+ transitionInteractor.startedKeyguardTransitionStep,
+ transitionInteractor.finishedKeyguardState,
+ ) { startedStep, finishedState ->
+ // If we finished the transition, use the finished state. If we're running a
+ // transition, use the state we're transitioning FROM. This can be different from
+ // the last finished state if a transition is interrupted. For example, if we were
+ // transitioning from GONE to AOD and then started AOD -> LOCKSCREEN mid-transition,
+ // we want to immediately use the visibility for AOD (lockscreenVisibility=true)
+ // even though the lastFinishedState is still GONE (lockscreenVisibility=false).
+ if (finishedState == startedStep.to) finishedState else startedStep.from
+ }
+ .map(::isLockscreenVisible)
+ .distinctUntilChanged()
+
+ /**
+ * Whether always-on-display (AOD) is visible when the lockscreen is visible, from window
+ * manager's perspective.
+ *
+ * Note: This may be true even if AOD is not user-visible, such as when the light sensor
+ * indicates the device is in the user's pocket. Don't use this if you want to know if the AOD
+ * clock/smartspace/notif icons are visible.
+ */
+ val aodVisibility: Flow<Boolean> =
+ combine(
+ keyguardInteractor.isDozing,
+ keyguardInteractor.biometricUnlockState,
+ ) { isDozing, biometricUnlockState ->
+ // AOD is visible if we're dozing, unless we are wake and unlocking (where we go
+ // directly from AOD to unlocked while dozing).
+ isDozing && !BiometricUnlockModel.isWakeAndUnlock(biometricUnlockState)
+ }
+ .distinctUntilChanged()
+
+ companion object {
+ fun isSurfaceVisible(state: KeyguardState): Boolean {
+ return !isLockscreenVisible(state)
+ }
+
+ fun isLockscreenVisible(state: KeyguardState): Boolean {
+ return state != KeyguardState.GONE
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt
new file mode 100644
index 0000000..7fb5cfd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.shared.model
+
+/**
+ * Models the appearance of the surface behind the keyguard, and (optionally) how it should be
+ * animating.
+ *
+ * This is intended to be an atomic, high-level description of the surface's appearance and related
+ * animations, which we can derive from the STARTED/FINISHED transition states rather than the
+ * individual TransitionSteps.
+ *
+ * For example, if we're transitioning from LOCKSCREEN to GONE, that means we should be
+ * animatingFromAlpha 0f -> 1f and animatingFromTranslationY 500f -> 0f.
+ * KeyguardSurfaceBehindAnimator can decide how best to implement this, depending on previously
+ * running animations, spring momentum, and other state.
+ */
+data class KeyguardSurfaceBehindModel(
+ val alpha: Float = 1f,
+
+ /**
+ * If provided, animate from this value to [alpha] unless an animation is already running, in
+ * which case we'll animate from the current value to [alpha].
+ */
+ val animateFromAlpha: Float = alpha,
+ val translationY: Float = 0f,
+
+ /**
+ * If provided, animate from this value to [translationY] unless an animation is already
+ * running, in which case we'll animate from the current value to [translationY].
+ */
+ val animateFromTranslationY: Float = translationY,
+) {
+ fun willAnimateAlpha(): Boolean {
+ return animateFromAlpha != alpha
+ }
+
+ fun willAnimateTranslationY(): Boolean {
+ return animateFromTranslationY != translationY
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt
new file mode 100644
index 0000000..a5b00e0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt
@@ -0,0 +1,219 @@
+/*
+ * 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.binder
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.graphics.Matrix
+import android.util.Log
+import android.view.RemoteAnimationTarget
+import android.view.SurfaceControl
+import android.view.SyncRtSurfaceTransactionApplier
+import android.view.View
+import androidx.dynamicanimation.animation.FloatValueHolder
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.keyguard.KeyguardViewController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.TAG
+import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.wm.shell.animation.Interpolators
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Applies [KeyguardSurfaceBehindViewParams] to a RemoteAnimationTarget, starting and managing
+ * animations as needed.
+ */
+@SysUISingleton
+class KeyguardSurfaceBehindParamsApplier
+@Inject
+constructor(
+ @Main private val executor: Executor,
+ private val keyguardViewController: KeyguardViewController,
+ private val interactor: KeyguardSurfaceBehindInteractor,
+) {
+ private var surfaceBehind: RemoteAnimationTarget? = null
+ private val surfaceTransactionApplier: SyncRtSurfaceTransactionApplier
+ get() = SyncRtSurfaceTransactionApplier(keyguardViewController.viewRootImpl.view)
+
+ private val matrix = Matrix()
+ private val tmpFloat = FloatArray(9)
+
+ private var animatedTranslationY = FloatValueHolder()
+ private val translateYSpring =
+ SpringAnimation(animatedTranslationY).apply {
+ spring =
+ SpringForce().apply {
+ stiffness = 200f
+ dampingRatio = 1f
+ }
+ addUpdateListener { _, _, _ -> applyToSurfaceBehind() }
+ addEndListener { _, _, _, _ ->
+ try {
+ updateIsAnimatingSurface()
+ } catch (e: NullPointerException) {
+ // TODO(b/291645410): Remove when we can isolate DynamicAnimations.
+ e.printStackTrace()
+ }
+ }
+ }
+
+ private var animatedAlpha = 0f
+ private var alphaAnimator =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = 500
+ interpolator = Interpolators.ALPHA_IN
+ addUpdateListener {
+ animatedAlpha = it.animatedValue as Float
+ applyToSurfaceBehind()
+ }
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ updateIsAnimatingSurface()
+ }
+ }
+ )
+ }
+
+ /**
+ * ViewParams to apply to the surface provided to [applyParamsToSurface]. If the surface is null
+ * these will be applied once someone gives us a surface via [applyParamsToSurface].
+ */
+ var viewParams: KeyguardSurfaceBehindModel = KeyguardSurfaceBehindModel()
+ set(newParams) {
+ field = newParams
+ startOrUpdateAnimators()
+ applyToSurfaceBehind()
+ }
+
+ /**
+ * Provides us with a surface to animate. We'll apply the [viewParams] to this surface and start
+ * any necessary animations.
+ */
+ fun applyParamsToSurface(surface: RemoteAnimationTarget) {
+ this.surfaceBehind = surface
+ startOrUpdateAnimators()
+ }
+
+ /**
+ * Notifies us that the [RemoteAnimationTarget] has been released, one way or another.
+ * Attempting to animate a released target will cause a crash.
+ *
+ * This can be called either because we finished animating the surface naturally, or by WM
+ * because external factors cancelled the remote animation (timeout, re-lock, etc). If it's the
+ * latter, cancel any outstanding animations we have.
+ */
+ fun notifySurfaceReleased() {
+ surfaceBehind = null
+
+ if (alphaAnimator.isRunning) {
+ alphaAnimator.cancel()
+ }
+
+ if (translateYSpring.isRunning) {
+ translateYSpring.cancel()
+ }
+ }
+
+ private fun startOrUpdateAnimators() {
+ if (surfaceBehind == null) {
+ return
+ }
+
+ if (viewParams.willAnimateAlpha()) {
+ var fromAlpha = viewParams.animateFromAlpha
+
+ if (alphaAnimator.isRunning) {
+ alphaAnimator.cancel()
+ fromAlpha = animatedAlpha
+ }
+
+ alphaAnimator.setFloatValues(fromAlpha, viewParams.alpha)
+ alphaAnimator.start()
+ }
+
+ if (viewParams.willAnimateTranslationY()) {
+ if (!translateYSpring.isRunning) {
+ // If the spring isn't running yet, set the start value. Otherwise, respect the
+ // current position.
+ animatedTranslationY.value = viewParams.animateFromTranslationY
+ }
+
+ translateYSpring.animateToFinalPosition(viewParams.translationY)
+ }
+
+ updateIsAnimatingSurface()
+ }
+
+ private fun updateIsAnimatingSurface() {
+ interactor.setAnimatingSurface(translateYSpring.isRunning || alphaAnimator.isRunning)
+ }
+
+ private fun applyToSurfaceBehind() {
+ surfaceBehind?.leash?.let { sc ->
+ executor.execute {
+ if (surfaceBehind == null) {
+ Log.d(
+ TAG,
+ "Attempting to modify params of surface that isn't " +
+ "animating. Ignoring."
+ )
+ matrix.set(Matrix.IDENTITY_MATRIX)
+ return@execute
+ }
+
+ val translationY =
+ if (translateYSpring.isRunning) animatedTranslationY.value
+ else viewParams.translationY
+
+ val alpha =
+ if (alphaAnimator.isRunning) {
+ animatedAlpha
+ } else {
+ viewParams.alpha
+ }
+
+ if (
+ keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+ sc.isValid
+ ) {
+ with(SurfaceControl.Transaction()) {
+ setMatrix(
+ sc,
+ matrix.apply { setTranslate(/* dx= */ 0f, translationY) },
+ tmpFloat
+ )
+ setAlpha(sc, alpha)
+ apply()
+ }
+ } else {
+ surfaceTransactionApplier.scheduleApply(
+ SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(sc)
+ .withMatrix(matrix.apply { setTranslate(/* dx= */ 0f, translationY) })
+ .withAlpha(alpha)
+ .build()
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt
new file mode 100644
index 0000000..599f69f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.binder
+
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSurfaceBehindViewModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Binds the [WindowManagerLockscreenVisibilityManager] "view", which manages the visibility of the
+ * surface behind the keyguard.
+ */
+object KeyguardSurfaceBehindViewBinder {
+ @JvmStatic
+ fun bind(
+ viewModel: KeyguardSurfaceBehindViewModel,
+ applier: KeyguardSurfaceBehindParamsApplier,
+ scope: CoroutineScope
+ ) {
+ scope.launch { viewModel.surfaceBehindViewParams.collect { applier.viewParams = it } }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt
new file mode 100644
index 0000000..fc0c78a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.binder
+
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
+import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibilityViewModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Binds the [WindowManagerLockscreenVisibilityManager] "view", which manages the visibility of the
+ * surface behind the keyguard.
+ */
+object WindowManagerLockscreenVisibilityViewBinder {
+ @JvmStatic
+ fun bind(
+ viewModel: WindowManagerLockscreenVisibilityViewModel,
+ lockscreenVisibilityManager: WindowManagerLockscreenVisibilityManager,
+ scope: CoroutineScope
+ ) {
+ scope.launch {
+ viewModel.surfaceBehindVisibility.collect {
+ lockscreenVisibilityManager.setSurfaceBehindVisibility(it)
+ }
+ }
+
+ scope.launch {
+ viewModel.lockscreenVisibility.collect {
+ lockscreenVisibilityManager.setLockscreenShown(it)
+ }
+ }
+
+ scope.launch {
+ viewModel.aodVisibility.collect { lockscreenVisibilityManager.setAodVisible(it) }
+ }
+
+ scope.launch {
+ viewModel.surfaceBehindAnimating.collect {
+ lockscreenVisibilityManager.setUsingGoingAwayRemoteAnimation(it)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSurfaceBehindViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSurfaceBehindViewModel.kt
new file mode 100644
index 0000000..4f52962
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSurfaceBehindViewModel.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor
+import javax.inject.Inject
+
+@SysUISingleton
+class KeyguardSurfaceBehindViewModel
+@Inject
+constructor(interactor: KeyguardSurfaceBehindInteractor) {
+ val surfaceBehindViewParams = interactor.viewParams
+}
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 f46d0eb..11e85d0 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
@@ -18,7 +18,6 @@
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
@@ -27,7 +26,6 @@
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
@@ -53,14 +51,15 @@
)
/** The key of the scene we should switch to when swiping up. */
- val upDestinationSceneKey: Flow<SceneKey> =
- authenticationInteractor.authenticationMethod.map { authenticationMethod ->
- if (authenticationMethod is AuthenticationMethodModel.Swipe) {
- SceneKey.Gone
- } else {
- SceneKey.Bouncer
- }
- }
+ val upDestinationSceneKey =
+ authenticationInteractor.canSwipeToDismiss
+ .map { canSwipeToDismiss -> upDestinationSceneKey(canSwipeToDismiss) }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue =
+ upDestinationSceneKey(authenticationInteractor.canSwipeToDismiss.value),
+ )
/** Notifies that the lock button on the lock screen was clicked. */
fun onLockButtonClicked() {
@@ -73,30 +72,24 @@
}
private fun upDestinationSceneKey(
- isSwipeToUnlockEnabled: Boolean,
+ canSwipeToDismiss: Boolean,
): SceneKey {
- return if (isSwipeToUnlockEnabled) SceneKey.Gone else SceneKey.Bouncer
+ return if (canSwipeToDismiss) SceneKey.Gone else SceneKey.Bouncer
}
private fun lockIcon(
isUnlocked: Boolean,
): Icon {
- return Icon.Resource(
- res =
- if (isUnlocked) {
- R.drawable.ic_device_lock_off
- } else {
- R.drawable.ic_device_lock_on
- },
- contentDescription =
- ContentDescription.Resource(
- res =
- if (isUnlocked) {
- R.string.accessibility_unlock_button
- } else {
- R.string.accessibility_lock_icon
- }
- )
- )
+ return if (isUnlocked) {
+ Icon.Resource(
+ R.drawable.ic_device_lock_off,
+ ContentDescription.Resource(R.string.accessibility_unlock_button)
+ )
+ } else {
+ Icon.Resource(
+ R.drawable.ic_device_lock_on,
+ ContentDescription.Resource(R.string.accessibility_lock_icon)
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/WindowManagerLockscreenVisibilityViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/WindowManagerLockscreenVisibilityViewModel.kt
new file mode 100644
index 0000000..f797640
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/WindowManagerLockscreenVisibilityViewModel.kt
@@ -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.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor
+import javax.inject.Inject
+
+@SysUISingleton
+class WindowManagerLockscreenVisibilityViewModel
+@Inject
+constructor(interactor: WindowManagerLockscreenVisibilityInteractor) {
+ val surfaceBehindVisibility = interactor.surfaceBehindVisibility
+ val surfaceBehindAnimating = interactor.usingKeyguardGoingAwayAnimation
+ val lockscreenVisibility = interactor.lockscreenVisibility
+ val aodVisibility = interactor.aodVisibility
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
index 67a985e..a7ffc5f 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
@@ -302,14 +302,14 @@
@Synchronized
override fun dump(pw: PrintWriter, args: Array<out String>) {
- pw.println(HEADER_PREFIX + name)
- pw.println("version $VERSION")
+ pw.append(HEADER_PREFIX).println(name)
+ pw.append("version ").println(VERSION)
lastEvictedValues.values.sortedBy { it.timestamp }.forEach { it.dump(pw) }
for (i in 0 until buffer.size) {
buffer[i].dump(pw)
}
- pw.println(FOOTER_PREFIX + name)
+ pw.append(FOOTER_PREFIX).println(name)
}
/** Dumps an individual [TableChange]. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index b88eba9..a3d1d8c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -157,6 +157,7 @@
private String mDeviceId;
private ValueAnimator mCornerAnimator;
private ValueAnimator mVolumeAnimator;
+ private int mLatestUpdateVolume = -1;
MediaDeviceBaseViewHolder(View view) {
super(view);
@@ -314,7 +315,11 @@
mSeekBar.setMaxVolume(device.getMaxVolume());
final int currentVolume = device.getCurrentVolume();
if (!mIsDragging) {
- if (mSeekBar.getVolume() != currentVolume) {
+ if (mSeekBar.getVolume() != currentVolume && (mLatestUpdateVolume == -1
+ || currentVolume == mLatestUpdateVolume)) {
+ // Update only if volume of device and value of volume bar doesn't match.
+ // Check if response volume match with the latest request, to ignore obsolete
+ // response
if (isCurrentSeekbarInvisible && !mIsInitVolumeFirstTime) {
updateTitleIcon(currentVolume == 0 ? R.drawable.media_output_icon_volume_off
: R.drawable.media_output_icon_volume,
@@ -330,12 +335,16 @@
updateUnmutedVolumeIcon();
}
mSeekBar.setVolume(currentVolume);
+ mLatestUpdateVolume = -1;
}
}
} else if (currentVolume == 0) {
mSeekBar.resetVolume();
updateMutedVolumeIcon();
}
+ if (currentVolume == mLatestUpdateVolume) {
+ mLatestUpdateVolume = -1;
+ }
}
if (mIsInitVolumeFirstTime) {
mIsInitVolumeFirstTime = false;
@@ -360,6 +369,7 @@
mStartFromMute = false;
}
if (progressToVolume != deviceVolume) {
+ mLatestUpdateVolume = progressToVolume;
mController.adjustVolume(device, progressToVolume);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 8f9cf4b..21da596 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -142,6 +142,9 @@
state.contentDescription = state.label;
state.expandedAccessibilityClassName = Switch.class.getName();
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
+ state.secondaryLabel = state.value
+ ? ""
+ : mContext.getString(R.string.quick_settings_work_mode_paused_state);
}
@Override
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 cf7abdd..76d9b03 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
@@ -17,21 +17,22 @@
package com.android.systemui.scene.domain.interactor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.shared.logger.SceneLogger
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.util.kotlin.pairwise
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
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.distinctUntilChanged
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.stateIn
/**
* Generic business logic and app state accessors for the scene framework.
@@ -44,6 +45,7 @@
class SceneInteractor
@Inject
constructor(
+ @Application applicationScope: CoroutineScope,
private val repository: SceneContainerRepository,
private val logger: SceneLogger,
) {
@@ -88,6 +90,22 @@
*/
val transitionState: StateFlow<ObservableTransitionState> = repository.transitionState
+ /**
+ * The key of the scene that the UI is currently transitioning to or `null` if there is no
+ * active transition at the moment.
+ *
+ * This is a convenience wrapper around [transitionState], meant for flow-challenged consumers
+ * like Java code.
+ */
+ val transitioningTo: StateFlow<SceneKey?> =
+ transitionState
+ .map { state -> (state as? ObservableTransitionState.Transition)?.toScene }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = null,
+ )
+
/** Whether the scene container is visible. */
val isVisible: StateFlow<Boolean> = repository.isVisible
@@ -142,21 +160,6 @@
repository.setTransitionState(transitionState)
}
- /**
- * Returns a stream of events that emits one [Unit] every time the framework transitions from
- * [from] to [to].
- */
- 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
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
index 6143308..4644d41 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
@@ -108,7 +108,7 @@
* @see NPVCDownEventState.asStringList
*/
fun toList(): List<Row> {
- return buffer.asSequence().map { it.asStringList }.toList()
+ return buffer.map { it.asStringList }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 35fd98c..f9ca05a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -3447,11 +3447,13 @@
ipw.print("mIgnoreXTouchSlop="); ipw.println(mIgnoreXTouchSlop);
ipw.print("mExpandLatencyTracking="); ipw.println(mExpandLatencyTracking);
ipw.println("gestureExclusionRect:" + calculateGestureExclusionRect());
+ Trace.beginSection("Table<DownEvents>");
new DumpsysTableLogger(
TAG,
NPVCDownEventState.TABLE_HEADERS,
mLastDownEvents.toList()
).printTableData(ipw);
+ Trace.endSection();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index b328c55..6f726a2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -840,13 +840,17 @@
pw.println(" mDeferWindowLayoutParams=" + mDeferWindowLayoutParams);
pw.println(mCurrentState);
if (mWindowRootView != null && mWindowRootView.getViewRootImpl() != null) {
+ Trace.beginSection("mWindowRootView.dump()");
mWindowRootView.getViewRootImpl().dump(" ", pw);
+ Trace.endSection();
}
+ Trace.beginSection("Table<State>");
new DumpsysTableLogger(
TAG,
NotificationShadeWindowState.TABLE_HEADERS,
mStateBuffer.toList()
).printTableData(pw);
+ Trace.endSection();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index d252943..e3010ca 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -170,7 +170,7 @@
* @see [NotificationShadeWindowState.asStringList]
*/
fun toList(): List<Row> {
- return buffer.asSequence().map { it.asStringList }.toList()
+ return buffer.map { it.asStringList }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 18e9644..5b5785e 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 @@
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/statusbar/events/SystemStatusAnimationSchedulerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
index 6fc715a..e018397 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
@@ -170,19 +170,19 @@
// Set hasPersistentDot to false. If the animationState is anything before ANIMATING_OUT,
// the disappear animation will not animate into a dot but remove the chip entirely
hasPersistentDot = false
- // if we are currently showing a persistent dot, hide it
- if (animationState.value == SHOWING_PERSISTENT_DOT) notifyHidePersistentDot()
- // if we are currently animating into a dot, wait for the animation to finish and then hide
- // the dot
- if (animationState.value == ANIMATING_OUT) {
- coroutineScope.launch {
- withTimeout(DISAPPEAR_ANIMATION_DURATION) {
- animationState.first {
- it == SHOWING_PERSISTENT_DOT || it == IDLE || it == ANIMATION_QUEUED
- }
- notifyHidePersistentDot()
- }
+
+ if (animationState.value == SHOWING_PERSISTENT_DOT) {
+ // if we are currently showing a persistent dot, hide it and update the animationState
+ notifyHidePersistentDot()
+ if (scheduledEvent.value != null) {
+ animationState.value = ANIMATION_QUEUED
+ } else {
+ animationState.value = IDLE
}
+ } else if (animationState.value == ANIMATING_OUT) {
+ // if we are currently animating out, hide the dot. The animationState will be updated
+ // once the animation has ended in the onAnimationEnd callback
+ notifyHidePersistentDot()
}
}
@@ -366,14 +366,6 @@
logger?.logHidePersistentDotCallbackInvoked()
val anims: List<Animator> = listeners.mapNotNull { it.onHidePersistentDot() }
- if (animationState.value == SHOWING_PERSISTENT_DOT) {
- if (scheduledEvent.value != null) {
- animationState.value = ANIMATION_QUEUED
- } else {
- animationState.value = IDLE
- }
- }
-
if (anims.isNotEmpty()) {
val aSet = AnimatorSet()
aSet.playTogether(anims)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographer.kt
index 4ebf337..ad22c61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographer.kt
@@ -51,7 +51,7 @@
object NotifPipelineChoreographerModule
@Module
-private interface PrivateModule {
+interface PrivateModule {
@Binds
fun bindChoreographer(impl: NotifPipelineChoreographerImpl): NotifPipelineChoreographer
}
@@ -59,7 +59,7 @@
private const val TIMEOUT_MS: Long = 100
@SysUISingleton
-private class NotifPipelineChoreographerImpl @Inject constructor(
+class NotifPipelineChoreographerImpl @Inject constructor(
private val viewChoreographer: Choreographer,
@Main private val executor: DelayableExecutor
) : NotifPipelineChoreographer {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
index aeeeb4f..9ba1f7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -38,7 +38,7 @@
interface SensitiveContentCoordinatorModule
@Module
-private interface PrivateSensitiveContentCoordinatorModule {
+interface PrivateSensitiveContentCoordinatorModule {
@Binds
fun bindCoordinator(impl: SensitiveContentCoordinatorImpl): SensitiveContentCoordinator
}
@@ -47,7 +47,7 @@
interface SensitiveContentCoordinator : Coordinator
@CoordinatorScope
-private class SensitiveContentCoordinatorImpl @Inject constructor(
+class SensitiveContentCoordinatorImpl @Inject constructor(
private val dynamicPrivacyController: DynamicPrivacyController,
private val lockscreenUserManager: NotificationLockscreenUserManager,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt
index 357c5b2..c00bb93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt
@@ -50,7 +50,7 @@
@Module(includes = [
SensitiveContentCoordinatorModule::class,
])
-private abstract class InternalCoordinatorsModule {
+abstract class InternalCoordinatorsModule {
@Binds
@Internal
abstract fun bindNotifCoordinators(impl: NotifCoordinatorsImpl): NotifCoordinators
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
index 7b59266..49990d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
@@ -38,7 +38,7 @@
}
@SectionHeaderScope
-internal class SectionHeaderNodeControllerImpl @Inject constructor(
+class SectionHeaderNodeControllerImpl @Inject constructor(
@NodeLabel override val nodeLabel: String,
private val layoutInflater: LayoutInflater,
@HeaderText @StringRes private val headerTextResId: Int,
@@ -103,4 +103,4 @@
override fun offerToKeepInParentForAnimation(): Boolean = false
override fun removeFromParentIfKeptForAnimation(): Boolean = false
override fun resetKeepInParentForAnimation() {}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt
index 2a9cfd0..75801a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt
@@ -145,7 +145,7 @@
}
@Module
-private abstract class SectionHeaderBindingModule {
+abstract class SectionHeaderBindingModule {
@Binds abstract fun bindsNodeController(impl: SectionHeaderNodeControllerImpl): NodeController
@Binds abstract fun bindsSectionHeaderController(
impl: SectionHeaderNodeControllerImpl
@@ -182,4 +182,4 @@
@Scope
@Retention(AnnotationRetention.BINARY)
-annotation class SectionHeaderScope
\ No newline at end of file
+annotation class SectionHeaderScope
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index 78225df..b2c32cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -57,7 +57,7 @@
object KeyguardNotificationVisibilityProviderModule
@Module
-private interface KeyguardNotificationVisibilityProviderImplModule {
+interface KeyguardNotificationVisibilityProviderImplModule {
@Binds
fun bindImpl(impl: KeyguardNotificationVisibilityProviderImpl):
KeyguardNotificationVisibilityProvider
@@ -69,7 +69,7 @@
}
@SysUISingleton
-private class KeyguardNotificationVisibilityProviderImpl @Inject constructor(
+class KeyguardNotificationVisibilityProviderImpl @Inject constructor(
@Main private val handler: Handler,
private val keyguardStateController: KeyguardStateController,
private val lockscreenUserManager: NotificationLockscreenUserManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index ed489a6c..d92d11b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1894,6 +1894,9 @@
return traceTag;
}
+ if (isSummaryWithChildren()) {
+ return traceTag + "(summary)";
+ }
Class<? extends Notification.Style> style =
getEntry().getSbn().getNotification().getNotificationStyle();
if (style == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 632f241..127569d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -634,7 +634,7 @@
private final ActivityIntentHelper mActivityIntentHelper;
- private final NotificationStackScrollLayoutController mStackScrollerController;
+ public final NotificationStackScrollLayoutController mStackScrollerController;
private final ColorExtractor.OnColorsChangedListener mOnColorsChangedListener =
(extractor, which) -> updateTheme();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index 7dcdc0b..97cb45a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -22,8 +22,6 @@
import android.view.View.LAYOUT_DIRECTION_RTL
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.policy.ConfigurationController
-
-import java.util.ArrayList
import javax.inject.Inject
@SysUISingleton
@@ -40,6 +38,7 @@
private var localeList: LocaleList? = null
private val context: Context
private var layoutDirection: Int
+ private var orientation = Configuration.ORIENTATION_UNDEFINED
init {
val currentConfig = context.resources.configuration
@@ -134,8 +133,18 @@
it.onThemeChanged()
}
}
+
+ val newOrientation = newConfig.orientation
+ if (orientation != newOrientation) {
+ orientation = newOrientation
+ listeners.filterForEach({ this.listeners.contains(it) }) {
+ it.onOrientationChanged(orientation)
+ }
+ }
}
+
+
override fun addCallback(listener: ConfigurationController.ConfigurationListener) {
listeners.add(listener)
listener.onDensityOrFontScaleChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index f15dcc3..a1f12b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -81,6 +81,7 @@
private final BiConsumer<Float, Float> mSetExpandedHeight = this::setAppearFraction;
private final KeyguardBypassController mBypassController;
private final StatusBarStateController mStatusBarStateController;
+ private final PhoneStatusBarTransitions mPhoneStatusBarTransitions;
private final CommandQueue mCommandQueue;
private final NotificationWakeUpCoordinator mWakeUpCoordinator;
@@ -109,6 +110,7 @@
NotificationIconAreaController notificationIconAreaController,
HeadsUpManagerPhone headsUpManager,
StatusBarStateController stateController,
+ PhoneStatusBarTransitions phoneStatusBarTransitions,
KeyguardBypassController bypassController,
NotificationWakeUpCoordinator wakeUpCoordinator,
DarkIconDispatcher darkIconDispatcher,
@@ -156,6 +158,7 @@
});
mBypassController = bypassController;
mStatusBarStateController = stateController;
+ mPhoneStatusBarTransitions = phoneStatusBarTransitions;
mWakeUpCoordinator = wakeUpCoordinator;
mCommandQueue = commandQueue;
mKeyguardStateController = keyguardStateController;
@@ -203,6 +206,7 @@
@Override
public void onHeadsUpStateChanged(@NonNull NotificationEntry entry, boolean isHeadsUp) {
updateHeadsUpAndPulsingRoundness(entry);
+ mPhoneStatusBarTransitions.onHeadsUpStateChanged(isHeadsUp);
}
private void updateTopEntry() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
index 15c6dcf..cc38405 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
@@ -31,6 +31,8 @@
private final float mIconAlphaWhenOpaque;
+ private boolean mIsHeadsUp;
+
private View mStartSide, mStatusIcons, mBattery;
private Animator mCurrentAnimation;
@@ -52,15 +54,32 @@
return ObjectAnimator.ofFloat(v, "alpha", v.getAlpha(), toAlpha);
}
- private float getNonBatteryClockAlphaFor(int mode) {
- return isLightsOut(mode) ? ICON_ALPHA_WHEN_LIGHTS_OUT_NON_BATTERY_CLOCK
- : !isOpaque(mode) ? ICON_ALPHA_WHEN_NOT_OPAQUE
- : mIconAlphaWhenOpaque;
+ private float getStatusIconsAlphaFor(int mode) {
+ return getDefaultAlphaFor(mode);
+ }
+
+ private float getStartSideAlphaFor(int mode) {
+ // When there's a heads up notification, we need the start side icons to show regardless of
+ // lights out mode.
+ if (mIsHeadsUp) {
+ return getIconAlphaBasedOnOpacity(mode);
+ }
+ return getDefaultAlphaFor(mode);
}
private float getBatteryClockAlpha(int mode) {
return isLightsOut(mode) ? ICON_ALPHA_WHEN_LIGHTS_OUT_BATTERY_CLOCK
- : getNonBatteryClockAlphaFor(mode);
+ : getIconAlphaBasedOnOpacity(mode);
+ }
+
+ private float getDefaultAlphaFor(int mode) {
+ return isLightsOut(mode) ? ICON_ALPHA_WHEN_LIGHTS_OUT_NON_BATTERY_CLOCK
+ : getIconAlphaBasedOnOpacity(mode);
+ }
+
+ private float getIconAlphaBasedOnOpacity(int mode) {
+ return !isOpaque(mode) ? ICON_ALPHA_WHEN_NOT_OPAQUE
+ : mIconAlphaWhenOpaque;
}
private boolean isOpaque(int mode) {
@@ -74,19 +93,28 @@
applyMode(newMode, animate);
}
+ /** Informs this controller that the heads up notification state has changed. */
+ public void onHeadsUpStateChanged(boolean isHeadsUp) {
+ mIsHeadsUp = isHeadsUp;
+ // We want the icon to be fully visible when the HUN appears, so just immediately change the
+ // icon visibility and don't animate.
+ applyMode(getMode(), /* animate= */ false);
+ }
+
private void applyMode(int mode, boolean animate) {
if (mStartSide == null) return; // pre-init
- float newAlpha = getNonBatteryClockAlphaFor(mode);
- float newAlphaBC = getBatteryClockAlpha(mode);
+ float newStartSideAlpha = getStartSideAlphaFor(mode);
+ float newStatusIconsAlpha = getStatusIconsAlphaFor(mode);
+ float newBatteryAlpha = getBatteryClockAlpha(mode);
if (mCurrentAnimation != null) {
mCurrentAnimation.cancel();
}
if (animate) {
AnimatorSet anims = new AnimatorSet();
anims.playTogether(
- animateTransitionTo(mStartSide, newAlpha),
- animateTransitionTo(mStatusIcons, newAlpha),
- animateTransitionTo(mBattery, newAlphaBC)
+ animateTransitionTo(mStartSide, newStartSideAlpha),
+ animateTransitionTo(mStatusIcons, newStatusIconsAlpha),
+ animateTransitionTo(mBattery, newBatteryAlpha)
);
if (isLightsOut(mode)) {
anims.setDuration(LIGHTS_OUT_DURATION);
@@ -94,9 +122,9 @@
anims.start();
mCurrentAnimation = anims;
} else {
- mStartSide.setAlpha(newAlpha);
- mStatusIcons.setAlpha(newAlpha);
- mBattery.setAlpha(newAlphaBC);
+ mStartSide.setAlpha(newStartSideAlpha);
+ mStatusIcons.setAlpha(newStatusIconsAlpha);
+ mBattery.setAlpha(newBatteryAlpha);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 5c1dfbe..ea57eb4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -22,6 +22,8 @@
import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -60,10 +62,14 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.bouncer.ui.BouncerView;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.TaskbarDelegate;
@@ -86,8 +92,6 @@
import com.android.systemui.unfold.FoldAodAnimationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
-import dagger.Lazy;
-
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
@@ -97,6 +101,9 @@
import javax.inject.Inject;
+import dagger.Lazy;
+import kotlinx.coroutines.CoroutineDispatcher;
+
/**
* Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
* via {@link ViewMediatorCallback} to poke the wake lock and report that the keyguard is done,
@@ -281,6 +288,9 @@
private int mLastBiometricMode;
private boolean mLastScreenOffAnimationPlaying;
private float mQsExpansion;
+
+ private FeatureFlags mFlags;
+
final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
private boolean mIsBackAnimationEnabled;
private final boolean mUdfpsNewTouchDetectionEnabled;
@@ -326,6 +336,7 @@
}
}
};
+ private Lazy<WindowManagerLockscreenVisibilityInteractor> mWmLockscreenVisibilityInteractor;
@Inject
public StatusBarKeyguardViewManager(
@@ -352,7 +363,10 @@
BouncerView primaryBouncerView,
AlternateBouncerInteractor alternateBouncerInteractor,
UdfpsOverlayInteractor udfpsOverlayInteractor,
- ActivityStarter activityStarter
+ ActivityStarter activityStarter,
+ KeyguardTransitionInteractor keyguardTransitionInteractor,
+ @Main CoroutineDispatcher mainDispatcher,
+ Lazy<WindowManagerLockscreenVisibilityInteractor> wmLockscreenVisibilityInteractor
) {
mContext = context;
mViewMediatorCallback = callback;
@@ -370,6 +384,7 @@
mShadeController = shadeController;
mLatencyTracker = latencyTracker;
mKeyguardSecurityModel = keyguardSecurityModel;
+ mFlags = featureFlags;
mPrimaryBouncerCallbackInteractor = primaryBouncerCallbackInteractor;
mPrimaryBouncerInteractor = primaryBouncerInteractor;
mPrimaryBouncerView = primaryBouncerView;
@@ -381,8 +396,14 @@
mUdfpsNewTouchDetectionEnabled = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION);
mUdfpsOverlayInteractor = udfpsOverlayInteractor;
mActivityStarter = activityStarter;
+ mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+ mMainDispatcher = mainDispatcher;
+ mWmLockscreenVisibilityInteractor = wmLockscreenVisibilityInteractor;
}
+ KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ CoroutineDispatcher mMainDispatcher;
+
@Override
public void registerCentralSurfaces(CentralSurfaces centralSurfaces,
ShadeViewController shadeViewController,
@@ -429,6 +450,14 @@
}
}
+ private KeyguardStateController.Callback mKeyguardStateControllerCallback =
+ new KeyguardStateController.Callback() {
+ @Override
+ public void onUnlockedChanged() {
+ updateAlternateBouncerShowing(mAlternateBouncerInteractor.maybeHide());
+ }
+ };
+
private void registerListeners() {
mKeyguardUpdateManager.registerCallback(mUpdateMonitorCallback);
mStatusBarStateController.addCallback(this);
@@ -442,6 +471,32 @@
mDockManager.addListener(mDockEventListener);
mIsDocked = mDockManager.isDocked();
}
+ mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
+
+ if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ mShadeViewController.postToView(() ->
+ collectFlow(
+ getViewRootImpl().getView(),
+ combineFlows(
+ mKeyguardTransitionInteractor.getFinishedKeyguardState(),
+ mWmLockscreenVisibilityInteractor.get()
+ .getUsingKeyguardGoingAwayAnimation(),
+ (finishedState, animating) ->
+ KeyguardInteractor.Companion.isKeyguardVisibleInState(
+ finishedState)
+ || animating),
+ this::consumeShowStatusBarKeyguardView));
+ }
+ }
+
+ private void consumeShowStatusBarKeyguardView(boolean show) {
+ if (show != mLastShowing) {
+ if (show) {
+ show(null);
+ } else {
+ hide(0, 0);
+ }
+ }
}
/** Register a callback, to be invoked by the Predictive Back system. */
@@ -1313,6 +1368,10 @@
hideAlternateBouncer(false);
executeAfterKeyguardGoneAction();
}
+
+ if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ mKeyguardTransitionInteractor.startDismissKeyguardTransition();
+ }
}
/** Display security message to relevant KeyguardMessageArea. */
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 b29d461..b5b99a7 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
@@ -29,9 +29,18 @@
/** Observable for the current wifi default status. */
val isWifiDefault: StateFlow<Boolean>
- /** Observable for the current wifi network. */
+ /** Observable for the current primary wifi network. */
val wifiNetwork: StateFlow<WifiNetworkModel>
+ /**
+ * Observable for secondary wifi networks (if any). Should specifically exclude the primary
+ * network emitted by [wifiNetwork].
+ *
+ * This isn't used by phones/tablets, which only display the primary network, but may be used by
+ * other variants like Car.
+ */
+ val secondaryNetworks: StateFlow<List<WifiNetworkModel>>
+
/** Observable for the current wifi network activity. */
val wifiActivity: StateFlow<DataActivityModel>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
index e96288a..80091ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
@@ -115,6 +115,11 @@
.flatMapLatest { it.wifiNetwork }
.stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.wifiNetwork.value)
+ override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
+ activeRepo
+ .flatMapLatest { it.secondaryNetworks }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.secondaryNetworks.value)
+
override val wifiActivity: StateFlow<DataActivityModel> =
activeRepo
.flatMapLatest { it.wifiActivity }
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 99b68005..4b19c3a 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
@@ -48,6 +48,9 @@
private val _wifiNetwork = MutableStateFlow<WifiNetworkModel>(WifiNetworkModel.Inactive)
override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork
+ private val _secondaryNetworks = MutableStateFlow<List<WifiNetworkModel>>(emptyList())
+ override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> = _secondaryNetworks
+
private val _wifiActivity =
MutableStateFlow(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
override val wifiActivity: StateFlow<DataActivityModel> = _wifiActivity
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
index 9ed7c6a..36c46a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
@@ -43,6 +43,9 @@
override val wifiNetwork: StateFlow<WifiNetworkModel> = MutableStateFlow(NETWORK).asStateFlow()
+ override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
+ MutableStateFlow(emptyList<WifiNetworkModel>()).asStateFlow()
+
override val wifiActivity: StateFlow<DataActivityModel> =
MutableStateFlow(ACTIVITY).asStateFlow()
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 afd1576..7c7b58d 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
@@ -56,8 +56,10 @@
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
@@ -216,6 +218,10 @@
initialValue = WIFI_NETWORK_DEFAULT,
)
+ // Secondary networks can only be supported by [WifiRepositoryViaTrackerLib].
+ override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
+ MutableStateFlow(emptyList<WifiNetworkModel>()).asStateFlow()
+
override val wifiActivity: StateFlow<DataActivityModel> =
WifiRepositoryHelper.createActivityFlow(
wifiManager,
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 175563b..d4f40dd 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
@@ -93,7 +93,8 @@
WifiPickerTrackerInfo(
state = WIFI_STATE_DEFAULT,
isDefault = false,
- network = WIFI_NETWORK_DEFAULT,
+ primaryNetwork = WIFI_NETWORK_DEFAULT,
+ secondaryNetworks = emptyList(),
)
callbackFlow {
val callback =
@@ -102,6 +103,17 @@
val connectedEntry = wifiPickerTracker?.connectedWifiEntry
logOnWifiEntriesChanged(connectedEntry)
+ val secondaryNetworks =
+ if (featureFlags.isEnabled(Flags.WIFI_SECONDARY_NETWORKS)) {
+ val activeNetworks =
+ wifiPickerTracker?.activeWifiEntries ?: emptyList()
+ activeNetworks
+ .filter { it != connectedEntry && !it.isPrimaryNetwork }
+ .map { it.toWifiNetworkModel() }
+ } else {
+ emptyList()
+ }
+
// [WifiPickerTracker.connectedWifiEntry] will return the same instance
// but with updated internals. For example, when its validation status
// changes from false to true, the same instance is re-used but with the
@@ -112,8 +124,9 @@
// into our internal model immediately. [toWifiNetworkModel] always
// returns a new instance, so the flow is guaranteed to emit.
send(
- newNetwork = connectedEntry?.toWifiNetworkModel()
+ newPrimaryNetwork = connectedEntry?.toPrimaryWifiNetworkModel()
?: WIFI_NETWORK_DEFAULT,
+ newSecondaryNetworks = secondaryNetworks,
newIsDefault = connectedEntry?.isDefaultNetwork ?: false,
)
}
@@ -131,9 +144,17 @@
private fun send(
newState: Int = current.state,
newIsDefault: Boolean = current.isDefault,
- newNetwork: WifiNetworkModel = current.network,
+ newPrimaryNetwork: WifiNetworkModel = current.primaryNetwork,
+ newSecondaryNetworks: List<WifiNetworkModel> =
+ current.secondaryNetworks,
) {
- val new = WifiPickerTrackerInfo(newState, newIsDefault, newNetwork)
+ val new =
+ WifiPickerTrackerInfo(
+ newState,
+ newIsDefault,
+ newPrimaryNetwork,
+ newSecondaryNetworks,
+ )
current = new
trySend(new)
}
@@ -170,7 +191,7 @@
override val wifiNetwork: StateFlow<WifiNetworkModel> =
wifiPickerTrackerInfo
- .map { it.network }
+ .map { it.primaryNetwork }
.distinctUntilChanged()
.logDiffsForTable(
wifiTrackerLibTableLogBuffer,
@@ -179,11 +200,32 @@
)
.stateIn(scope, SharingStarted.Eagerly, WIFI_NETWORK_DEFAULT)
+ override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
+ wifiPickerTrackerInfo
+ .map { it.secondaryNetworks }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ wifiTrackerLibTableLogBuffer,
+ columnPrefix = "",
+ columnName = "secondaryNetworks",
+ initialValue = emptyList(),
+ )
+ .stateIn(scope, SharingStarted.Eagerly, emptyList())
+
+ /**
+ * Converts WifiTrackerLib's [WifiEntry] into our internal model only if the entry is the
+ * primary network. Returns an inactive network if it's not primary.
+ */
+ private fun WifiEntry.toPrimaryWifiNetworkModel(): WifiNetworkModel {
+ return if (!this.isPrimaryNetwork) {
+ WIFI_NETWORK_DEFAULT
+ } else {
+ this.toWifiNetworkModel()
+ }
+ }
+
/** Converts WifiTrackerLib's [WifiEntry] into our internal model. */
private fun WifiEntry.toWifiNetworkModel(): WifiNetworkModel {
- if (!this.isPrimaryNetwork) {
- return WIFI_NETWORK_DEFAULT
- }
return if (this is MergedCarrierEntry) {
this.convertCarrierMergedToModel()
} else {
@@ -291,7 +333,9 @@
/** True if wifi is currently the default connection and false otherwise. */
val isDefault: Boolean,
/** The currently primary wifi network. */
- val network: WifiNetworkModel,
+ val primaryNetwork: WifiNetworkModel,
+ /** The current secondary network(s), if any. Specifically excludes the primary network. */
+ val secondaryNetworks: List<WifiNetworkModel>
)
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
index 6b80a9d..b2ef818 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
@@ -42,5 +42,6 @@
default void onThemeChanged() {}
default void onLocaleListChanged() {}
default void onLayoutDirectionChanged(boolean isLayoutRtl) {}
+ default void onOrientationChanged(int orientation) {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 710588c..63dcad9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -18,6 +18,8 @@
import static android.hardware.biometrics.BiometricSourceType.FACE;
+import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
+
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -38,6 +40,7 @@
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import dagger.Lazy;
@@ -49,6 +52,7 @@
import javax.inject.Inject;
/**
+ *
*/
@SysUISingleton
public class KeyguardStateControllerImpl implements KeyguardStateController, Dumpable {
@@ -103,7 +107,10 @@
*/
private boolean mSnappingKeyguardBackAfterSwipe = false;
+ private FeatureFlags mFeatureFlags;
+
/**
+ *
*/
@Inject
public KeyguardStateControllerImpl(
@@ -112,13 +119,15 @@
LockPatternUtils lockPatternUtils,
Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController,
KeyguardUpdateMonitorLogger logger,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ FeatureFlags featureFlags) {
mContext = context;
mLogger = logger;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
mUnlockAnimationControllerLazy = keyguardUnlockAnimationController;
+ mFeatureFlags = featureFlags;
dumpManager.registerDumpable(getClass().getSimpleName(), this);
@@ -272,7 +281,8 @@
@Override
public boolean isKeyguardScreenRotationAllowed() {
return SystemProperties.getBoolean("lockscreen.rot_override", false)
- || mContext.getResources().getBoolean(R.bool.config_enableLockScreenRotation);
+ || mContext.getResources().getBoolean(R.bool.config_enableLockScreenRotation)
+ || mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/RemoteInput.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/RemoteInput.kt
index d4abc40..d31c001 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/RemoteInput.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/RemoteInput.kt
@@ -41,7 +41,7 @@
}
@Module
-private interface InternalRemoteInputViewModule {
+interface InternalRemoteInputViewModule {
@Binds
fun bindController(impl: RemoteInputViewControllerImpl): RemoteInputViewController
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 12d7b4d..0d0a646 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -29,7 +29,7 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
/** A class allowing Java classes to collect on Kotlin flows. */
@@ -75,3 +75,7 @@
repeatOnLifecycle(state) { flow.collect { consumer.accept(it) } }
}
}
+
+fun <A, B, R> combineFlows(flow1: Flow<A>, flow2: Flow<B>, bifunction: (A, B) -> R): Flow<R> {
+ return combine(flow1, flow2, bifunction)
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 9ba21da..e7483ad 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -38,6 +38,7 @@
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate
import com.android.systemui.biometrics.SideFpsController
import com.android.systemui.biometrics.SideFpsUiRequestSource
@@ -46,6 +47,8 @@
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.log.SessionTracker
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.plugins.FalsingManager
@@ -144,6 +147,8 @@
private lateinit var testableResources: TestableResources
private lateinit var sceneTestUtils: SceneTestUtils
private lateinit var sceneInteractor: SceneInteractor
+ private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+ private lateinit var authenticationInteractor: AuthenticationInteractor
private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState>
private lateinit var underTest: KeyguardSecurityContainerController
@@ -182,6 +187,7 @@
featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
featureFlags.set(Flags.SCENE_CONTAINER, false)
featureFlags.set(Flags.BOUNCER_USER_SWITCHER, false)
+ featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
keyguardPasswordViewController =
KeyguardPasswordViewController(
@@ -204,9 +210,17 @@
whenever(userInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID)
sceneTestUtils = SceneTestUtils(this)
sceneInteractor = sceneTestUtils.sceneInteractor()
+ keyguardTransitionInteractor =
+ KeyguardTransitionInteractorFactory.create(sceneTestUtils.testScope.backgroundScope)
+ .keyguardTransitionInteractor
sceneTransitionStateFlow =
MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen))
sceneInteractor.setTransitionState(sceneTransitionStateFlow)
+ authenticationInteractor =
+ sceneTestUtils.authenticationInteractor(
+ repository = sceneTestUtils.authenticationRepository(),
+ sceneInteractor = sceneInteractor
+ )
underTest =
KeyguardSecurityContainerController(
@@ -236,8 +250,9 @@
{ JavaAdapter(sceneTestUtils.testScope.backgroundScope) },
userInteractor,
faceAuthAccessibilityDelegate,
+ keyguardTransitionInteractor
) {
- sceneInteractor
+ authenticationInteractor
}
}
@@ -753,7 +768,7 @@
}
@Test
- fun dismissesKeyguard_whenSceneChangesFromBouncerToGone() =
+ fun dismissesKeyguard_whenSceneChangesToGone() =
sceneTestUtils.testScope.runTest {
featureFlags.set(Flags.SCENE_CONTAINER, true)
@@ -815,23 +830,30 @@
runCurrent()
verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
- // While not listening, moving back to the bouncer does not dismiss the keyguard.
- sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason")
+ // While not listening, moving to the lockscreen does not dismiss the keyguard.
+ sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen, null), "reason")
sceneTransitionStateFlow.value =
- ObservableTransitionState.Transition(SceneKey.Gone, SceneKey.Bouncer, flowOf(.5f))
+ ObservableTransitionState.Transition(
+ SceneKey.Gone,
+ SceneKey.Lockscreen,
+ flowOf(.5f)
+ )
runCurrent()
- sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason")
- sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer)
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen, null), "reason")
+ sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Lockscreen)
runCurrent()
verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
// Reattaching the view starts listening again so moving from the bouncer scene to the
- // gone
- // scene now does dismiss the keyguard again.
+ // gone scene now does dismiss the keyguard again, this time from lockscreen.
underTest.onViewAttached()
sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason")
sceneTransitionStateFlow.value =
- ObservableTransitionState.Transition(SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f))
+ ObservableTransitionState.Transition(
+ SceneKey.Lockscreen,
+ SceneKey.Gone,
+ flowOf(.5f)
+ )
runCurrent()
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason")
sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index 956e0b81..b18137c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -49,7 +49,7 @@
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
@@ -164,8 +164,9 @@
mVibrator,
mAuthRippleController,
mResources,
- new KeyguardTransitionInteractor(mTransitionRepository,
- TestScopeProvider.getTestScope().getBackgroundScope()),
+ KeyguardTransitionInteractorFactory.create(
+ TestScopeProvider.getTestScope().getBackgroundScope(),
+ mTransitionRepository).getKeyguardTransitionInteractor(),
KeyguardInteractorFactory.create(mFeatureFlags).getKeyguardInteractor(),
mFeatureFlags,
mPrimaryBouncerInteractor
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt
index 9fcb9c8..7c2550f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt
@@ -47,10 +47,10 @@
@Test
fun testOnLayout() {
- underTest.onLayout(100)
+ underTest.onLayout(100, 100)
verify(background).cornerRadius = 50f
reset(background)
- underTest.onLayout(100)
+ underTest.onLayout(100, 100)
verify(background, never()).cornerRadius = anyFloat()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
index 01d3a39..ea7cc3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
@@ -27,6 +27,8 @@
import com.android.systemui.biometrics.AuthController
import com.android.systemui.decor.FaceScanningProviderFactory
import com.android.systemui.dump.logcatLogBuffer
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.log.ScreenDecorationsLogger
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.util.mockito.whenever
@@ -80,6 +82,8 @@
R.bool.config_fillMainBuiltInDisplayCutout,
true
)
+ val featureFlags = FakeFeatureFlags()
+ featureFlags.set(Flags.STOP_PULSING_FACE_SCANNING_ANIMATION, true)
underTest =
FaceScanningProviderFactory(
authController,
@@ -87,7 +91,8 @@
statusBarStateController,
keyguardUpdateMonitor,
mock(Executor::class.java),
- ScreenDecorationsLogger(logcatLogBuffer("FaceScanningProviderFactoryTest"))
+ ScreenDecorationsLogger(logcatLogBuffer("FaceScanningProviderFactoryTest")),
+ featureFlags,
)
whenever(authController.faceSensorLocation).thenReturn(Point(10, 10))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 796e665..f81ef10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -92,6 +92,8 @@
import com.android.systemui.decor.PrivacyDotCornerDecorProviderImpl;
import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
import com.android.systemui.decor.RoundedCornerResDelegate;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.log.ScreenDecorationsLogger;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.FakeDisplayTracker;
@@ -226,13 +228,16 @@
doAnswer(it -> !(mMockCutoutList.isEmpty())).when(mCutoutFactory).getHasProviders();
doReturn(mMockCutoutList).when(mCutoutFactory).getProviders();
+ FakeFeatureFlags featureFlags = new FakeFeatureFlags();
+ featureFlags.set(Flags.STOP_PULSING_FACE_SCANNING_ANIMATION, true);
mFaceScanningDecorProvider = spy(new FaceScanningOverlayProviderImpl(
BOUNDS_POSITION_TOP,
mAuthController,
mStatusBarStateController,
mKeyguardUpdateMonitor,
mExecutor,
- new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer"))));
+ new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
+ featureFlags));
mScreenDecorations = spy(new ScreenDecorations(mContext, mSecureSettings,
mCommandRegistry, mUserTracker, mDisplayTracker, mDotViewController,
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 d848cd4..fc7d20a 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
@@ -27,6 +27,8 @@
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
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 kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
@@ -46,9 +48,11 @@
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
private val repository: AuthenticationRepository = utils.authenticationRepository()
+ private val sceneInteractor = utils.sceneInteractor()
private val underTest =
utils.authenticationInteractor(
repository = repository,
+ sceneInteractor = sceneInteractor,
)
@Test
@@ -75,10 +79,10 @@
val authMethod by collectLastValue(underTest.authenticationMethod)
runCurrent()
- utils.authenticationRepository.setAuthenticationMethod(
- DataLayerAuthenticationMethodModel.None
- )
- utils.authenticationRepository.setLockscreenEnabled(true)
+ utils.authenticationRepository.apply {
+ setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+ setLockscreenEnabled(true)
+ }
assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Swipe)
assertThat(underTest.getAuthenticationMethod())
@@ -91,10 +95,10 @@
val authMethod by collectLastValue(underTest.authenticationMethod)
runCurrent()
- utils.authenticationRepository.setAuthenticationMethod(
- DataLayerAuthenticationMethodModel.None
- )
- utils.authenticationRepository.setLockscreenEnabled(false)
+ utils.authenticationRepository.apply {
+ setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+ setLockscreenEnabled(false)
+ }
assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.None)
assertThat(underTest.getAuthenticationMethod())
@@ -104,51 +108,87 @@
@Test
fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(
- DataLayerAuthenticationMethodModel.None
- )
- utils.authenticationRepository.setLockscreenEnabled(false)
-
val isUnlocked by collectLastValue(underTest.isUnlocked)
- // Toggle isUnlocked, twice.
- //
- // This is done because the underTest.isUnlocked flow doesn't receive values from
- // just changing the state above; the actual isUnlocked state needs to change to
- // cause the logic under test to "pick up" the current state again.
- //
- // It is done twice to make sure that we don't actually change the isUnlocked
- // state from what it originally was.
- utils.authenticationRepository.setUnlocked(
- !utils.authenticationRepository.isUnlocked.value
- )
- runCurrent()
- utils.authenticationRepository.setUnlocked(
- !utils.authenticationRepository.isUnlocked.value
- )
- runCurrent()
+ utils.authenticationRepository.apply {
+ setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+ setLockscreenEnabled(false)
+ // Toggle isUnlocked, twice.
+ //
+ // This is done because the underTest.isUnlocked flow doesn't receive values from
+ // just changing the state above; the actual isUnlocked state needs to change to
+ // cause the logic under test to "pick up" the current state again.
+ //
+ // It is done twice to make sure that we don't actually change the isUnlocked state
+ // from what it originally was.
+ setUnlocked(!utils.authenticationRepository.isUnlocked.value)
+ runCurrent()
+ setUnlocked(!utils.authenticationRepository.isUnlocked.value)
+ runCurrent()
+ }
+
assertThat(isUnlocked).isTrue()
}
@Test
- fun isUnlocked_whenAuthMethodIsNoneAndLockscreenEnabled_isFalse() =
+ fun isUnlocked_whenAuthMethodIsNoneAndLockscreenEnabled_isTrue() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(
- DataLayerAuthenticationMethodModel.None
- )
- utils.authenticationRepository.setLockscreenEnabled(true)
+ utils.authenticationRepository.apply {
+ setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+ setLockscreenEnabled(true)
+ }
val isUnlocked by collectLastValue(underTest.isUnlocked)
- assertThat(isUnlocked).isFalse()
+ assertThat(isUnlocked).isTrue()
+ }
+
+ @Test
+ fun canSwipeToDismiss_onLockscreenWithSwipe_isTrue() =
+ testScope.runTest {
+ utils.authenticationRepository.apply {
+ setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+ setLockscreenEnabled(true)
+ }
+ switchToScene(SceneKey.Lockscreen)
+
+ val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss)
+ assertThat(canSwipeToDismiss).isTrue()
+ }
+
+ @Test
+ fun canSwipeToDismiss_onLockscreenWithPin_isFalse() =
+ testScope.runTest {
+ utils.authenticationRepository.apply {
+ setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+ setLockscreenEnabled(true)
+ }
+ switchToScene(SceneKey.Lockscreen)
+
+ val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss)
+ assertThat(canSwipeToDismiss).isFalse()
+ }
+
+ @Test
+ fun canSwipeToDismiss_afterLockscreenDismissedInSwipeMode_isFalse() =
+ testScope.runTest {
+ utils.authenticationRepository.apply {
+ setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+ setLockscreenEnabled(true)
+ }
+ switchToScene(SceneKey.Lockscreen)
+ switchToScene(SceneKey.Gone)
+
+ val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss)
+ assertThat(canSwipeToDismiss).isFalse()
}
@Test
fun isAuthenticationRequired_lockedAndSecured_true() =
testScope.runTest {
- utils.authenticationRepository.setUnlocked(false)
- runCurrent()
- utils.authenticationRepository.setAuthenticationMethod(
- DataLayerAuthenticationMethodModel.Password
- )
+ utils.authenticationRepository.apply {
+ setUnlocked(false)
+ runCurrent()
+ setAuthenticationMethod(DataLayerAuthenticationMethodModel.Password)
+ }
assertThat(underTest.isAuthenticationRequired()).isTrue()
}
@@ -156,11 +196,11 @@
@Test
fun isAuthenticationRequired_lockedAndNotSecured_false() =
testScope.runTest {
- utils.authenticationRepository.setUnlocked(false)
- runCurrent()
- utils.authenticationRepository.setAuthenticationMethod(
- DataLayerAuthenticationMethodModel.None
- )
+ utils.authenticationRepository.apply {
+ setUnlocked(false)
+ runCurrent()
+ setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+ }
assertThat(underTest.isAuthenticationRequired()).isFalse()
}
@@ -168,11 +208,11 @@
@Test
fun isAuthenticationRequired_unlockedAndSecured_false() =
testScope.runTest {
- utils.authenticationRepository.setUnlocked(true)
- runCurrent()
- utils.authenticationRepository.setAuthenticationMethod(
- DataLayerAuthenticationMethodModel.Password
- )
+ utils.authenticationRepository.apply {
+ setUnlocked(true)
+ runCurrent()
+ setAuthenticationMethod(DataLayerAuthenticationMethodModel.Password)
+ }
assertThat(underTest.isAuthenticationRequired()).isFalse()
}
@@ -180,11 +220,11 @@
@Test
fun isAuthenticationRequired_unlockedAndNotSecured_false() =
testScope.runTest {
- utils.authenticationRepository.setUnlocked(true)
- runCurrent()
- utils.authenticationRepository.setAuthenticationMethod(
- DataLayerAuthenticationMethodModel.None
- )
+ utils.authenticationRepository.apply {
+ setUnlocked(true)
+ runCurrent()
+ setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+ }
assertThat(underTest.isAuthenticationRequired()).isFalse()
}
@@ -221,11 +261,12 @@
@Test
fun authenticate_withCorrectMaxLengthPin_returnsTrue() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(
- DataLayerAuthenticationMethodModel.Pin
- )
val pin = List(16) { 9 }
- utils.authenticationRepository.overrideCredential(pin)
+ utils.authenticationRepository.apply {
+ setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+ overrideCredential(pin)
+ }
+
assertThat(underTest.authenticate(pin)).isTrue()
}
@@ -308,10 +349,10 @@
fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNullAndHasNoEffect() =
testScope.runTest {
val isThrottled by collectLastValue(underTest.isThrottled)
- utils.authenticationRepository.setAuthenticationMethod(
- DataLayerAuthenticationMethodModel.Pin
- )
- utils.authenticationRepository.setAutoConfirmEnabled(true)
+ utils.authenticationRepository.apply {
+ setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+ setAutoConfirmEnabled(true)
+ }
assertThat(
underTest.authenticate(
FakeAuthenticationRepository.DEFAULT_PIN.toMutableList().apply {
@@ -328,10 +369,10 @@
fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalseAndDoesNotUnlockDevice() =
testScope.runTest {
val isUnlocked by collectLastValue(underTest.isUnlocked)
- utils.authenticationRepository.setAuthenticationMethod(
- DataLayerAuthenticationMethodModel.Pin
- )
- utils.authenticationRepository.setAutoConfirmEnabled(true)
+ utils.authenticationRepository.apply {
+ setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+ setAutoConfirmEnabled(true)
+ }
assertThat(
underTest.authenticate(
FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 },
@@ -346,10 +387,10 @@
fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalseAndDoesNotUnlockDevice() =
testScope.runTest {
val isUnlocked by collectLastValue(underTest.isUnlocked)
- utils.authenticationRepository.setAuthenticationMethod(
- DataLayerAuthenticationMethodModel.Pin
- )
- utils.authenticationRepository.setAutoConfirmEnabled(true)
+ utils.authenticationRepository.apply {
+ setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+ setAutoConfirmEnabled(true)
+ }
assertThat(
underTest.authenticate(
FakeAuthenticationRepository.DEFAULT_PIN + listOf(7),
@@ -364,10 +405,10 @@
fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrueAndUnlocksDevice() =
testScope.runTest {
val isUnlocked by collectLastValue(underTest.isUnlocked)
- utils.authenticationRepository.setAuthenticationMethod(
- DataLayerAuthenticationMethodModel.Pin
- )
- utils.authenticationRepository.setAutoConfirmEnabled(true)
+ utils.authenticationRepository.apply {
+ setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+ setAutoConfirmEnabled(true)
+ }
assertThat(
underTest.authenticate(
FakeAuthenticationRepository.DEFAULT_PIN,
@@ -382,10 +423,10 @@
fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNullAndHasNoEffects() =
testScope.runTest {
val isUnlocked by collectLastValue(underTest.isUnlocked)
- utils.authenticationRepository.setAuthenticationMethod(
- DataLayerAuthenticationMethodModel.Pin
- )
- utils.authenticationRepository.setAutoConfirmEnabled(false)
+ utils.authenticationRepository.apply {
+ setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+ setAutoConfirmEnabled(false)
+ }
assertThat(
underTest.authenticate(
FakeAuthenticationRepository.DEFAULT_PIN,
@@ -505,10 +546,10 @@
fun hintedPinLength_withoutAutoConfirm_isNull() =
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.setAuthenticationMethod(
- DataLayerAuthenticationMethodModel.Pin
- )
- utils.authenticationRepository.setAutoConfirmEnabled(false)
+ utils.authenticationRepository.apply {
+ setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+ setAutoConfirmEnabled(false)
+ }
assertThat(hintedPinLength).isNull()
}
@@ -517,15 +558,15 @@
fun hintedPinLength_withAutoConfirmPinTooShort_isNull() =
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.setAuthenticationMethod(
- DataLayerAuthenticationMethodModel.Pin
- )
- utils.authenticationRepository.overrideCredential(
- buildList {
- repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) }
- }
- )
- utils.authenticationRepository.setAutoConfirmEnabled(true)
+ utils.authenticationRepository.apply {
+ setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+ overrideCredential(
+ buildList {
+ repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) }
+ }
+ )
+ setAutoConfirmEnabled(true)
+ }
assertThat(hintedPinLength).isNull()
}
@@ -534,13 +575,15 @@
fun hintedPinLength_withAutoConfirmPinAtRightLength_isSameLength() =
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.setAuthenticationMethod(
- DataLayerAuthenticationMethodModel.Pin
- )
- utils.authenticationRepository.setAutoConfirmEnabled(true)
- utils.authenticationRepository.overrideCredential(
- buildList { repeat(utils.authenticationRepository.hintedPinLength) { add(it + 1) } }
- )
+ utils.authenticationRepository.apply {
+ setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+ setAutoConfirmEnabled(true)
+ overrideCredential(
+ buildList {
+ repeat(utils.authenticationRepository.hintedPinLength) { add(it + 1) }
+ }
+ )
+ }
assertThat(hintedPinLength).isEqualTo(utils.authenticationRepository.hintedPinLength)
}
@@ -549,16 +592,20 @@
fun hintedPinLength_withAutoConfirmPinTooLong_isNull() =
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.setAuthenticationMethod(
- DataLayerAuthenticationMethodModel.Pin
- )
- utils.authenticationRepository.overrideCredential(
- buildList {
- repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) }
- }
- )
- utils.authenticationRepository.setAutoConfirmEnabled(true)
+ utils.authenticationRepository.apply {
+ setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+ overrideCredential(
+ buildList {
+ repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) }
+ }
+ )
+ setAutoConfirmEnabled(true)
+ }
assertThat(hintedPinLength).isNull()
}
+
+ private fun switchToScene(sceneKey: SceneKey) {
+ sceneInteractor.changeScene(SceneModel(sceneKey), "reason")
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
index 49cdfa7..7311f4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
@@ -33,15 +33,14 @@
import android.app.AlarmManager;
import android.os.Handler;
import android.os.HandlerThread;
+import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.DejankUtils;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.wakelock.WakeLockFake;
import org.junit.After;
@@ -53,6 +52,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@TestableLooper.RunWithLooper
public class DozeUiTest extends SysuiTestCase {
@Mock
@@ -62,23 +62,19 @@
@Mock
private DozeParameters mDozeParameters;
@Mock
- private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock
private DozeHost mHost;
@Mock
private DozeLog mDozeLog;
- @Mock
- private TunerService mTunerService;
private WakeLockFake mWakeLock;
private Handler mHandler;
private HandlerThread mHandlerThread;
private DozeUi mDozeUi;
- @Mock
- private StatusBarStateController mStatusBarStateController;
@Before
public void setUp() throws Exception {
+ allowTestableLooperAsMainThread();
MockitoAnnotations.initMocks(this);
+ DejankUtils.setImmediate(true);
mHandlerThread = new HandlerThread("DozeUiTest");
mHandlerThread.start();
@@ -86,12 +82,13 @@
mHandler = mHandlerThread.getThreadHandler();
mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler,
- mDozeParameters, mStatusBarStateController, mDozeLog);
+ mDozeParameters, mDozeLog);
mDozeUi.setDozeMachine(mMachine);
}
@After
public void tearDown() throws Exception {
+ DejankUtils.setImmediate(false);
mHandlerThread.quit();
mHandler = null;
mHandlerThread = null;
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 28328c2..4a79a21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -236,7 +236,7 @@
mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager,
mShadeWindowLogger);
mFeatureFlags = new FakeFeatureFlags();
-
+ mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
DejankUtils.setImmediate(true);
@@ -634,6 +634,7 @@
TestableLooper.get(this).processAllMessages();
assertFalse(mViewMediator.isShowingAndNotOccluded());
+ verify(mKeyguardUnlockAnimationController).notifyFinishedKeyguardExitAnimation(false);
}
@Test
@@ -650,6 +651,7 @@
TestableLooper.get(this).processAllMessages();
assertTrue(mViewMediator.isShowingAndNotOccluded());
+ verify(mKeyguardUnlockAnimationController).notifyFinishedKeyguardExitAnimation(true);
}
@Test
@@ -658,6 +660,9 @@
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 @@
@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
@@ -1058,7 +1068,8 @@
mSystemClock,
mDispatcher,
() -> mDreamingToLockscreenTransitionViewModel,
- mSystemPropertiesHelper);
+ mSystemPropertiesHelper,
+ () -> mock(WindowManagerLockscreenVisibilityManager.class));
mViewMediator.start();
mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 85ee0e4..04ebbf6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -52,6 +52,7 @@
import com.android.systemui.dump.logcatLogBuffer
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
+import com.android.systemui.flags.Flags.KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
@@ -168,7 +169,11 @@
biometricSettingsRepository = FakeBiometricSettingsRepository()
deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
trustRepository = FakeTrustRepository()
- featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) }
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(FACE_AUTH_REFACTOR, true)
+ set(KEYGUARD_WM_STATE_REFACTOR, false)
+ }
val withDeps =
KeyguardInteractorFactory.create(
featureFlags = featureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 5e3376a..5ead16b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -63,6 +63,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -193,7 +194,7 @@
assertThat(underTest.isKeyguardShowing()).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
- verify(keyguardStateController).addCallback(captor.capture())
+ verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
whenever(keyguardStateController.isShowing).thenReturn(true)
captor.value.onKeyguardShowingChanged()
@@ -255,7 +256,7 @@
assertThat(latest).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
- verify(keyguardStateController).addCallback(captor.capture())
+ verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
whenever(keyguardStateController.isOccluded).thenReturn(true)
captor.value.onKeyguardShowingChanged()
@@ -280,7 +281,7 @@
assertThat(isKeyguardUnlocked).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
- verify(keyguardStateController).addCallback(captor.capture())
+ verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
whenever(keyguardStateController.isUnlocked).thenReturn(true)
captor.value.onUnlockedChanged()
@@ -454,7 +455,7 @@
assertThat(latest).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
- verify(keyguardStateController).addCallback(captor.capture())
+ verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(true)
captor.value.onKeyguardGoingAwayChanged()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt
new file mode 100644
index 0000000..bed959f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardSurfaceBehindRepositoryImplTest : SysuiTestCase() {
+ private val testScope = TestScope()
+
+ private lateinit var underTest: KeyguardSurfaceBehindRepositoryImpl
+
+ @Before
+ fun setUp() {
+ underTest = KeyguardSurfaceBehindRepositoryImpl()
+ }
+
+ @Test
+ fun testSetAnimatingSurface() {
+ testScope.runTest {
+ val values by collectValues(underTest.isAnimatingSurface)
+
+ runCurrent()
+ underTest.setAnimatingSurface(true)
+ runCurrent()
+ underTest.setAnimatingSurface(false)
+ runCurrent()
+
+ // Default (first) value should be false.
+ assertThat(values).isEqualTo(listOf(false, true, false))
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
new file mode 100644
index 0000000..e2bf2f8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
@@ -0,0 +1,190 @@
+/*
+ * 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.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import dagger.Lazy
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import junit.framework.Assert.fail
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromLockscreenTransitionInteractorTest : KeyguardTransitionInteractorTestCase() {
+ private lateinit var underTest: FromLockscreenTransitionInteractor
+
+ // Override the fromLockscreenTransitionInteractor provider from the superclass so our underTest
+ // interactor is provided to any classes that need it.
+ override var fromLockscreenTransitionInteractorLazy: Lazy<FromLockscreenTransitionInteractor>? =
+ Lazy {
+ underTest
+ }
+
+ @Before
+ override fun setUp() {
+ super.setUp()
+
+ underTest =
+ FromLockscreenTransitionInteractor(
+ transitionRepository = super.transitionRepository,
+ transitionInteractor = super.transitionInteractor,
+ scope = super.testScope.backgroundScope,
+ keyguardInteractor = super.keyguardInteractor,
+ flags = FakeFeatureFlags(),
+ shadeRepository = FakeShadeRepository(),
+ )
+ }
+
+ @Test
+ fun testSurfaceBehindVisibility_nonNullOnlyForRelevantTransitions() =
+ testScope.runTest {
+ val values by collectValues(underTest.surfaceBehindVisibility)
+ runCurrent()
+
+ // Transition-specific surface visibility should be null ("don't care") initially.
+ assertEquals(
+ listOf(
+ null,
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ )
+ )
+
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ null, // LOCKSCREEN -> AOD does not have any specific surface visibility.
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ null,
+ true, // Surface is made visible immediately during LOCKSCREEN -> GONE
+ ),
+ values
+ )
+ }
+
+ @Test
+ fun testSurfaceBehindModel() =
+ testScope.runTest {
+ val values by collectValues(underTest.surfaceBehindModel)
+ runCurrent()
+
+ assertEquals(
+ values,
+ listOf(
+ null, // We should start null ("don't care").
+ )
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ null, // LOCKSCREEN -> AOD does not have specific view params.
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.RUNNING,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ value = 0.01f,
+ )
+ )
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.RUNNING,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ value = 0.99f,
+ )
+ )
+ runCurrent()
+
+ assertEquals(3, values.size)
+ val model1percent = values[1]
+ val model99percent = values[2]
+
+ try {
+ // We should initially have an alpha of 0f when unlocking, so the surface is not
+ // visible
+ // while lockscreen UI animates out.
+ assertEquals(0f, model1percent!!.alpha)
+
+ // By the end it should probably be visible.
+ assertTrue(model99percent!!.alpha > 0f)
+ } catch (e: NullPointerException) {
+ fail("surfaceBehindModel was unexpectedly null.")
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
new file mode 100644
index 0000000..85bc374
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
@@ -0,0 +1,214 @@
+/*
+ * 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.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.mockito.mock
+import dagger.Lazy
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import junit.framework.Assert.fail
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromPrimaryBouncerTransitionInteractorTest : KeyguardTransitionInteractorTestCase() {
+ private lateinit var underTest: FromPrimaryBouncerTransitionInteractor
+
+ // Override the fromPrimaryBouncerTransitionInteractor provider from the superclass so our
+ // underTest interactor is provided to any classes that need it.
+ override var fromPrimaryBouncerTransitionInteractorLazy:
+ Lazy<FromPrimaryBouncerTransitionInteractor>? =
+ Lazy {
+ underTest
+ }
+
+ @Before
+ override fun setUp() {
+ super.setUp()
+
+ underTest =
+ FromPrimaryBouncerTransitionInteractor(
+ transitionRepository = super.transitionRepository,
+ transitionInteractor = super.transitionInteractor,
+ scope = super.testScope.backgroundScope,
+ keyguardInteractor = super.keyguardInteractor,
+ flags = FakeFeatureFlags(),
+ keyguardSecurityModel = mock(),
+ )
+ }
+
+ @Test
+ fun testSurfaceBehindVisibility() =
+ testScope.runTest {
+ val values by collectValues(underTest.surfaceBehindVisibility)
+ runCurrent()
+
+ // Transition-specific surface visibility should be null ("don't care") initially.
+ assertEquals(
+ listOf(
+ null,
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ null, // PRIMARY_BOUNCER -> LOCKSCREEN does not have any specific visibility.
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ )
+ )
+
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.RUNNING,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ value = 0.01f,
+ )
+ )
+
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ null,
+ false, // Surface is only made visible once the bouncer UI animates out.
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ value = 0.99f,
+ )
+ )
+
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ null,
+ false,
+ true, // Surface should eventually be visible.
+ ),
+ values
+ )
+ }
+
+ @Test
+ fun testSurfaceBehindModel() =
+ testScope.runTest {
+ val values by collectValues(underTest.surfaceBehindModel)
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ null, // PRIMARY_BOUNCER -> LOCKSCREEN does not have specific view params.
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ )
+ )
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.RUNNING,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ value = 0.01f,
+ )
+ )
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.RUNNING,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ value = 0.99f,
+ )
+ )
+ runCurrent()
+
+ assertEquals(3, values.size)
+ val model1percent = values[1]
+ val model99percent = values[2]
+
+ try {
+ // We should initially have an alpha of 0f when unlocking, so the surface is not
+ // visible
+ // while lockscreen UI animates out.
+ assertEquals(0f, model1percent!!.alpha)
+
+ // By the end it should probably be visible.
+ assertTrue(model99percent!!.alpha > 0f)
+ } catch (e: NullPointerException) {
+ fail("surfaceBehindModel was unexpectedly null.")
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
new file mode 100644
index 0000000..fdcc66b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
@@ -0,0 +1,169 @@
+/*
+ * 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.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.mockito.whenever
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.TestScope
+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.mockito.Mock
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class KeyguardSurfaceBehindInteractorTest : SysuiTestCase() {
+
+ private lateinit var underTest: KeyguardSurfaceBehindInteractor
+ private lateinit var repository: FakeKeyguardSurfaceBehindRepository
+
+ @Mock
+ private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
+ @Mock
+ private lateinit var fromPrimaryBouncerTransitionInteractor:
+ FromPrimaryBouncerTransitionInteractor
+
+ private val lockscreenSurfaceBehindModel = KeyguardSurfaceBehindModel(alpha = 0.33f)
+ private val primaryBouncerSurfaceBehindModel = KeyguardSurfaceBehindModel(alpha = 0.66f)
+
+ private val testScope = TestScope()
+
+ private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+ private lateinit var transitionInteractor: KeyguardTransitionInteractor
+
+ @Before
+ fun setUp() {
+ initMocks(this)
+
+ whenever(fromLockscreenTransitionInteractor.surfaceBehindModel)
+ .thenReturn(flowOf(lockscreenSurfaceBehindModel))
+ whenever(fromPrimaryBouncerTransitionInteractor.surfaceBehindModel)
+ .thenReturn(flowOf(primaryBouncerSurfaceBehindModel))
+
+ transitionRepository = FakeKeyguardTransitionRepository()
+
+ transitionInteractor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = testScope.backgroundScope,
+ repository = transitionRepository,
+ )
+ .keyguardTransitionInteractor
+
+ repository = FakeKeyguardSurfaceBehindRepository()
+ underTest =
+ KeyguardSurfaceBehindInteractor(
+ repository = repository,
+ fromLockscreenInteractor = fromLockscreenTransitionInteractor,
+ fromPrimaryBouncerInteractor = fromPrimaryBouncerTransitionInteractor,
+ transitionInteractor = transitionInteractor,
+ )
+ }
+
+ @Test
+ fun viewParamsSwitchToCorrectFlow() =
+ testScope.runTest {
+ val values by collectValues(underTest.viewParams)
+
+ // Start on the LOCKSCREEN.
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+
+ runCurrent()
+
+ // We're on LOCKSCREEN; we should be using the default params.
+ assertEquals(1, values.size)
+ assertTrue(values[0].alpha == 0f)
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+
+ runCurrent()
+
+ // We're going from LOCKSCREEN -> GONE, we should be using the lockscreen interactor's
+ // surface behind model.
+ assertEquals(2, values.size)
+ assertEquals(values[1], lockscreenSurfaceBehindModel)
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ )
+ )
+
+ runCurrent()
+
+ // We're going from PRIMARY_BOUNCER -> GONE, we should be using the bouncer interactor's
+ // surface behind model.
+ assertEquals(3, values.size)
+ assertEquals(values[2], primaryBouncerSurfaceBehindModel)
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ )
+ )
+
+ runCurrent()
+
+ // Once PRIMARY_BOUNCER -> GONE finishes, we should be using default params, which is
+ // alpha=1f when we're GONE.
+ assertEquals(4, values.size)
+ assertEquals(1f, values[3].alpha)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt
new file mode 100644
index 0000000..8db19ae
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt
@@ -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 com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.util.mockito.mock
+import dagger.Lazy
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+
+open class KeyguardTransitionInteractorTestCase : SysuiTestCase() {
+ val testDispatcher = StandardTestDispatcher()
+ val testScope = TestScope(testDispatcher)
+
+ lateinit var keyguardRepository: FakeKeyguardRepository
+ lateinit var transitionRepository: FakeKeyguardTransitionRepository
+
+ lateinit var keyguardInteractor: KeyguardInteractor
+ lateinit var transitionInteractor: KeyguardTransitionInteractor
+
+ /**
+ * Replace these lazy providers with non-null ones if you want test dependencies to use a real
+ * instance of the interactor for the test.
+ */
+ open var fromLockscreenTransitionInteractorLazy: Lazy<FromLockscreenTransitionInteractor>? =
+ null
+ open var fromPrimaryBouncerTransitionInteractorLazy:
+ Lazy<FromPrimaryBouncerTransitionInteractor>? =
+ null
+
+ open fun setUp() {
+ keyguardRepository = FakeKeyguardRepository()
+ transitionRepository = FakeKeyguardTransitionRepository()
+
+ keyguardInteractor =
+ KeyguardInteractorFactory.create(repository = keyguardRepository).keyguardInteractor
+
+ transitionInteractor =
+ KeyguardTransitionInteractorFactory.create(
+ repository = transitionRepository,
+ keyguardInteractor = keyguardInteractor,
+ scope = testScope.backgroundScope,
+ fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractorLazy
+ ?: Lazy { mock() },
+ fromPrimaryBouncerTransitionInteractor =
+ fromPrimaryBouncerTransitionInteractorLazy ?: Lazy { mock() },
+ )
+ .also {
+ fromLockscreenTransitionInteractorLazy = it.fromLockscreenTransitionInteractor
+ fromPrimaryBouncerTransitionInteractorLazy =
+ it.fromPrimaryBouncerTransitionInteractor
+ }
+ .keyguardTransitionInteractor
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index aa6bd4e..4b221a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -104,12 +104,21 @@
whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
- featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, true) }
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.FACE_AUTH_REFACTOR, true)
+ set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
+ }
transitionInteractor =
KeyguardTransitionInteractorFactory.create(
scope = testScope,
repository = transitionRepository,
+ keyguardInteractor = createKeyguardInteractor(),
+ fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
+ fromPrimaryBouncerTransitionInteractor = {
+ fromPrimaryBouncerTransitionInteractor
+ },
)
.keyguardTransitionInteractor
@@ -119,6 +128,7 @@
keyguardInteractor = createKeyguardInteractor(),
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
+ flags = featureFlags,
shadeRepository = shadeRepository,
)
.apply { start() }
@@ -129,6 +139,7 @@
keyguardInteractor = createKeyguardInteractor(),
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
+ flags = featureFlags,
keyguardSecurityModel = keyguardSecurityModel,
)
.apply { start() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
new file mode 100644
index 0000000..73ecae5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
@@ -0,0 +1,412 @@
+/*
+ * 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.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.mockito.whenever
+import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+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.mockito.Mock
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
+
+ private lateinit var underTest: WindowManagerLockscreenVisibilityInteractor
+
+ @Mock private lateinit var surfaceBehindInteractor: KeyguardSurfaceBehindInteractor
+ @Mock
+ private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
+ @Mock
+ private lateinit var fromPrimaryBouncerTransitionInteractor:
+ FromPrimaryBouncerTransitionInteractor
+
+ private val lockscreenSurfaceVisibilityFlow = MutableStateFlow<Boolean?>(false)
+ private val primaryBouncerSurfaceVisibilityFlow = MutableStateFlow<Boolean?>(false)
+ private val surfaceBehindIsAnimatingFlow = MutableStateFlow(false)
+
+ private val testScope = TestScope()
+
+ private lateinit var keyguardInteractor: KeyguardInteractor
+ private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+ private lateinit var transitionInteractor: KeyguardTransitionInteractor
+
+ @Before
+ fun setUp() {
+ initMocks(this)
+
+ whenever(fromLockscreenTransitionInteractor.surfaceBehindVisibility)
+ .thenReturn(lockscreenSurfaceVisibilityFlow)
+ whenever(fromPrimaryBouncerTransitionInteractor.surfaceBehindVisibility)
+ .thenReturn(primaryBouncerSurfaceVisibilityFlow)
+ whenever(surfaceBehindInteractor.isAnimatingSurface)
+ .thenReturn(surfaceBehindIsAnimatingFlow)
+
+ transitionRepository = FakeKeyguardTransitionRepository()
+
+ transitionInteractor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = testScope.backgroundScope,
+ repository = transitionRepository,
+ )
+ .also { keyguardInteractor = it.keyguardInteractor }
+ .keyguardTransitionInteractor
+
+ underTest =
+ WindowManagerLockscreenVisibilityInteractor(
+ keyguardInteractor = keyguardInteractor,
+ transitionInteractor = transitionInteractor,
+ surfaceBehindInteractor = surfaceBehindInteractor,
+ fromLockscreenTransitionInteractor,
+ fromPrimaryBouncerTransitionInteractor,
+ )
+ }
+
+ @Test
+ fun surfaceBehindVisibility_switchesToCorrectFlow() =
+ testScope.runTest {
+ val values by collectValues(underTest.surfaceBehindVisibility)
+
+ // Start on LOCKSCREEN.
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false, // We should start with the surface invisible on LOCKSCREEN.
+ ),
+ values
+ )
+
+ val lockscreenSpecificSurfaceVisibility = true
+ lockscreenSurfaceVisibilityFlow.emit(lockscreenSpecificSurfaceVisibility)
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+
+ runCurrent()
+
+ // We started a transition from LOCKSCREEN, we should be using the value emitted by the
+ // lockscreenSurfaceVisibilityFlow.
+ assertEquals(
+ listOf(
+ false,
+ lockscreenSpecificSurfaceVisibility,
+ ),
+ values
+ )
+
+ // Go back to LOCKSCREEN, since we won't emit 'true' twice in a row.
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ lockscreenSpecificSurfaceVisibility,
+ false, // FINISHED (LOCKSCREEN)
+ ),
+ values
+ )
+
+ val bouncerSpecificVisibility = true
+ primaryBouncerSurfaceVisibilityFlow.emit(bouncerSpecificVisibility)
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ )
+ )
+
+ runCurrent()
+
+ // We started a transition from PRIMARY_BOUNCER, we should be using the value emitted by
+ // the
+ // primaryBouncerSurfaceVisibilityFlow.
+ assertEquals(
+ listOf(
+ false,
+ lockscreenSpecificSurfaceVisibility,
+ false,
+ bouncerSpecificVisibility,
+ ),
+ values
+ )
+ }
+
+ @Test
+ fun testUsingGoingAwayAnimation_duringTransitionToGone() =
+ testScope.runTest {
+ val values by collectValues(underTest.usingKeyguardGoingAwayAnimation)
+
+ // Start on LOCKSCREEN.
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false, // Not using the animation when we're just sitting on LOCKSCREEN.
+ ),
+ values
+ )
+
+ surfaceBehindIsAnimatingFlow.emit(true)
+ runCurrent()
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true, // Still true when we're FINISHED -> GONE, since we're still animating.
+ ),
+ values
+ )
+
+ surfaceBehindIsAnimatingFlow.emit(false)
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true,
+ false, // False once the animation ends.
+ ),
+ values
+ )
+ }
+
+ @Test
+ fun testNotUsingGoingAwayAnimation_evenWhenAnimating_ifStateIsNotGone() =
+ testScope.runTest {
+ val values by collectValues(underTest.usingKeyguardGoingAwayAnimation)
+
+ // Start on LOCKSCREEN.
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false, // Not using the animation when we're just sitting on LOCKSCREEN.
+ ),
+ values
+ )
+
+ surfaceBehindIsAnimatingFlow.emit(true)
+ runCurrent()
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true, // We're happily animating while transitioning to gone.
+ ),
+ values
+ )
+
+ // Oh no, we're still surfaceBehindAnimating=true, but no longer transitioning to GONE.
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true,
+ false, // Despite the animator still running, this should be false.
+ ),
+ values
+ )
+
+ surfaceBehindIsAnimatingFlow.emit(false)
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true,
+ false, // The animator ending should have no effect.
+ ),
+ values
+ )
+ }
+
+ @Test
+ fun lockscreenVisibility_visibleWhenGone() =
+ testScope.runTest {
+ val values by collectValues(underTest.lockscreenVisibility)
+
+ // Start on LOCKSCREEN.
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ true, // Unsurprisingly, we should start with the lockscreen visible on
+ // LOCKSCREEN.
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ true, // Lockscreen remains visible while we're transitioning to GONE.
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ true,
+ false, // Once we're fully GONE, the lockscreen should not be visible.
+ ),
+ values
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt
new file mode 100644
index 0000000..a22f603
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt
@@ -0,0 +1,160 @@
+/*
+ * 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.binder
+
+import android.testing.TestableLooper.RunWithLooper
+import android.view.RemoteAnimationTarget
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardViewController
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.AnimatorTestRule
+import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertNull
+import junit.framework.Assert.assertTrue
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.doAnswer
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWithLooper(setAsMainLooper = true)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class KeyguardSurfaceBehindParamsApplierTest : SysuiTestCase() {
+ @get:Rule val animatorTestRule = AnimatorTestRule()
+
+ private lateinit var underTest: KeyguardSurfaceBehindParamsApplier
+ private lateinit var executor: FakeExecutor
+
+ @Mock private lateinit var keyguardViewController: KeyguardViewController
+
+ @Mock private lateinit var interactor: KeyguardSurfaceBehindInteractor
+
+ @Mock private lateinit var remoteAnimationTarget: RemoteAnimationTarget
+
+ private var isAnimatingSurface: Boolean? = null
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ executor = FakeExecutor(FakeSystemClock())
+ underTest =
+ KeyguardSurfaceBehindParamsApplier(
+ executor = executor,
+ keyguardViewController = keyguardViewController,
+ interactor = interactor,
+ )
+
+ doAnswer {
+ (it.arguments[0] as Boolean).let { animating -> isAnimatingSurface = animating }
+ }
+ .whenever(interactor)
+ .setAnimatingSurface(anyBoolean())
+ }
+
+ @After
+ fun tearDown() {
+ animatorTestRule.advanceTimeBy(1000.toLong())
+ }
+
+ @Test
+ fun testNotAnimating_setParamsWithNoAnimation() {
+ underTest.viewParams =
+ KeyguardSurfaceBehindModel(
+ alpha = 0.3f,
+ translationY = 300f,
+ )
+
+ // A surface has not yet been provided, so we shouldn't have set animating to false OR true
+ // just yet.
+ assertNull(isAnimatingSurface)
+
+ underTest.applyParamsToSurface(remoteAnimationTarget)
+
+ // We should now explicitly not be animating the surface.
+ assertFalse(checkNotNull(isAnimatingSurface))
+ }
+
+ @Test
+ fun testAnimating_paramsThenSurfaceProvided() {
+ underTest.viewParams =
+ KeyguardSurfaceBehindModel(
+ animateFromAlpha = 0f,
+ alpha = 0.3f,
+ animateFromTranslationY = 0f,
+ translationY = 300f,
+ )
+
+ // A surface has not yet been provided, so we shouldn't have set animating to false OR true
+ // just yet.
+ assertNull(isAnimatingSurface)
+
+ underTest.applyParamsToSurface(remoteAnimationTarget)
+
+ // We should now be animating the surface.
+ assertTrue(checkNotNull(isAnimatingSurface))
+ }
+
+ @Test
+ fun testAnimating_surfaceThenParamsProvided() {
+ underTest.applyParamsToSurface(remoteAnimationTarget)
+
+ // The default params (which do not animate) should have been applied, so we're explicitly
+ // NOT animating yet.
+ assertFalse(checkNotNull(isAnimatingSurface))
+
+ underTest.viewParams =
+ KeyguardSurfaceBehindModel(
+ animateFromAlpha = 0f,
+ alpha = 0.3f,
+ animateFromTranslationY = 0f,
+ translationY = 300f,
+ )
+
+ // We should now be animating the surface.
+ assertTrue(checkNotNull(isAnimatingSurface))
+ }
+
+ @Test
+ fun testAnimating_thenReleased_animatingIsFalse() {
+ underTest.viewParams =
+ KeyguardSurfaceBehindModel(
+ animateFromAlpha = 0f,
+ alpha = 0.3f,
+ animateFromTranslationY = 0f,
+ translationY = 300f,
+ )
+ underTest.applyParamsToSurface(remoteAnimationTarget)
+
+ assertTrue(checkNotNull(isAnimatingSurface))
+
+ underTest.notifySurfaceReleased()
+
+ // Releasing the surface should immediately cancel animators.
+ assertFalse(checkNotNull(isAnimatingSurface))
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
new file mode 100644
index 0000000..623c877
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.binder
+
+import android.app.IActivityTaskManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() {
+ private lateinit var underTest: WindowManagerLockscreenVisibilityManager
+ private lateinit var executor: FakeExecutor
+
+ @Mock private lateinit var activityTaskManagerService: IActivityTaskManager
+
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+
+ @Mock private lateinit var keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ executor = FakeExecutor(FakeSystemClock())
+
+ underTest =
+ WindowManagerLockscreenVisibilityManager(
+ executor = executor,
+ activityTaskManagerService = activityTaskManagerService,
+ keyguardStateController = keyguardStateController,
+ keyguardSurfaceBehindAnimator = keyguardSurfaceBehindAnimator,
+ )
+ }
+
+ @Test
+ fun testLockscreenVisible_andAodVisible() {
+ underTest.setLockscreenShown(true)
+ underTest.setAodVisible(true)
+
+ verify(activityTaskManagerService).setLockScreenShown(true, true)
+ verifyNoMoreInteractions(activityTaskManagerService)
+ }
+
+ @Test
+ fun testGoingAway_whenLockscreenVisible_thenSurfaceMadeVisible() {
+ underTest.setLockscreenShown(true)
+ underTest.setAodVisible(true)
+
+ verify(activityTaskManagerService).setLockScreenShown(true, true)
+ verifyNoMoreInteractions(activityTaskManagerService)
+
+ underTest.setSurfaceBehindVisibility(true)
+
+ verify(activityTaskManagerService).keyguardGoingAway(anyInt())
+ verifyNoMoreInteractions(activityTaskManagerService)
+ }
+
+ @Test
+ fun testSurfaceVisible_whenLockscreenNotShowing_doesNotTriggerGoingAway() {
+ underTest.setLockscreenShown(false)
+ underTest.setAodVisible(false)
+
+ verify(activityTaskManagerService).setLockScreenShown(false, false)
+ verifyNoMoreInteractions(activityTaskManagerService)
+
+ underTest.setSurfaceBehindVisibility(true)
+
+ verifyNoMoreInteractions(activityTaskManagerService)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index c67f535..bfc6f31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -21,9 +21,7 @@
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
-import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
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 45d7a5e..23f243c 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
@@ -84,22 +84,24 @@
}
@Test
- fun upTransitionSceneKey_swipeToUnlockEnabled_gone() =
+ fun upTransitionSceneKey_canSwipeToUnlock_gone() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
utils.authenticationRepository.setLockscreenEnabled(true)
- utils.authenticationRepository.setUnlocked(false)
+ utils.authenticationRepository.setUnlocked(true)
+ sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
}
@Test
- fun upTransitionSceneKey_swipeToUnlockNotEnabled_bouncer() =
+ fun upTransitionSceneKey_cannotSwipeToUnlock_bouncer() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
+ sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
index 80ab418..0ad14d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
@@ -31,7 +31,7 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.statusbar.phone.SystemUIDialogManager
@@ -83,16 +83,21 @@
bouncerRepository = FakeKeyguardBouncerRepository()
transitionRepository = FakeKeyguardTransitionRepository()
shadeRepository = FakeShadeRepository()
- val transitionInteractor =
- KeyguardTransitionInteractor(
- transitionRepository,
- testScope.backgroundScope,
- )
val keyguardInteractor =
KeyguardInteractorFactory.create(
+ repository = keyguardRepository,
featureFlags = featureFlags,
)
.keyguardInteractor
+
+ val transitionInteractor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = testScope.backgroundScope,
+ repository = transitionRepository,
+ keyguardInteractor = keyguardInteractor,
+ )
+ .keyguardTransitionInteractor
+
underTest =
FingerprintViewModel(
context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
index 0456824..edcaa1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
@@ -30,7 +30,7 @@
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -98,15 +98,20 @@
bouncerRepository = it.bouncerRepository
}
+ val transitionInteractor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = testScope.backgroundScope,
+ repository = transitionRepository,
+ keyguardInteractor = keyguardInteractor,
+ )
+ .keyguardTransitionInteractor
+
underTest =
UdfpsLockscreenViewModel(
context,
lockscreenColorResId,
alternateBouncerResId,
- KeyguardTransitionInteractor(
- transitionRepository,
- testScope.backgroundScope,
- ),
+ transitionInteractor,
UdfpsKeyguardInteractor(
configRepository,
BurnInInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index 5b8272b0..ae0a334 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -30,6 +30,7 @@
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.keyguard.TestScopeProvider
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
@@ -37,6 +38,7 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -66,7 +68,6 @@
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -152,7 +153,11 @@
debugLogger,
mediaFlags,
keyguardUpdateMonitor,
- KeyguardTransitionInteractor(transitionRepository, TestScope().backgroundScope),
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScopeProvider.getTestScope().backgroundScope,
+ repository = transitionRepository,
+ )
+ .keyguardTransitionInteractor,
globalSettings
)
verify(configurationController).addCallback(capture(configListener))
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 0a93a7c..16cc924 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
@@ -28,9 +28,6 @@
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
@@ -105,6 +102,40 @@
}
@Test
+ fun transitioningTo() =
+ testScope.runTest {
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(underTest.desiredScene.value.key)
+ )
+ underTest.setTransitionState(transitionState)
+
+ val transitionTo by collectLastValue(underTest.transitioningTo)
+ assertThat(transitionTo).isNull()
+
+ underTest.changeScene(SceneModel(SceneKey.Shade), "reason")
+ assertThat(transitionTo).isNull()
+
+ val progress = MutableStateFlow(0f)
+ transitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = underTest.desiredScene.value.key,
+ toScene = SceneKey.Shade,
+ progress = progress,
+ )
+ assertThat(transitionTo).isEqualTo(SceneKey.Shade)
+
+ progress.value = 0.5f
+ assertThat(transitionTo).isEqualTo(SceneKey.Shade)
+
+ progress.value = 1f
+ assertThat(transitionTo).isEqualTo(SceneKey.Shade)
+
+ transitionState.value = ObservableTransitionState.Idle(SceneKey.Shade)
+ assertThat(transitionTo).isNull()
+ }
+
+ @Test
fun isVisible() =
testScope.runTest {
val isVisible by collectLastValue(underTest.isVisible)
@@ -118,81 +149,6 @@
}
@Test
- fun finishedSceneTransitions() =
- testScope.runTest {
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(SceneKey.Lockscreen)
- )
- 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)
-
- job.cancel()
- }
-
- @Test
fun remoteUserInput() =
testScope.runTest {
val remoteUserInput by collectLastValue(underTest.remoteUserInput)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
index b6da20f..280897d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
@@ -17,21 +17,19 @@
package com.android.systemui.statusbar;
-import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
-
import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import android.app.ActivityManager;
import android.app.Notification;
-import android.app.PendingIntent;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
@@ -67,17 +65,15 @@
private static final String TEST_PACKAGE_NAME = "test";
private static final int TEST_UID = 0;
- protected static final int TEST_MINIMUM_DISPLAY_TIME = 200;
- protected static final int TEST_STICKY_DISPLAY_TIME = 1000;
- protected static final int TEST_AUTO_DISMISS_TIME = 500;
+ protected static final int TEST_MINIMUM_DISPLAY_TIME = 400;
+ protected static final int TEST_AUTO_DISMISS_TIME = 600;
+ protected static final int TEST_STICKY_AUTO_DISMISS_TIME = 800;
// Number of notifications to use in tests requiring multiple notifications
private static final int TEST_NUM_NOTIFICATIONS = 4;
- protected static final int TEST_TIMEOUT_TIME = 15000;
+ protected static final int TEST_TIMEOUT_TIME = 2_000;
protected final Runnable mTestTimeoutRunnable = () -> mTimedOut = true;
- protected NotificationEntry mEntry;
protected Handler mTestHandler;
- private StatusBarNotification mSbn;
protected boolean mTimedOut = false;
@Mock protected ExpandableNotificationRow mRow;
@@ -88,8 +84,8 @@
private TestableAlertingNotificationManager(Handler handler) {
super(new HeadsUpManagerLogger(logcatLogBuffer()), handler);
mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
- mStickyDisplayTime = TEST_STICKY_DISPLAY_TIME;
mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
+ mStickyDisplayTime = TEST_STICKY_AUTO_DISMISS_TIME;
}
@Override
@@ -114,7 +110,7 @@
return new TestableAlertingNotificationManager(mTestHandler);
}
- protected StatusBarNotification createNewSbn(int id, Notification n) {
+ protected StatusBarNotification createSbn(int id, Notification n) {
return new StatusBarNotification(
TEST_PACKAGE_NAME /* pkg */,
TEST_PACKAGE_NAME,
@@ -128,45 +124,53 @@
0 /* postTime */);
}
- protected StatusBarNotification createNewSbn(int id, Notification.Builder n) {
- return new StatusBarNotification(
- TEST_PACKAGE_NAME /* pkg */,
- TEST_PACKAGE_NAME,
- id,
- null /* tag */,
- TEST_UID,
- 0 /* initialPid */,
- n.build(),
- new UserHandle(ActivityManager.getCurrentUser()),
- null /* overrideGroupKey */,
- 0 /* postTime */);
+ protected StatusBarNotification createSbn(int id, Notification.Builder n) {
+ return createSbn(id, n.build());
}
- protected StatusBarNotification createNewNotification(int id) {
- Notification.Builder n = new Notification.Builder(mContext, "")
+ protected StatusBarNotification createSbn(int id) {
+ final Notification.Builder b = new Notification.Builder(mContext, "")
.setSmallIcon(R.drawable.ic_person)
.setContentTitle("Title")
.setContentText("Text");
- return createNewSbn(id, n);
+ return createSbn(id, b);
}
- protected StatusBarNotification createStickySbn(int id) {
- Notification stickyHun = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .setFullScreenIntent(mock(PendingIntent.class), /* highPriority */ true)
- .build();
- stickyHun.flags |= FLAG_FSI_REQUESTED_BUT_DENIED;
- return createNewSbn(id, stickyHun);
+ protected NotificationEntry createEntry(int id, Notification n) {
+ return new NotificationEntryBuilder().setSbn(createSbn(id, n)).build();
+ }
+
+ protected NotificationEntry createEntry(int id) {
+ return new NotificationEntryBuilder().setSbn(createSbn(id)).build();
+ }
+
+ protected void verifyAlertingAtTime(AlertingNotificationManager anm, NotificationEntry entry,
+ boolean shouldBeAlerting, int whenToCheckAlertingMillis, String whenCondition) {
+ final Boolean[] wasAlerting = {null};
+ final Runnable checkAlerting =
+ () -> wasAlerting[0] = anm.isAlerting(entry.getKey());
+
+ mTestHandler.postDelayed(checkAlerting, whenToCheckAlertingMillis);
+ mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_TIMEOUT_TIME);
+ TestableLooper.get(this).processMessages(2);
+
+ assertFalse("Test timed out", mTimedOut);
+ if (shouldBeAlerting) {
+ assertTrue("Should still be alerting after " + whenCondition, wasAlerting[0]);
+ } else {
+ assertFalse("Should not still be alerting after " + whenCondition, wasAlerting[0]);
+ }
+ assertFalse("Should not still be alerting after processing",
+ anm.isAlerting(entry.getKey()));
}
@Before
public void setUp() {
mTestHandler = Handler.createAsync(Looper.myLooper());
- mSbn = createNewNotification(0 /* id */);
- mEntry = new NotificationEntryBuilder()
- .setSbn(mSbn)
- .build();
- mEntry.setRow(mRow);
+
+ assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
+ assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
+ assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_TIMEOUT_TIME);
}
@After
@@ -176,59 +180,64 @@
@Test
public void testShowNotification_addsEntry() {
- AlertingNotificationManager alm = createAlertingNotificationManager();
+ final AlertingNotificationManager alm = createAlertingNotificationManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
- alm.showNotification(mEntry);
+ alm.showNotification(entry);
- assertTrue(alm.isAlerting(mEntry.getKey()));
+ assertTrue(alm.isAlerting(entry.getKey()));
assertTrue(alm.hasNotifications());
- assertEquals(mEntry, alm.getEntry(mEntry.getKey()));
+ assertEquals(entry, alm.getEntry(entry.getKey()));
}
@Test
public void testShowNotification_autoDismisses() {
- AlertingNotificationManager alm = createAlertingNotificationManager();
+ final AlertingNotificationManager alm = createAlertingNotificationManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
- alm.showNotification(mEntry);
- mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_TIMEOUT_TIME);
+ alm.showNotification(entry);
- // Wait for remove runnable and then process it immediately
- TestableLooper.get(this).processMessages(1);
+ verifyAlertingAtTime(alm, entry, false, TEST_AUTO_DISMISS_TIME * 3 / 2,
+ "auto dismiss time");
- assertFalse("Test timed out", mTimedOut);
- assertFalse(alm.isAlerting(mEntry.getKey()));
+ assertFalse(alm.isAlerting(entry.getKey()));
}
@Test
public void testRemoveNotification_removeDeferred() {
- AlertingNotificationManager alm = createAlertingNotificationManager();
- alm.showNotification(mEntry);
+ final AlertingNotificationManager alm = createAlertingNotificationManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ alm.showNotification(entry);
// Try to remove but defer, since the notification has not been shown long enough.
- alm.removeNotification(mEntry.getKey(), false /* releaseImmediately */);
+ final boolean removedImmediately = alm.removeNotification(entry.getKey(),
+ false /* releaseImmediately */);
- assertTrue(alm.isAlerting(mEntry.getKey()));
+ assertFalse(removedImmediately);
+ assertTrue(alm.isAlerting(entry.getKey()));
}
@Test
public void testRemoveNotification_forceRemove() {
- AlertingNotificationManager alm = createAlertingNotificationManager();
- alm.showNotification(mEntry);
+ final AlertingNotificationManager alm = createAlertingNotificationManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ alm.showNotification(entry);
// Remove forcibly with releaseImmediately = true.
- alm.removeNotification(mEntry.getKey(), true /* releaseImmediately */);
+ final boolean removedImmediately = alm.removeNotification(entry.getKey(),
+ true /* releaseImmediately */);
- assertFalse(alm.isAlerting(mEntry.getKey()));
+ assertTrue(removedImmediately);
+ assertFalse(alm.isAlerting(entry.getKey()));
}
@Test
public void testReleaseAllImmediately() {
- AlertingNotificationManager alm = createAlertingNotificationManager();
+ final AlertingNotificationManager alm = createAlertingNotificationManager();
for (int i = 0; i < TEST_NUM_NOTIFICATIONS; i++) {
- StatusBarNotification sbn = createNewNotification(i);
- NotificationEntry entry = new NotificationEntryBuilder()
- .setSbn(sbn)
- .build();
+ final NotificationEntry entry = createEntry(i);
entry.setRow(mRow);
alm.showNotification(entry);
}
@@ -240,10 +249,12 @@
@Test
public void testCanRemoveImmediately_notShownLongEnough() {
- AlertingNotificationManager alm = createAlertingNotificationManager();
- alm.showNotification(mEntry);
+ final AlertingNotificationManager alm = createAlertingNotificationManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ alm.showNotification(entry);
// The entry has just been added so we should not remove immediately.
- assertFalse(alm.canRemoveImmediately(mEntry.getKey()));
+ assertFalse(alm.canRemoveImmediately(entry.getKey()));
}
}
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 414256f..6be2fa5 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
@@ -400,15 +400,16 @@
// remove persistent dot
systemStatusAnimationScheduler.removePersistentDot()
- testScheduler.runCurrent()
+
+ // verify that the onHidePersistentDot callback is invoked
+ verify(listener, times(1)).onHidePersistentDot()
// skip disappear animation
animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
testScheduler.runCurrent()
- // verify that animationState changes to IDLE and onHidePersistentDot callback is invoked
+ // verify that animationState changes to IDLE
assertEquals(IDLE, systemStatusAnimationScheduler.getAnimationState())
- verify(listener, times(1)).onHidePersistentDot()
}
@Test
@@ -473,7 +474,6 @@
// request removal of persistent dot
systemStatusAnimationScheduler.removePersistentDot()
- testScheduler.runCurrent()
// schedule another high priority event while the event is animating out
createAndScheduleFakePrivacyEvent()
@@ -489,6 +489,42 @@
verify(listener, times(1)).onHidePersistentDot()
}
+ @Test
+ fun testDotIsRemoved_evenIfAnimatorCallbackIsDelayed() = runTest {
+ // Instantiate class under test with TestScope from runTest
+ initializeSystemStatusAnimationScheduler(testScope = this)
+
+ // create and schedule high priority event
+ createAndScheduleFakePrivacyEvent()
+
+ // skip chip animation lifecycle and fast forward to ANIMATING_OUT state
+ fastForwardAnimationToState(ANIMATING_OUT)
+ assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+ verify(listener, times(1)).onSystemStatusAnimationTransitionToPersistentDot(any())
+
+ // request removal of persistent dot
+ systemStatusAnimationScheduler.removePersistentDot()
+
+ // verify that the state is still ANIMATING_OUT
+ assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+
+ // skip disappear animation duration
+ testScheduler.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION + 1)
+ // In an old implementation this would trigger a coroutine timeout causing the
+ // onHidePersistentDot callback to be missed.
+ testScheduler.runCurrent()
+
+ // advance animator time to invoke onAnimationEnd callback
+ animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
+ testScheduler.runCurrent()
+
+ // verify that onHidePersistentDot is invoked despite the animator callback being delayed
+ // (it's invoked more than DISAPPEAR_ANIMATION_DURATION after the dot removal was requested)
+ verify(listener, times(1)).onHidePersistentDot()
+ // verify that animationState is IDLE
+ assertEquals(IDLE, systemStatusAnimationScheduler.getAnimationState())
+ }
+
private fun TestScope.fastForwardAnimationToState(@SystemAnimationState animationState: Int) {
// this function should only be called directly after posting a status event
assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
index 6155e3c..03d3854 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
@@ -274,6 +274,23 @@
}
@Test
+ fun orientationUpdated_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.orientation = Configuration.ORIENTATION_LANDSCAPE
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the orientation is updated
+ config.orientation = Configuration.ORIENTATION_PORTRAIT
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.orientationChanged).isTrue()
+ }
+
+
+ @Test
fun multipleUpdates_listenerNotifiedOfAll() {
val config = mContext.resources.configuration
config.densityDpi = 14
@@ -325,6 +342,7 @@
var themeChanged = false
var localeListChanged = false
var layoutDirectionChanged = false
+ var orientationChanged = false
override fun onConfigChanged(newConfig: Configuration?) {
changedConfig = newConfig
@@ -350,6 +368,9 @@
override fun onLayoutDirectionChanged(isLayoutRtl: Boolean) {
layoutDirectionChanged = true
}
+ override fun onOrientationChanged(orientation: Int) {
+ orientationChanged = true
+ }
fun assertNoMethodsCalled() {
assertThat(densityOrFontScaleChanged).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index 2f1e372..ec6286b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -75,6 +75,7 @@
private HeadsUpManagerPhone mHeadsUpManager;
private View mOperatorNameView;
private StatusBarStateController mStatusbarStateController;
+ private PhoneStatusBarTransitions mPhoneStatusBarTransitions;
private KeyguardBypassController mBypassController;
private NotificationWakeUpCoordinator mWakeUpCoordinator;
private KeyguardStateController mKeyguardStateController;
@@ -95,6 +96,7 @@
mHeadsUpManager = mock(HeadsUpManagerPhone.class);
mOperatorNameView = new View(mContext);
mStatusbarStateController = mock(StatusBarStateController.class);
+ mPhoneStatusBarTransitions = mock(PhoneStatusBarTransitions.class);
mBypassController = mock(KeyguardBypassController.class);
mWakeUpCoordinator = mock(NotificationWakeUpCoordinator.class);
mKeyguardStateController = mock(KeyguardStateController.class);
@@ -105,6 +107,7 @@
mock(NotificationIconAreaController.class),
mHeadsUpManager,
mStatusbarStateController,
+ mPhoneStatusBarTransitions,
mBypassController,
mWakeUpCoordinator,
mDarkIconDispatcher,
@@ -188,6 +191,7 @@
mock(NotificationIconAreaController.class),
mHeadsUpManager,
mStatusbarStateController,
+ mPhoneStatusBarTransitions,
mBypassController,
mWakeUpCoordinator,
mDarkIconDispatcher,
@@ -283,4 +287,18 @@
/* delta = */ 0.001
);
}
+
+ @Test
+ public void onHeadsUpStateChanged_true_transitionsNotified() {
+ mHeadsUpAppearanceController.onHeadsUpStateChanged(mEntry, true);
+
+ verify(mPhoneStatusBarTransitions).onHeadsUpStateChanged(true);
+ }
+
+ @Test
+ public void onHeadsUpStateChanged_false_transitionsNotified() {
+ mHeadsUpAppearanceController.onHeadsUpStateChanged(mEntry, false);
+
+ verify(mPhoneStatusBarTransitions).onHeadsUpStateChanged(false);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index 72522ca..bb20d18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -39,7 +39,6 @@
import com.android.systemui.statusbar.AlertingNotificationManagerTest;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
@@ -62,8 +61,6 @@
public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest {
@Rule public MockitoRule rule = MockitoJUnit.rule();
- private HeadsUpManagerPhone mHeadsUpManager;
-
private final HeadsUpManagerLogger mHeadsUpManagerLogger = new HeadsUpManagerLogger(
logcatLogBuffer());
@Mock private GroupMembershipManager mGroupManager;
@@ -108,25 +105,8 @@
}
}
- @Override
- protected AlertingNotificationManager createAlertingNotificationManager() {
- return mHeadsUpManager;
- }
-
- @Before
- @Override
- public void setUp() {
- AccessibilityManagerWrapper accessibilityMgr =
- mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
- when(accessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt()))
- .thenReturn(TEST_AUTO_DISMISS_TIME);
- when(mVSProvider.isReorderingAllowed()).thenReturn(true);
- mDependency.injectMockDependency(NotificationShadeWindowController.class);
- mContext.getOrCreateTestableResources().addOverride(
- R.integer.ambient_notification_extension_time, 500);
-
- super.setUp();
- mHeadsUpManager = new TestableHeadsUpManagerPhone(
+ private HeadsUpManagerPhone createHeadsUpManagerPhone() {
+ return new TestableHeadsUpManagerPhone(
mContext,
mHeadsUpManagerLogger,
mGroupManager,
@@ -141,6 +121,26 @@
);
}
+ @Override
+ protected AlertingNotificationManager createAlertingNotificationManager() {
+ return createHeadsUpManagerPhone();
+ }
+
+ @Before
+ @Override
+ public void setUp() {
+ final AccessibilityManagerWrapper accessibilityMgr =
+ mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
+ when(accessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt()))
+ .thenReturn(TEST_AUTO_DISMISS_TIME);
+ when(mVSProvider.isReorderingAllowed()).thenReturn(true);
+ mDependency.injectMockDependency(NotificationShadeWindowController.class);
+ mContext.getOrCreateTestableResources().addOverride(
+ R.integer.ambient_notification_extension_time, 500);
+
+ super.setUp();
+ }
+
@After
@Override
public void tearDown() {
@@ -149,63 +149,67 @@
@Test
public void testSnooze() {
- mHeadsUpManager.showNotification(mEntry);
+ final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
- mHeadsUpManager.snooze();
+ hmp.showNotification(entry);
+ hmp.snooze();
- assertTrue(mHeadsUpManager.isSnoozed(mEntry.getSbn().getPackageName()));
+ assertTrue(hmp.isSnoozed(entry.getSbn().getPackageName()));
}
@Test
public void testSwipedOutNotification() {
- mHeadsUpManager.showNotification(mEntry);
- mHeadsUpManager.addSwipedOutNotification(mEntry.getKey());
+ final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ hmp.showNotification(entry);
+ hmp.addSwipedOutNotification(entry.getKey());
// Remove should succeed because the notification is swiped out
- mHeadsUpManager.removeNotification(mEntry.getKey(), false /* releaseImmediately */);
+ final boolean removedImmediately = hmp.removeNotification(entry.getKey(),
+ /* releaseImmediately = */ false);
- assertFalse(mHeadsUpManager.isAlerting(mEntry.getKey()));
+ assertTrue(removedImmediately);
+ assertFalse(hmp.isAlerting(entry.getKey()));
}
@Test
public void testCanRemoveImmediately_swipedOut() {
- mHeadsUpManager.showNotification(mEntry);
- mHeadsUpManager.addSwipedOutNotification(mEntry.getKey());
+ final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ hmp.showNotification(entry);
+ hmp.addSwipedOutNotification(entry.getKey());
// Notification is swiped so it can be immediately removed.
- assertTrue(mHeadsUpManager.canRemoveImmediately(mEntry.getKey()));
+ assertTrue(hmp.canRemoveImmediately(entry.getKey()));
}
@Ignore("b/141538055")
@Test
public void testCanRemoveImmediately_notTopEntry() {
- NotificationEntry laterEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(1))
- .build();
+ final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+ final NotificationEntry earlierEntry = createEntry(/* id = */ 0);
+ final NotificationEntry laterEntry = createEntry(/* id = */ 1);
laterEntry.setRow(mRow);
- mHeadsUpManager.showNotification(mEntry);
- mHeadsUpManager.showNotification(laterEntry);
+
+ hmp.showNotification(earlierEntry);
+ hmp.showNotification(laterEntry);
// Notification is "behind" a higher priority notification so we can remove it immediately.
- assertTrue(mHeadsUpManager.canRemoveImmediately(mEntry.getKey()));
+ assertTrue(hmp.canRemoveImmediately(earlierEntry.getKey()));
}
@Test
public void testExtendHeadsUp() {
- mHeadsUpManager.showNotification(mEntry);
- Runnable pastNormalTimeRunnable =
- () -> mLivesPastNormalTime = mHeadsUpManager.isAlerting(mEntry.getKey());
- mTestHandler.postDelayed(pastNormalTimeRunnable,
- TEST_AUTO_DISMISS_TIME + mHeadsUpManager.mExtensionTime / 2);
- mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_TIMEOUT_TIME);
+ final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
- mHeadsUpManager.extendHeadsUp();
+ hmp.showNotification(entry);
+ hmp.extendHeadsUp();
- // Wait for normal time runnable and extended remove runnable and process them on arrival.
- TestableLooper.get(this).processMessages(2);
-
- assertFalse("Test timed out", mTimedOut);
- assertTrue("Pulse was not extended", mLivesPastNormalTime);
- assertFalse(mHeadsUpManager.isAlerting(mEntry.getKey()));
+ final int pastNormalTimeMillis = TEST_AUTO_DISMISS_TIME + hmp.mExtensionTime / 2;
+ verifyAlertingAtTime(hmp, entry, true, pastNormalTimeMillis, "normal time");
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt
new file mode 100644
index 0000000..4af1b24
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt
@@ -0,0 +1,237 @@
+/*
+ * 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.phone
+
+import android.testing.TestableLooper
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class PhoneStatusBarTransitionsTest : SysuiTestCase() {
+
+ // PhoneStatusBarView does a lot of non-standard things when inflating, so just use mocks.
+ private val batteryView = mock<View>()
+ private val statusIcons = mock<View>()
+ private val startIcons = mock<View>()
+ private val statusBarView =
+ mock<PhoneStatusBarView>().apply {
+ whenever(this.context).thenReturn(mContext)
+ whenever(this.findViewById<View>(R.id.battery)).thenReturn(batteryView)
+ whenever(this.findViewById<View>(R.id.statusIcons)).thenReturn(statusIcons)
+ whenever(this.findViewById<View>(R.id.status_bar_start_side_except_heads_up))
+ .thenReturn(startIcons)
+ }
+ private val backgroundView = mock<View>().apply { whenever(this.context).thenReturn(mContext) }
+
+ private val underTest: PhoneStatusBarTransitions by lazy {
+ PhoneStatusBarTransitions(statusBarView, backgroundView).also {
+ // The views' alphas will be set when PhoneStatusBarTransitions is created and we want
+ // to ignore those in the tests, so clear those verifications here.
+ reset(batteryView)
+ reset(statusIcons)
+ reset(startIcons)
+ }
+ }
+
+ @Before
+ fun setUp() {
+ context.orCreateTestableResources.addOverride(
+ R.dimen.status_bar_icon_drawing_alpha,
+ RESOURCE_ALPHA,
+ )
+ }
+
+ @Test
+ fun transitionTo_lightsOutMode_batteryTranslucent() {
+ underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+
+ val alpha = batteryView.capturedAlpha()
+ assertThat(alpha).isGreaterThan(0)
+ assertThat(alpha).isLessThan(1)
+ }
+
+ @Test
+ fun transitionTo_lightsOutMode_statusIconsHidden() {
+ underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+
+ assertThat(statusIcons.capturedAlpha()).isEqualTo(0)
+ }
+
+ @Test
+ fun transitionTo_lightsOutMode_startIconsHidden() {
+ underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(0)
+ }
+
+ @Test
+ fun transitionTo_lightsOutTransparentMode_batteryTranslucent() {
+ underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT_TRANSPARENT, /* animate= */ false)
+
+ val alpha = batteryView.capturedAlpha()
+ assertThat(alpha).isGreaterThan(0)
+ assertThat(alpha).isLessThan(1)
+ }
+
+ @Test
+ fun transitionTo_lightsOutTransparentMode_statusIconsHidden() {
+ underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT_TRANSPARENT, /* animate= */ false)
+
+ assertThat(statusIcons.capturedAlpha()).isEqualTo(0)
+ }
+
+ @Test
+ fun transitionTo_lightsOutTransparentMode_startIconsHidden() {
+ underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT_TRANSPARENT, /* animate= */ false)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(0)
+ }
+
+ @Test
+ fun transitionTo_translucentMode_batteryIconShown() {
+ underTest.transitionTo(/* mode= */ MODE_TRANSLUCENT, /* animate= */ false)
+
+ assertThat(batteryView.capturedAlpha()).isEqualTo(1)
+ }
+
+ @Test
+ fun transitionTo_semiTransparentMode_statusIconsShown() {
+ underTest.transitionTo(/* mode= */ MODE_SEMI_TRANSPARENT, /* animate= */ false)
+
+ assertThat(statusIcons.capturedAlpha()).isEqualTo(1)
+ }
+
+ @Test
+ fun transitionTo_transparentMode_startIconsShown() {
+ // Transparent is the default, so we need to switch to a different mode first
+ underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+ reset(startIcons)
+
+ underTest.transitionTo(/* mode= */ MODE_TRANSPARENT, /* animate= */ false)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(1)
+ }
+
+ @Test
+ fun transitionTo_opaqueMode_batteryIconUsesResourceAlpha() {
+ underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+
+ assertThat(batteryView.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+ }
+
+ @Test
+ fun transitionTo_opaqueMode_statusIconsUseResourceAlpha() {
+ underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+
+ assertThat(statusIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+ }
+
+ @Test
+ fun transitionTo_opaqueMode_startIconsUseResourceAlpha() {
+ underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+ }
+
+ @Test
+ fun onHeadsUpStateChanged_true_semiTransparentMode_startIconsShown() {
+ underTest.transitionTo(/* mode= */ MODE_SEMI_TRANSPARENT, /* animate= */ false)
+ reset(startIcons)
+
+ underTest.onHeadsUpStateChanged(true)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(1)
+ }
+
+ @Test
+ fun onHeadsUpStateChanged_true_opaqueMode_startIconsUseResourceAlpha() {
+ underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+ reset(startIcons)
+
+ underTest.onHeadsUpStateChanged(true)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+ }
+
+ /** Regression test for b/291173113. */
+ @Test
+ fun onHeadsUpStateChanged_true_lightsOutMode_startIconsUseResourceAlpha() {
+ underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+ reset(startIcons)
+
+ underTest.onHeadsUpStateChanged(true)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+ }
+
+ @Test
+ fun onHeadsUpStateChanged_false_semiTransparentMode_startIconsShown() {
+ underTest.transitionTo(/* mode= */ MODE_SEMI_TRANSPARENT, /* animate= */ false)
+ reset(startIcons)
+
+ underTest.onHeadsUpStateChanged(false)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(1)
+ }
+
+ @Test
+ fun onHeadsUpStateChanged_false_opaqueMode_startIconsUseResourceAlpha() {
+ underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+ reset(startIcons)
+
+ underTest.onHeadsUpStateChanged(false)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+ }
+
+ @Test
+ fun onHeadsUpStateChanged_false_lightsOutMode_startIconsHidden() {
+ underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+ reset(startIcons)
+
+ underTest.onHeadsUpStateChanged(false)
+
+ assertThat(startIcons.capturedAlpha()).isEqualTo(0)
+ }
+
+ private fun View.capturedAlpha(): Float {
+ val captor = argumentCaptor<Float>()
+ verify(this).alpha = captor.capture()
+ return captor.value
+ }
+
+ private companion object {
+ const val RESOURCE_ALPHA = 0.34f
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index ed9cf3f..0da7360 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -35,6 +35,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
+
import android.service.trust.TrustAgentService;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -73,6 +75,8 @@
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.TaskbarDelegate;
import com.android.systemui.plugins.ActivityStarter;
@@ -201,7 +205,10 @@
mBouncerView,
mAlternateBouncerInteractor,
mUdfpsOverlayInteractor,
- mActivityStarter) {
+ mActivityStarter,
+ mock(KeyguardTransitionInteractor.class),
+ StandardTestDispatcher(null, null),
+ () -> mock(WindowManagerLockscreenVisibilityInteractor.class)) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
@@ -701,7 +708,10 @@
mBouncerView,
mAlternateBouncerInteractor,
mUdfpsOverlayInteractor,
- mActivityStarter) {
+ mActivityStarter,
+ mock(KeyguardTransitionInteractor.class),
+ StandardTestDispatcher(null, null),
+ () -> mock(WindowManagerLockscreenVisibilityInteractor.class)) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
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 1c8dac1..4f7bb72 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
@@ -34,6 +34,8 @@
MutableStateFlow(WifiNetworkModel.Inactive)
override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork
+ override val secondaryNetworks = MutableStateFlow<List<WifiNetworkModel>>(emptyList())
+
private val _wifiActivity = MutableStateFlow(ACTIVITY_DEFAULT)
override val wifiActivity: StateFlow<DataActivityModel> = _wifiActivity
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 2dbeb7a..bea1154 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
@@ -1004,6 +1004,27 @@
}
@Test
+ fun secondaryNetworks_alwaysEmpty() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.secondaryNetworks)
+ collectLastValue(underTest.wifiNetwork)
+
+ // Even WHEN we do have non-primary wifi info
+ val wifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.ssid).thenReturn(SSID)
+ whenever(this.isPrimary).thenReturn(false)
+ }
+ val network = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
+
+ getNetworkCallback()
+ .onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo))
+
+ // THEN the secondary networks list is empty because this repo doesn't support it
+ assertThat(latest).isEmpty()
+ }
+
+ @Test
fun isWifiConnectedWithValidSsid_inactiveNetwork_false() =
testScope.runTest {
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 9959e00..662e36a 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
@@ -101,6 +101,7 @@
@Before
fun setUp() {
featureFlags.set(Flags.INSTANT_TETHER, false)
+ featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, false)
whenever(wifiPickerTrackerFactory.create(any(), capture(callbackCaptor)))
.thenReturn(wifiPickerTracker)
}
@@ -763,6 +764,197 @@
}
@Test
+ fun secondaryNetworks_activeEntriesEmpty_isEmpty() =
+ testScope.runTest {
+ featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+ val latest by collectLastValue(underTest.secondaryNetworks)
+
+ whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf())
+
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest).isEmpty()
+ }
+
+ @Test
+ fun secondaryNetworks_oneActiveEntry_hasOne() =
+ testScope.runTest {
+ featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+ val latest by collectLastValue(underTest.secondaryNetworks)
+
+ val wifiEntry = mock<WifiEntry>()
+ whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf(wifiEntry))
+
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest).hasSize(1)
+ }
+
+ @Test
+ fun secondaryNetworks_multipleActiveEntries_hasMultiple() =
+ testScope.runTest {
+ featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+ val latest by collectLastValue(underTest.secondaryNetworks)
+
+ val wifiEntry1 = mock<WifiEntry>()
+ val wifiEntry2 = mock<WifiEntry>()
+ whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf(wifiEntry1, wifiEntry2))
+
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest).hasSize(2)
+ }
+
+ @Test
+ fun secondaryNetworks_mapsToInactive() =
+ testScope.runTest {
+ featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+ val latest by collectLastValue(underTest.secondaryNetworks)
+
+ val inactiveEntry =
+ mock<WifiEntry>().apply { whenever(this.level).thenReturn(WIFI_LEVEL_UNREACHABLE) }
+ whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf(inactiveEntry))
+
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0]).isInstanceOf(WifiNetworkModel.Inactive::class.java)
+ }
+
+ @Test
+ fun secondaryNetworks_mapsToActive() =
+ testScope.runTest {
+ featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+ val latest by collectLastValue(underTest.secondaryNetworks)
+
+ val activeEntry = mock<WifiEntry>().apply { whenever(this.level).thenReturn(2) }
+ whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf(activeEntry))
+
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0]).isInstanceOf(WifiNetworkModel.Active::class.java)
+ assertThat((latest!![0] as WifiNetworkModel.Active).level).isEqualTo(2)
+ }
+
+ @Test
+ fun secondaryNetworks_mapsToCarrierMerged() =
+ testScope.runTest {
+ featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+ val latest by collectLastValue(underTest.secondaryNetworks)
+
+ val carrierMergedEntry =
+ mock<MergedCarrierEntry>().apply { whenever(this.level).thenReturn(3) }
+ whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf(carrierMergedEntry))
+
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0]).isInstanceOf(WifiNetworkModel.CarrierMerged::class.java)
+ assertThat((latest!![0] as WifiNetworkModel.CarrierMerged).level).isEqualTo(3)
+ }
+
+ @Test
+ fun secondaryNetworks_mapsMultipleInOrder() =
+ testScope.runTest {
+ featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+ val latest by collectLastValue(underTest.secondaryNetworks)
+
+ val activeEntry = mock<WifiEntry>().apply { whenever(this.level).thenReturn(2) }
+ val carrierMergedEntry =
+ mock<MergedCarrierEntry>().apply { whenever(this.level).thenReturn(3) }
+ whenever(wifiPickerTracker.activeWifiEntries)
+ .thenReturn(listOf(activeEntry, carrierMergedEntry))
+
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest!![0]).isInstanceOf(WifiNetworkModel.Active::class.java)
+ assertThat((latest!![0] as WifiNetworkModel.Active).level).isEqualTo(2)
+ assertThat(latest!![1]).isInstanceOf(WifiNetworkModel.CarrierMerged::class.java)
+ assertThat((latest!![1] as WifiNetworkModel.CarrierMerged).level).isEqualTo(3)
+ }
+
+ @Test
+ fun secondaryNetworks_filtersOutConnectedEntry() =
+ testScope.runTest {
+ featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+ val latest by collectLastValue(underTest.secondaryNetworks)
+
+ val connectedEntry = mock<WifiEntry>().apply { whenever(this.level).thenReturn(1) }
+ val secondaryEntry1 = mock<WifiEntry>().apply { whenever(this.level).thenReturn(2) }
+ val secondaryEntry2 = mock<WifiEntry>().apply { whenever(this.level).thenReturn(3) }
+ // WHEN the active list has both a primary and secondary networks
+ whenever(wifiPickerTracker.activeWifiEntries)
+ .thenReturn(listOf(connectedEntry, secondaryEntry1, secondaryEntry2))
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(connectedEntry)
+
+ getCallback().onWifiEntriesChanged()
+
+ // THEN only the secondary networks are included
+ assertThat(latest).hasSize(2)
+ assertThat((latest!![0] as WifiNetworkModel.Active).level).isEqualTo(2)
+ assertThat((latest!![1] as WifiNetworkModel.Active).level).isEqualTo(3)
+ }
+
+ @Test
+ fun secondaryNetworks_noConnectedEntry_hasAllActiveEntries() =
+ testScope.runTest {
+ featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+ val latest by collectLastValue(underTest.secondaryNetworks)
+
+ val secondaryEntry1 = mock<WifiEntry>().apply { whenever(this.level).thenReturn(2) }
+ val secondaryEntry2 = mock<WifiEntry>().apply { whenever(this.level).thenReturn(3) }
+ whenever(wifiPickerTracker.activeWifiEntries)
+ .thenReturn(listOf(secondaryEntry1, secondaryEntry2))
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest).hasSize(2)
+ assertThat((latest!![0] as WifiNetworkModel.Active).level).isEqualTo(2)
+ assertThat((latest!![1] as WifiNetworkModel.Active).level).isEqualTo(3)
+ }
+
+ @Test
+ fun secondaryNetworks_filtersOutPrimaryNetwork() =
+ testScope.runTest {
+ featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+ val latest by collectLastValue(underTest.secondaryNetworks)
+
+ val primaryEntry =
+ mock<WifiEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.level).thenReturn(1)
+ }
+ val secondaryEntry1 = mock<WifiEntry>().apply { whenever(this.level).thenReturn(2) }
+ val secondaryEntry2 = mock<WifiEntry>().apply { whenever(this.level).thenReturn(3) }
+ // WHEN the active list has both a primary and secondary networks
+ whenever(wifiPickerTracker.activeWifiEntries)
+ .thenReturn(listOf(secondaryEntry1, primaryEntry, secondaryEntry2))
+
+ getCallback().onWifiEntriesChanged()
+
+ // THEN only the secondary networks are included
+ assertThat(latest).hasSize(2)
+ assertThat((latest!![0] as WifiNetworkModel.Active).level).isEqualTo(2)
+ assertThat((latest!![1] as WifiNetworkModel.Active).level).isEqualTo(3)
+ }
+
+ @Test
+ fun secondaryNetworks_flagOff_noNetworks() =
+ testScope.runTest {
+ featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, false)
+ val latest by collectLastValue(underTest.secondaryNetworks)
+
+ val wifiEntry = mock<WifiEntry>()
+ whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf(wifiEntry))
+
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest).isEmpty()
+ }
+
+ @Test
fun isWifiConnectedWithValidSsid_inactiveNetwork_false() =
testScope.runTest {
collectLastValue(underTest.wifiNetwork)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
index 14edf3d..e5bbead 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.policy;
+import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
+
import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
import static com.google.common.truth.Truth.assertThat;
@@ -40,7 +42,6 @@
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
-import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -48,6 +49,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
+import com.android.systemui.R;
import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.AlertingNotificationManagerTest;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -63,16 +65,11 @@
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class HeadsUpManagerTest extends AlertingNotificationManagerTest {
- private static final int TEST_A11Y_AUTO_DISMISS_TIME = 600;
- private static final int TEST_A11Y_TIMEOUT_TIME = 5_000;
+ private static final int TEST_TOUCH_ACCEPTANCE_TIME = 200;
+ private static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000;
+ private static final int TEST_A11Y_TIMEOUT_TIME = 3_000;
- private HeadsUpManager mHeadsUpManager;
- private boolean mLivesPastNormalTime;
private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
- @Mock private HeadsUpManager.HeadsUpEntry mAlertEntry;
- @Mock private NotificationEntry mEntry;
- @Mock private StatusBarNotification mSbn;
- @Mock private Notification mNotification;
private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
@Mock private AccessibilityManagerWrapper mAccessibilityMgr;
@@ -83,27 +80,81 @@
AccessibilityManagerWrapper accessibilityManagerWrapper,
UiEventLogger uiEventLogger) {
super(context, logger, handler, accessibilityManagerWrapper, uiEventLogger);
+ mTouchAcceptanceDelay = TEST_TOUCH_ACCEPTANCE_TIME;
mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
- mStickyDisplayTime = TEST_STICKY_DISPLAY_TIME;
mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
+ mStickyDisplayTime = TEST_STICKY_AUTO_DISMISS_TIME;
}
}
+ private HeadsUpManager createHeadsUpManager() {
+ return new TestableHeadsUpManager(mContext, mLogger, mTestHandler, mAccessibilityMgr,
+ mUiEventLoggerFake);
+ }
+
@Override
protected AlertingNotificationManager createAlertingNotificationManager() {
- return mHeadsUpManager;
+ return createHeadsUpManager();
}
+ private NotificationEntry createStickyEntry(int id) {
+ final Notification notif = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setFullScreenIntent(mock(PendingIntent.class), /* highPriority */ true)
+ .build();
+ return createEntry(id, notif);
+ }
+
+ private NotificationEntry createStickyForSomeTimeEntry(int id) {
+ final Notification notif = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setFlag(FLAG_FSI_REQUESTED_BUT_DENIED, true)
+ .build();
+ return createEntry(id, notif);
+ }
+
+ private PendingIntent createFullScreenIntent() {
+ return PendingIntent.getActivity(
+ getContext(), 0, new Intent(getContext(), this.getClass()),
+ PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ }
+
+ private NotificationEntry createFullScreenIntentEntry(int id) {
+ final Notification notif = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setFullScreenIntent(createFullScreenIntent(), /* highPriority */ true)
+ .build();
+ return createEntry(id, notif);
+ }
+
+
+ private void useAccessibilityTimeout(boolean use) {
+ if (use) {
+ doReturn(TEST_A11Y_AUTO_DISMISS_TIME).when(mAccessibilityMgr)
+ .getRecommendedTimeoutMillis(anyInt(), anyInt());
+ } else {
+ when(mAccessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt())).then(
+ i -> i.getArgument(0));
+ }
+ }
+
+
@Before
@Override
public void setUp() {
initMocks(this);
- when(mEntry.getSbn()).thenReturn(mSbn);
- when(mEntry.getKey()).thenReturn("entryKey");
- when(mSbn.getNotification()).thenReturn(mNotification);
super.setUp();
- mHeadsUpManager = new TestableHeadsUpManager(mContext, mLogger, mTestHandler,
- mAccessibilityMgr, mUiEventLoggerFake);
+
+ assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
+ assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
+ assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_A11Y_AUTO_DISMISS_TIME);
+
+ assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_AUTO_DISMISS_TIME).isLessThan(
+ TEST_TIMEOUT_TIME);
+ assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(
+ TEST_TIMEOUT_TIME);
+ assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_A11Y_AUTO_DISMISS_TIME).isLessThan(
+ TEST_A11Y_TIMEOUT_TIME);
}
@After
@@ -114,193 +165,327 @@
@Test
public void testHunRemovedLogging() {
- mAlertEntry.mEntry = mEntry;
- mHeadsUpManager.onAlertEntryRemoved(mAlertEntry);
- verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(mEntry));
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry notifEntry = createEntry(/* id = */ 0);
+ final HeadsUpManager.HeadsUpEntry headsUpEntry = mock(HeadsUpManager.HeadsUpEntry.class);
+ headsUpEntry.mEntry = notifEntry;
+
+ hum.onAlertEntryRemoved(headsUpEntry);
+
+ verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(notifEntry));
}
@Test
public void testShouldHeadsUpBecomePinned_hasFSI_notUnpinned_true() {
- // Set up NotifEntry with FSI
- NotificationEntry notifEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(/* id= */ 0))
- .build();
- notifEntry.getSbn().getNotification().fullScreenIntent = PendingIntent.getActivity(
- getContext(), 0, new Intent(getContext(), this.getClass()),
- PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0);
// Add notifEntry to ANM mAlertEntries map and make it NOT unpinned
- mHeadsUpManager.showNotification(notifEntry);
- HeadsUpManager.HeadsUpEntry headsUpEntry =
- mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
+ hum.showNotification(notifEntry);
+
+ final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
headsUpEntry.wasUnpinned = false;
- assertTrue(mHeadsUpManager.shouldHeadsUpBecomePinned(notifEntry));
+ assertTrue(hum.shouldHeadsUpBecomePinned(notifEntry));
}
@Test
public void testShouldHeadsUpBecomePinned_wasUnpinned_false() {
- // Set up NotifEntry with FSI
- NotificationEntry notifEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(/* id= */ 0))
- .build();
- notifEntry.getSbn().getNotification().fullScreenIntent = PendingIntent.getActivity(
- getContext(), 0, new Intent(getContext(), this.getClass()),
- PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0);
// Add notifEntry to ANM mAlertEntries map and make it unpinned
- mHeadsUpManager.showNotification(notifEntry);
- HeadsUpManager.HeadsUpEntry headsUpEntry =
- mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
+ hum.showNotification(notifEntry);
+
+ final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
headsUpEntry.wasUnpinned = true;
- assertFalse(mHeadsUpManager.shouldHeadsUpBecomePinned(notifEntry));
+ assertFalse(hum.shouldHeadsUpBecomePinned(notifEntry));
}
@Test
public void testShouldHeadsUpBecomePinned_noFSI_false() {
- // Set up NotifEntry with no FSI
- NotificationEntry notifEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(/* id= */ 0))
- .build();
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
- assertFalse(mHeadsUpManager.shouldHeadsUpBecomePinned(notifEntry));
+ assertFalse(hum.shouldHeadsUpBecomePinned(entry));
}
+
+ @Test
+ public void testShowNotification_autoDismissesIncludingTouchAcceptanceDelay() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+ useAccessibilityTimeout(false);
+
+ hum.showNotification(entry);
+
+ final int pastJustAutoDismissMillis =
+ TEST_TOUCH_ACCEPTANCE_TIME / 2 + TEST_AUTO_DISMISS_TIME;
+ verifyAlertingAtTime(hum, entry, true, pastJustAutoDismissMillis, "just auto dismiss");
+ }
+
+
+ @Test
+ public void testShowNotification_autoDismissesWithDefaultTimeout() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+ useAccessibilityTimeout(false);
+
+ hum.showNotification(entry);
+
+ final int pastDefaultTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
+ + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2;
+ verifyAlertingAtTime(hum, entry, false, pastDefaultTimeoutMillis, "default timeout");
+ }
+
+
+ @Test
+ public void testShowNotification_stickyForSomeTime_autoDismissesWithStickyTimeout() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
+ useAccessibilityTimeout(false);
+
+ hum.showNotification(entry);
+
+ final int pastDefaultTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
+ + (TEST_AUTO_DISMISS_TIME + TEST_STICKY_AUTO_DISMISS_TIME) / 2;
+ verifyAlertingAtTime(hum, entry, true, pastDefaultTimeoutMillis, "default timeout");
+ }
+
+
+ @Test
+ public void testShowNotification_sticky_neverAutoDismisses() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createStickyEntry(/* id = */ 0);
+ useAccessibilityTimeout(false);
+
+ hum.showNotification(entry);
+
+ final int pastLongestAutoDismissMillis =
+ TEST_TOUCH_ACCEPTANCE_TIME + 2 * TEST_A11Y_AUTO_DISMISS_TIME;
+ final Boolean[] wasAlerting = {null};
+ final Runnable checkAlerting =
+ () -> wasAlerting[0] = hum.isAlerting(entry.getKey());
+ mTestHandler.postDelayed(checkAlerting, pastLongestAutoDismissMillis);
+ TestableLooper.get(this).processMessages(1);
+
+ assertTrue("Should still be alerting past longest auto-dismiss", wasAlerting[0]);
+ assertTrue("Should still be alerting after processing",
+ hum.isAlerting(entry.getKey()));
+ }
+
+
@Test
public void testShowNotification_autoDismissesWithAccessibilityTimeout() {
- doReturn(TEST_A11Y_AUTO_DISMISS_TIME).when(mAccessibilityMgr)
- .getRecommendedTimeoutMillis(anyInt(), anyInt());
- mHeadsUpManager.showNotification(mEntry);
- Runnable pastNormalTimeRunnable =
- () -> mLivesPastNormalTime = mHeadsUpManager.isAlerting(mEntry.getKey());
- mTestHandler.postDelayed(pastNormalTimeRunnable,
- (TEST_A11Y_AUTO_DISMISS_TIME + TEST_AUTO_DISMISS_TIME) / 2);
- mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_A11Y_TIMEOUT_TIME);
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+ useAccessibilityTimeout(true);
- TestableLooper.get(this).processMessages(2);
+ hum.showNotification(entry);
- assertFalse("Test timed out", mTimedOut);
- assertTrue("Heads up should live long enough", mLivesPastNormalTime);
- assertFalse(mHeadsUpManager.isAlerting(mEntry.getKey()));
+ final int pastDefaultTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
+ + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2;
+ verifyAlertingAtTime(hum, entry, true, pastDefaultTimeoutMillis, "default timeout");
+ }
+
+
+ @Test
+ public void testShowNotification_stickyForSomeTime_autoDismissesWithAccessibilityTimeout() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
+ useAccessibilityTimeout(true);
+
+ hum.showNotification(entry);
+
+ final int pastStickyTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
+ + (TEST_STICKY_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2;
+ verifyAlertingAtTime(hum, entry, true, pastStickyTimeoutMillis, "sticky timeout");
+ }
+
+
+ @Test
+ public void testRemoveNotification_beforeMinimumDisplayTime() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+ useAccessibilityTimeout(false);
+
+ hum.showNotification(entry);
+
+ // Try to remove but defer, since the notification has not been shown long enough.
+ final boolean removedImmediately = hum.removeNotification(
+ entry.getKey(), false /* releaseImmediately */);
+
+ assertFalse("HUN should not be removed before minimum display time", removedImmediately);
+ assertTrue("HUN should still be alerting before minimum display time",
+ hum.isAlerting(entry.getKey()));
+
+ final int pastMinimumDisplayTimeMillis =
+ (TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2;
+ verifyAlertingAtTime(hum, entry, false, pastMinimumDisplayTimeMillis,
+ "minimum display time");
+ }
+
+
+ @Test
+ public void testRemoveNotification_afterMinimumDisplayTime() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+ useAccessibilityTimeout(false);
+
+ hum.showNotification(entry);
+
+ // After the minimum display time:
+ // 1. Check whether the notification is still alerting.
+ // 2. Try to remove it and check whether the remove succeeded.
+ // 3. Check whether it is still alerting after trying to remove it.
+ final Boolean[] livedPastMinimumDisplayTime = {null};
+ final Boolean[] removedAfterMinimumDisplayTime = {null};
+ final Boolean[] livedPastRemoveAfterMinimumDisplayTime = {null};
+ final Runnable pastMinimumDisplayTimeRunnable = () -> {
+ livedPastMinimumDisplayTime[0] = hum.isAlerting(entry.getKey());
+ removedAfterMinimumDisplayTime[0] = hum.removeNotification(
+ entry.getKey(), /* releaseImmediately = */ false);
+ livedPastRemoveAfterMinimumDisplayTime[0] = hum.isAlerting(entry.getKey());
+ };
+ final int pastMinimumDisplayTimeMillis =
+ (TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2;
+ mTestHandler.postDelayed(pastMinimumDisplayTimeRunnable, pastMinimumDisplayTimeMillis);
+ // Wait until the minimum display time has passed before attempting removal.
+ TestableLooper.get(this).processMessages(1);
+
+ assertTrue("HUN should live past minimum display time",
+ livedPastMinimumDisplayTime[0]);
+ assertTrue("HUN should be removed immediately past minimum display time",
+ removedAfterMinimumDisplayTime[0]);
+ assertFalse("HUN should not live after being removed past minimum display time",
+ livedPastRemoveAfterMinimumDisplayTime[0]);
+ assertFalse(hum.isAlerting(entry.getKey()));
+ }
+
+
+ @Test
+ public void testRemoveNotification_releaseImmediately() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ hum.showNotification(entry);
+
+ // Remove forcibly with releaseImmediately = true.
+ final boolean removedImmediately = hum.removeNotification(
+ entry.getKey(), /* releaseImmediately = */ true);
+
+ assertTrue(removedImmediately);
+ assertFalse(hum.isAlerting(entry.getKey()));
}
@Test
public void testIsSticky_rowPinnedAndExpanded_true() {
- NotificationEntry notifEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(/* id= */ 0))
- .build();
-
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry notifEntry = createEntry(/* id = */ 0);
when(mRow.isPinned()).thenReturn(true);
notifEntry.setRow(mRow);
- mHeadsUpManager.showNotification(notifEntry);
+ hum.showNotification(notifEntry);
- HeadsUpManager.HeadsUpEntry headsUpEntry =
- mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
+ final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
headsUpEntry.setExpanded(true);
- assertTrue(mHeadsUpManager.isSticky(notifEntry.getKey()));
+ assertTrue(hum.isSticky(notifEntry.getKey()));
}
@Test
public void testIsSticky_remoteInputActive_true() {
- NotificationEntry notifEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(/* id= */ 0))
- .build();
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry notifEntry = createEntry(/* id = */ 0);
- mHeadsUpManager.showNotification(notifEntry);
+ hum.showNotification(notifEntry);
- HeadsUpManager.HeadsUpEntry headsUpEntry =
- mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
-
+ final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
headsUpEntry.remoteInputActive = true;
- assertTrue(mHeadsUpManager.isSticky(notifEntry.getKey()));
+ assertTrue(hum.isSticky(notifEntry.getKey()));
}
@Test
public void testIsSticky_hasFullScreenIntent_true() {
- NotificationEntry notifEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(/* id= */ 0))
- .build();
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0);
- mHeadsUpManager.showNotification(notifEntry);
+ hum.showNotification(notifEntry);
- HeadsUpManager.HeadsUpEntry headsUpEntry =
- mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
-
- notifEntry.getSbn().getNotification().fullScreenIntent = PendingIntent.getActivity(
- getContext(), 0, new Intent(getContext(), this.getClass()),
- PendingIntent.FLAG_MUTABLE_UNAUDITED);
-
- assertTrue(mHeadsUpManager.isSticky(notifEntry.getKey()));
+ assertTrue(hum.isSticky(notifEntry.getKey()));
}
+
@Test
- public void testIsSticky_stickyAndNotDemoted_true() {
- NotificationEntry alertEntry = new NotificationEntryBuilder()
- .setSbn(createStickySbn(0))
- .build();
+ public void testIsSticky_stickyForSomeTime_false() {
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
- mHeadsUpManager.showNotification(alertEntry);
+ hum.showNotification(entry);
- assertTrue(mHeadsUpManager.isSticky(alertEntry.getKey()));
+ assertFalse(hum.isSticky(entry.getKey()));
}
+
@Test
public void testIsSticky_false() {
- NotificationEntry notifEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(/* id= */ 0))
- .build();
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry notifEntry = createEntry(/* id = */ 0);
- mHeadsUpManager.showNotification(notifEntry);
+ hum.showNotification(notifEntry);
- HeadsUpManager.HeadsUpEntry headsUpEntry =
- mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
+ final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
headsUpEntry.setExpanded(false);
headsUpEntry.remoteInputActive = false;
- assertFalse(mHeadsUpManager.isSticky(notifEntry.getKey()));
+ assertFalse(hum.isSticky(notifEntry.getKey()));
}
@Test
public void testCompareTo_withNullEntries() {
- NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
- mHeadsUpManager.showNotification(alertEntry);
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
- assertThat(mHeadsUpManager.compare(alertEntry, null)).isLessThan(0);
- assertThat(mHeadsUpManager.compare(null, alertEntry)).isGreaterThan(0);
- assertThat(mHeadsUpManager.compare(null, null)).isEqualTo(0);
+ hum.showNotification(alertEntry);
+
+ assertThat(hum.compare(alertEntry, null)).isLessThan(0);
+ assertThat(hum.compare(null, alertEntry)).isGreaterThan(0);
+ assertThat(hum.compare(null, null)).isEqualTo(0);
}
@Test
public void testCompareTo_withNonAlertEntries() {
- NotificationEntry nonAlertEntry1 = new NotificationEntryBuilder().setTag("nae1").build();
- NotificationEntry nonAlertEntry2 = new NotificationEntryBuilder().setTag("nae2").build();
- NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
- mHeadsUpManager.showNotification(alertEntry);
+ final HeadsUpManager hum = createHeadsUpManager();
- assertThat(mHeadsUpManager.compare(alertEntry, nonAlertEntry1)).isLessThan(0);
- assertThat(mHeadsUpManager.compare(nonAlertEntry1, alertEntry)).isGreaterThan(0);
- assertThat(mHeadsUpManager.compare(nonAlertEntry1, nonAlertEntry2)).isEqualTo(0);
+ final NotificationEntry nonAlertEntry1 = new NotificationEntryBuilder().setTag(
+ "nae1").build();
+ final NotificationEntry nonAlertEntry2 = new NotificationEntryBuilder().setTag(
+ "nae2").build();
+ final NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
+ hum.showNotification(alertEntry);
+
+ assertThat(hum.compare(alertEntry, nonAlertEntry1)).isLessThan(0);
+ assertThat(hum.compare(nonAlertEntry1, alertEntry)).isGreaterThan(0);
+ assertThat(hum.compare(nonAlertEntry1, nonAlertEntry2)).isEqualTo(0);
}
@Test
public void testAlertEntryCompareTo_ongoingCallLessThanActiveRemoteInput() {
- HeadsUpManager.HeadsUpEntry ongoingCall = mHeadsUpManager.new HeadsUpEntry();
+ final HeadsUpManager hum = createHeadsUpManager();
+
+ final HeadsUpManager.HeadsUpEntry ongoingCall = hum.new HeadsUpEntry();
ongoingCall.setEntry(new NotificationEntryBuilder()
- .setSbn(createNewSbn(0,
+ .setSbn(createSbn(/* id = */ 0,
new Notification.Builder(mContext, "")
.setCategory(Notification.CATEGORY_CALL)
.setOngoing(true)))
.build());
- HeadsUpManager.HeadsUpEntry activeRemoteInput = mHeadsUpManager.new HeadsUpEntry();
- activeRemoteInput.setEntry(new NotificationEntryBuilder()
- .setSbn(createNewNotification(1))
- .build());
+ final HeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry();
+ activeRemoteInput.setEntry(createEntry(/* id = */ 1));
activeRemoteInput.remoteInputActive = true;
assertThat(ongoingCall.compareTo(activeRemoteInput)).isLessThan(0);
@@ -309,20 +494,20 @@
@Test
public void testAlertEntryCompareTo_incomingCallLessThanActiveRemoteInput() {
- HeadsUpManager.HeadsUpEntry incomingCall = mHeadsUpManager.new HeadsUpEntry();
- Person person = new Person.Builder().setName("person").build();
- PendingIntent intent = mock(PendingIntent.class);
+ final HeadsUpManager hum = createHeadsUpManager();
+
+ final HeadsUpManager.HeadsUpEntry incomingCall = hum.new HeadsUpEntry();
+ final Person person = new Person.Builder().setName("person").build();
+ final PendingIntent intent = mock(PendingIntent.class);
incomingCall.setEntry(new NotificationEntryBuilder()
- .setSbn(createNewSbn(0,
+ .setSbn(createSbn(/* id = */ 0,
new Notification.Builder(mContext, "")
.setStyle(Notification.CallStyle
.forIncomingCall(person, intent, intent))))
.build());
- HeadsUpManager.HeadsUpEntry activeRemoteInput = mHeadsUpManager.new HeadsUpEntry();
- activeRemoteInput.setEntry(new NotificationEntryBuilder()
- .setSbn(createNewNotification(1))
- .build());
+ final HeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry();
+ activeRemoteInput.setEntry(createEntry(/* id = */ 1));
activeRemoteInput.remoteInputActive = true;
assertThat(incomingCall.compareTo(activeRemoteInput)).isLessThan(0);
@@ -331,22 +516,18 @@
@Test
public void testPinEntry_logsPeek() {
- // Needs full screen intent in order to be pinned
- final PendingIntent fullScreenIntent = PendingIntent.getActivity(mContext, 0,
- new Intent().setPackage(mContext.getPackageName()), PendingIntent.FLAG_MUTABLE);
+ final HeadsUpManager hum = createHeadsUpManager();
- HeadsUpManager.HeadsUpEntry entryToPin = mHeadsUpManager.new HeadsUpEntry();
- entryToPin.setEntry(new NotificationEntryBuilder()
- .setSbn(createNewSbn(0,
- new Notification.Builder(mContext, "")
- .setFullScreenIntent(fullScreenIntent, true)))
- .build());
+ // Needs full screen intent in order to be pinned
+ final HeadsUpManager.HeadsUpEntry entryToPin = hum.new HeadsUpEntry();
+ entryToPin.setEntry(createFullScreenIntentEntry(/* id = */ 0));
+
// Note: the standard way to show a notification would be calling showNotification rather
// than onAlertEntryAdded. However, in practice showNotification in effect adds
// the notification and then updates it; in order to not log twice, the entry needs
// to have a functional ExpandableNotificationRow that can keep track of whether it's
// pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit.
- mHeadsUpManager.onAlertEntryAdded(entryToPin);
+ hum.onAlertEntryAdded(entryToPin);
assertEquals(1, mUiEventLoggerFake.numLogs());
assertEquals(HeadsUpManager.NotificationPeekEvent.NOTIFICATION_PEEK.getId(),
@@ -355,14 +536,15 @@
@Test
public void testSetUserActionMayIndirectlyRemove() {
- NotificationEntry notifEntry = new NotificationEntryBuilder()
- .setSbn(createNewNotification(/* id= */ 0))
- .build();
+ final HeadsUpManager hum = createHeadsUpManager();
+ final NotificationEntry notifEntry = createEntry(/* id = */ 0);
- mHeadsUpManager.showNotification(notifEntry);
- assertFalse(mHeadsUpManager.canRemoveImmediately(notifEntry.getKey()));
+ hum.showNotification(notifEntry);
- mHeadsUpManager.setUserActionMayIndirectlyRemove(notifEntry);
- assertTrue(mHeadsUpManager.canRemoveImmediately(notifEntry.getKey()));
+ assertFalse(hum.canRemoveImmediately(notifEntry.getKey()));
+
+ hum.setUserActionMayIndirectlyRemove(notifEntry);
+
+ assertTrue(hum.canRemoveImmediately(notifEntry.getKey()));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
index 5cabcd4..cae892f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
@@ -36,6 +36,7 @@
import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import dagger.Lazy;
@@ -67,6 +68,8 @@
private Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
@Mock
private KeyguardUpdateMonitorLogger mLogger;
+ @Mock
+ private FeatureFlags mFeatureFlags;
@Captor
private ArgumentCaptor<KeyguardUpdateMonitorCallback> mUpdateCallbackCaptor;
@@ -80,7 +83,8 @@
mLockPatternUtils,
mKeyguardUnlockAnimationControllerLazy,
mLogger,
- mDumpManager);
+ mDumpManager,
+ mFeatureFlags);
}
@Test
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSurfaceBehindRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSurfaceBehindRepository.kt
new file mode 100644
index 0000000..823f29a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSurfaceBehindRepository.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeKeyguardSurfaceBehindRepository : KeyguardSurfaceBehindRepository {
+ private val _isAnimatingSurface = MutableStateFlow(false)
+ override val isAnimatingSurface = _isAnimatingSurface.asStateFlow()
+
+ override fun setAnimatingSurface(animating: Boolean) {
+ _isAnimatingSurface.value = animating
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
index 312ade5..23faaf3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
@@ -18,6 +18,8 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.util.mockito.mock
+import dagger.Lazy
import kotlinx.coroutines.CoroutineScope
/**
@@ -30,18 +32,36 @@
fun create(
scope: CoroutineScope,
repository: KeyguardTransitionRepository = FakeKeyguardTransitionRepository(),
+ keyguardInteractor: KeyguardInteractor =
+ KeyguardInteractorFactory.create().keyguardInteractor,
+ fromLockscreenTransitionInteractor: Lazy<FromLockscreenTransitionInteractor> = Lazy {
+ mock<FromLockscreenTransitionInteractor>()
+ },
+ fromPrimaryBouncerTransitionInteractor: Lazy<FromPrimaryBouncerTransitionInteractor> =
+ Lazy {
+ mock<FromPrimaryBouncerTransitionInteractor>()
+ },
): WithDependencies {
return WithDependencies(
repository = repository,
+ keyguardInteractor = keyguardInteractor,
+ fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractor,
+ fromPrimaryBouncerTransitionInteractor = fromPrimaryBouncerTransitionInteractor,
KeyguardTransitionInteractor(
scope = scope,
repository = repository,
+ keyguardInteractor = { keyguardInteractor },
+ fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractor,
+ fromPrimaryBouncerTransitionInteractor = fromPrimaryBouncerTransitionInteractor,
)
)
}
data class WithDependencies(
val repository: KeyguardTransitionRepository,
+ val keyguardInteractor: KeyguardInteractor,
+ val fromLockscreenTransitionInteractor: Lazy<FromLockscreenTransitionInteractor>,
+ val fromPrimaryBouncerTransitionInteractor: Lazy<FromPrimaryBouncerTransitionInteractor>,
val keyguardTransitionInteractor: KeyguardTransitionInteractor,
)
}
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 507267e..df7f447 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
@@ -123,6 +123,7 @@
repository: SceneContainerRepository = fakeSceneContainerRepository()
): SceneInteractor {
return SceneInteractor(
+ applicationScope = applicationScope(),
repository = repository,
logger = mock(),
)
@@ -134,6 +135,7 @@
fun authenticationInteractor(
repository: AuthenticationRepository,
+ sceneInteractor: SceneInteractor = sceneInteractor(),
): AuthenticationInteractor {
return AuthenticationInteractor(
applicationScope = applicationScope(),
@@ -141,6 +143,7 @@
backgroundDispatcher = testDispatcher,
userRepository = userRepository,
keyguardRepository = keyguardRepository,
+ sceneInteractor = sceneInteractor,
clock = mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } }
)
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 061b422..ca4a8a2 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1875,6 +1875,13 @@
}
}
+ /**
+ * Computes datasets that are eligible to be shown based on provider detections.
+ * Datasets are populated in the provided container for them to be later merged with the
+ * PCC eligible datasets based on preference strategy.
+ * @param response
+ * @param container
+ */
private void computeDatasetsForProviderAndUpdateContainer(
FillResponse response, DatasetComputationContainer container) {
@DatasetEligibleReason int globalPickReason = PICK_REASON_UNKNOWN;
@@ -1975,6 +1982,13 @@
container.mAutofillIds = eligibleAutofillIds;
}
+ /**
+ * Computes datasets that are eligible to be shown based on PCC detections.
+ * Datasets are populated in the provided container for them to be later merged with the
+ * provider eligible datasets based on preference strategy.
+ * @param response
+ * @param container
+ */
private void computeDatasetsForPccAndUpdateContainer(
FillResponse response, DatasetComputationContainer container) {
List<Dataset> datasets = response.getDatasets();
@@ -2009,11 +2023,33 @@
ArrayList<Dataset.DatasetFieldFilter> fieldFilters = new ArrayList<>();
Set<AutofillId> datasetAutofillIds = new ArraySet<>();
+ boolean isDatasetAvailable = false;
+ Set<AutofillId> additionalDatasetAutofillIds = new LinkedHashSet<>();
+ Set<AutofillId> additionalEligibleAutofillIds = new LinkedHashSet<>();
+
for (int j = 0; j < dataset.getAutofillDatatypes().size(); j++) {
if (dataset.getAutofillDatatypes().get(j) == null) {
+ // TODO : revisit pickReason logic
if (dataset.getFieldIds() != null && dataset.getFieldIds().get(j) != null) {
pickReason = PICK_REASON_PCC_DETECTION_PREFERRED_WITH_PROVIDER;
}
+ // Check if the autofill id at this index is detected by PCC.
+ // If not, add that id here, otherwise, we can have duplicates when later
+ // merging with provider datasets.
+ // Howover, this doesn't make datasetAvailable for PCC on its own.
+ // For that, there has to be a datatype detected by PCC, and the dataset
+ // for that datatype provided by the provider.
+ AutofillId autofillId = dataset.getFieldIds().get(j);
+ if (!mClassificationState.mClassificationCombinedHintsMap
+ .containsKey(autofillId)) {
+ additionalEligibleAutofillIds.add(autofillId);
+ additionalDatasetAutofillIds.add(autofillId);
+ // For each of the field, copy over values.
+ copyFieldsFromDataset(dataset, j, autofillId, fieldIds, fieldValues,
+ fieldPresentations, fieldDialogPresentations,
+ fieldInlinePresentations, fieldInlineTooltipPresentations,
+ fieldFilters);
+ }
continue;
}
String hint = dataset.getAutofillDatatypes().get(j);
@@ -2021,22 +2057,18 @@
if (hintsToAutofillIdMap.containsKey(hint)) {
ArrayList<AutofillId> tempIds =
new ArrayList<>(hintsToAutofillIdMap.get(hint));
-
+ if (tempIds.isEmpty()) {
+ continue;
+ }
+ isDatasetAvailable = true;
for (AutofillId autofillId : tempIds) {
eligibleAutofillIds.add(autofillId);
datasetAutofillIds.add(autofillId);
// For each of the field, copy over values.
- fieldIds.add(autofillId);
- fieldValues.add(dataset.getFieldValues().get(j));
- // TODO(b/266379948): might need to make it more efficient by not
- // copying over value if it didn't exist. This would require creating
- // a getter for the presentations arraylist.
- fieldPresentations.add(dataset.getFieldPresentation(j));
- fieldDialogPresentations.add(dataset.getFieldDialogPresentation(j));
- fieldInlinePresentations.add(dataset.getFieldInlinePresentation(j));
- fieldInlineTooltipPresentations.add(
- dataset.getFieldInlineTooltipPresentation(j));
- fieldFilters.add(dataset.getFilter(j));
+ copyFieldsFromDataset(dataset, j, autofillId, fieldIds, fieldValues,
+ fieldPresentations, fieldDialogPresentations,
+ fieldInlinePresentations, fieldInlineTooltipPresentations,
+ fieldFilters);
}
}
// TODO(b/266379948): handle the case:
@@ -2045,34 +2077,38 @@
// TODO(b/266379948): also handle the case where there could be more types in
// the dataset, provided by the provider, however, they aren't applicable.
}
- Dataset newDataset =
- new Dataset(
- fieldIds,
- fieldValues,
- fieldPresentations,
- fieldDialogPresentations,
- fieldInlinePresentations,
- fieldInlineTooltipPresentations,
- fieldFilters,
- new ArrayList<>(),
- dataset.getFieldContent(),
- null,
- null,
- null,
- null,
- dataset.getId(),
- dataset.getAuthentication());
- newDataset.setEligibleReasonReason(pickReason);
- eligibleDatasets.add(newDataset);
- Set<Dataset> newDatasets;
- for (AutofillId autofillId : datasetAutofillIds) {
- if (map.containsKey(autofillId)) {
- newDatasets = map.get(autofillId);
- } else {
- newDatasets = new ArraySet<>();
+ if (isDatasetAvailable) {
+ datasetAutofillIds.addAll(additionalDatasetAutofillIds);
+ eligibleAutofillIds.addAll(additionalEligibleAutofillIds);
+ Dataset newDataset =
+ new Dataset(
+ fieldIds,
+ fieldValues,
+ fieldPresentations,
+ fieldDialogPresentations,
+ fieldInlinePresentations,
+ fieldInlineTooltipPresentations,
+ fieldFilters,
+ new ArrayList<>(),
+ dataset.getFieldContent(),
+ null,
+ null,
+ null,
+ null,
+ dataset.getId(),
+ dataset.getAuthentication());
+ newDataset.setEligibleReasonReason(pickReason);
+ eligibleDatasets.add(newDataset);
+ Set<Dataset> newDatasets;
+ for (AutofillId autofillId : datasetAutofillIds) {
+ if (map.containsKey(autofillId)) {
+ newDatasets = map.get(autofillId);
+ } else {
+ newDatasets = new LinkedHashSet<>();
+ }
+ newDatasets.add(newDataset);
+ map.put(autofillId, newDatasets);
}
- newDatasets.add(newDataset);
- map.put(autofillId, newDatasets);
}
}
container.mAutofillIds = eligibleAutofillIds;
@@ -2081,6 +2117,31 @@
}
}
+ private void copyFieldsFromDataset(
+ Dataset dataset,
+ int index,
+ AutofillId autofillId,
+ ArrayList<AutofillId> fieldIds,
+ ArrayList<AutofillValue> fieldValues,
+ ArrayList<RemoteViews> fieldPresentations,
+ ArrayList<RemoteViews> fieldDialogPresentations,
+ ArrayList<InlinePresentation> fieldInlinePresentations,
+ ArrayList<InlinePresentation> fieldInlineTooltipPresentations,
+ ArrayList<Dataset.DatasetFieldFilter> fieldFilters) {
+ // copy over values
+ fieldIds.add(autofillId);
+ fieldValues.add(dataset.getFieldValues().get(index));
+ // TODO(b/266379948): might need to make it more efficient by not
+ // copying over value if it didn't exist. This would require creating
+ // a getter for the presentations arraylist.
+ fieldPresentations.add(dataset.getFieldPresentation(index));
+ fieldDialogPresentations.add(dataset.getFieldDialogPresentation(index));
+ fieldInlinePresentations.add(dataset.getFieldInlinePresentation(index));
+ fieldInlineTooltipPresentations.add(
+ dataset.getFieldInlineTooltipPresentation(index));
+ fieldFilters.add(dataset.getFilter(index));
+ }
+
@GuardedBy("mLock")
private void processNullResponseOrFallbackLocked(int requestId, int flags) {
if (!mSessionFlags.mClientSuggestionsEnabled) {
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index 631b453..fd45d24 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 @@
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 c5501f1..996c68b 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 @@
addOnAssociationsChangedListener_enforcePermission();
enforceCallerIsSystemOrCanInteractWithUserId(getContext(), userId);
-
mListeners.register(listener, userId);
}
@@ -1015,6 +1014,29 @@
}
@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 byte[] getBackupPayload(int userId) {
+ // TODO(b/286124853): back up CDM data
+ return new byte[0];
+ }
+
+ @Override
+ public void applyRestoredPayload(byte[] payload, int userId) {
+ // TODO(b/286124853): restore CDM data
+ }
+
+ @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 54798f4..b4b9379 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 @@
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 @@
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 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 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 @@
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 @@
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 @@
}
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/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
index d862a54..253ef43 100644
--- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
+++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
@@ -33,16 +33,27 @@
]
},
{
- "name": "CtsVirtualDevicesTestCases",
+ "name": "CtsHardwareTestCases",
"options": [
{
"include-filter": "android.hardware.input.cts.tests"
},
{
- "exclude-annotation": "androidx.test.filters.FlakyTest"
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
}
],
"file_patterns": ["Virtual[^/]*\\.java"]
+ },
+ {
+ "name": "CtsAccessibilityServiceTestCases",
+ "options": [
+ {
+ "include-filter": "android.accessibilityservice.cts.AccessibilityDisplayProxyTest"
+ },
+ {
+ "exclude-annotation": "android.support.test.filters.FlakyTest"
+ }
+ ]
}
]
}
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 402fbb8..ee6c28e 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -106,9 +106,7 @@
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_TOUCH_FEEDBACK_DISABLED
| DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL
| DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH
| DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 4b58988..36ef9b7 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 @@
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;
}
@@ -5140,7 +5140,7 @@
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,
@@ -5172,7 +5172,7 @@
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,
@@ -5572,7 +5572,7 @@
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
@@ -5714,7 +5714,7 @@
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);
@@ -5878,7 +5878,7 @@
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);
@@ -6380,7 +6380,7 @@
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 {
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 3fd7d7e..c20f0aa 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -1032,7 +1032,7 @@
private static final String KEY_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION =
"enable_wait_for_finish_attach_application";
- private static final boolean DEFAULT_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION = false;
+ private static final boolean DEFAULT_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION = true;
/** @see #KEY_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION */
public volatile boolean mEnableWaitForFinishAttachApplication =
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 9fdb833..e84fed7 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -30,6 +30,7 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import java.util.ArrayList;
import java.util.HashMap;
@@ -163,6 +164,12 @@
WidgetFlags.MAGNIFIER_ASPECT_RATIO_DEFAULT));
sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION,
+ SystemUiDeviceConfigFlags.KEY_REMOTEVIEWS_ADAPTER_CONVERSION, boolean.class,
+ SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT));
+
+ sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
TextFlags.NAMESPACE, TextFlags.ENABLE_NEW_CONTEXT_MENU,
TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU, boolean.class,
TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT));
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 7037fec..e2edd8a 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -774,6 +774,11 @@
}
@GuardedBy("mService")
+ boolean isThreadReady() {
+ return mThread != null && !mPendingFinishAttach;
+ }
+
+ @GuardedBy("mService")
long getStartSeq() {
return mStartSeq;
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index b641119..76c4cfe 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -43,7 +43,6 @@
import static com.android.server.utils.EventLogger.Event.ALOGW;
import android.Manifest;
-import android.annotation.EnforcePermission;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -9999,14 +9998,6 @@
return mMediaFocusControl.abandonAudioFocus(fd, clientId, aa, callingPackageName);
}
- /** see {@link AudioManager#getFocusDuckedUidsForTest()} */
- @Override
- @EnforcePermission("QUERY_AUDIO_STATE")
- public @NonNull List<Integer> getFocusDuckedUidsForTest() {
- super.getFocusDuckedUidsForTest_enforcePermission();
- return mPlaybackMonitor.getFocusDuckedUids();
- }
-
public void unregisterAudioFocusClient(String clientId) {
new MediaMetrics.Item(mMetricsId + "focus")
.set(MediaMetrics.Property.CLIENT_NAME, clientId)
@@ -10023,67 +10014,6 @@
return mMediaFocusControl.getFocusRampTimeMs(focusGain, attr);
}
- /**
- * Test method to return the duration of the fade out applied on the players of a focus loser
- * @see AudioManager#getFocusFadeOutDurationForTest()
- * @return the fade out duration, in ms
- */
- public long getFocusFadeOutDurationForTest() {
- super.getFocusFadeOutDurationForTest_enforcePermission();
- return mMediaFocusControl.getFocusFadeOutDurationForTest();
- }
-
- /**
- * Test method to return the length of time after a fade out before the focus loser is unmuted
- * (and is faded back in).
- * @see AudioManager#getFocusUnmuteDelayAfterFadeOutForTest()
- * @return the time gap after a fade out completion on focus loss, and fade in start, in ms
- */
- @Override
- @EnforcePermission("QUERY_AUDIO_STATE")
- public long getFocusUnmuteDelayAfterFadeOutForTest() {
- super.getFocusUnmuteDelayAfterFadeOutForTest_enforcePermission();
- return mMediaFocusControl.getFocusUnmuteDelayAfterFadeOutForTest();
- }
-
- /**
- * Test method to start preventing applications from requesting audio focus during a test,
- * which could interfere with the testing of the functionality/behavior under test.
- * Calling this method needs to be paired with a call to {@link #exitAudioFocusFreezeForTest}
- * when the testing is done. If this is not the case (e.g. in case of a test crash),
- * a death observer mechanism will ensure the system is not left in a bad state, but this should
- * not be relied on when implementing tests.
- * @see AudioManager#enterAudioFocusFreezeForTest(List)
- * @param cb IBinder to track the death of the client of this method
- * @param exemptedUids a list of UIDs that are exempt from the freeze. This would for instance
- * be those of the test runner and other players used in the test
- * @return true if the focus freeze mode is successfully entered, false if there was an issue,
- * such as another freeze currently used.
- */
- @Override
- @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
- public boolean enterAudioFocusFreezeForTest(IBinder cb, int[] exemptedUids) {
- super.enterAudioFocusFreezeForTest_enforcePermission();
- Objects.requireNonNull(exemptedUids);
- Objects.requireNonNull(cb);
- return mMediaFocusControl.enterAudioFocusFreezeForTest(cb, exemptedUids);
- }
-
- /**
- * Test method to end preventing applications from requesting audio focus during a test.
- * @see AudioManager#exitAudioFocusFreezeForTest()
- * @param cb IBinder identifying the client of this method
- * @return true if the focus freeze mode is successfully exited, false if there was an issue,
- * such as the freeze already having ended, or not started.
- */
- @Override
- @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
- public boolean exitAudioFocusFreezeForTest(IBinder cb) {
- super.exitAudioFocusFreezeForTest_enforcePermission();
- Objects.requireNonNull(cb);
- return mMediaFocusControl.exitAudioFocusFreezeForTest(cb);
- }
-
/** only public for mocking/spying, do not call outside of AudioService */
@VisibleForTesting
public boolean hasAudioFocusUsers() {
@@ -10091,8 +10021,6 @@
}
/** see {@link AudioManager#getFadeOutDurationOnFocusLossMillis(AudioAttributes)} */
- @Override
- @EnforcePermission("QUERY_AUDIO_STATE")
public long getFadeOutDurationOnFocusLossMillis(AudioAttributes aa) {
if (!enforceQueryAudioStateForTest("fade out duration")) {
return 0;
diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java
index 010d5f4..88a4b05 100644
--- a/services/core/java/com/android/server/audio/FocusRequester.java
+++ b/services/core/java/com/android/server/audio/FocusRequester.java
@@ -43,7 +43,7 @@
public class FocusRequester {
// on purpose not using this classe's name, as it will only be used from MediaFocusControl
- private static final String TAG = "FocusRequester";
+ private static final String TAG = "MediaFocusControl";
private static final boolean DEBUG = false;
private AudioFocusDeathHandler mDeathHandler; // may be null
@@ -340,9 +340,6 @@
@GuardedBy("MediaFocusControl.mAudioFocusLock")
boolean handleFocusLossFromGain(int focusGain, final FocusRequester frWinner, boolean forceDuck)
{
- if (DEBUG) {
- Log.i(TAG, "handleFocusLossFromGain for " + mClientId + " gain:" + focusGain);
- }
final int focusLoss = focusLossForGainRequest(focusGain);
handleFocusLoss(focusLoss, frWinner, forceDuck);
return (focusLoss == AudioManager.AUDIOFOCUS_LOSS);
@@ -381,9 +378,6 @@
@GuardedBy("MediaFocusControl.mAudioFocusLock")
void handleFocusLoss(int focusLoss, @Nullable final FocusRequester frWinner, boolean forceDuck)
{
- if (DEBUG) {
- Log.i(TAG, "handleFocusLoss for " + mClientId + " loss:" + focusLoss);
- }
try {
if (focusLoss != mFocusLossReceived) {
mFocusLossReceived = focusLoss;
@@ -433,9 +427,6 @@
toAudioFocusInfo(), true /* wasDispatched */);
mFocusLossWasNotified = true;
fd.dispatchAudioFocusChange(mFocusLossReceived, mClientId);
- } else if (DEBUG) {
- Log.i(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived)
- + " to " + mClientId + " no IAudioFocusDispatcher");
}
}
} catch (android.os.RemoteException e) {
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index 65f6c9b..b218096 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -45,7 +45,6 @@
import java.io.PrintWriter;
import java.text.DateFormat;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
@@ -123,23 +122,6 @@
dumpMultiAudioFocus(pw);
}
- /**
- * Test method to return the duration of the fade out applied on the players of a focus loser
- * @return the fade out duration in ms
- */
- public long getFocusFadeOutDurationForTest() {
- return FadeOutManager.FADE_OUT_DURATION_MS;
- }
-
- /**
- * Test method to return the length of time after a fade out before the focus loser is unmuted
- * (and is faded back in).
- * @return the time gap after a fade out completion on focus loss, and fade in start in ms
- */
- public long getFocusUnmuteDelayAfterFadeOutForTest() {
- return FadeOutManager.DELAY_FADE_IN_OFFENDERS_MS;
- }
-
//=================================================================
// PlayerFocusEnforcer implementation
@Override
@@ -322,26 +304,17 @@
@GuardedBy("mAudioFocusLock")
private void propagateFocusLossFromGain_syncAf(int focusGain, final FocusRequester fr,
boolean forceDuck) {
- if (DEBUG) {
- Log.i(TAG, "propagateFocusLossFromGain_syncAf gain:" + focusGain);
- }
final List<String> clientsToRemove = new LinkedList<String>();
// going through the audio focus stack to signal new focus, traversing order doesn't
// matter as all entries respond to the same external focus gain
if (!mFocusStack.empty()) {
for (FocusRequester focusLoser : mFocusStack) {
- if (DEBUG) {
- Log.i(TAG, "propagateFocusLossFromGain_syncAf checking client:"
- + focusLoser.getClientId());
- }
final boolean isDefinitiveLoss =
focusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck);
if (isDefinitiveLoss) {
clientsToRemove.add(focusLoser.getClientId());
}
}
- } else if (DEBUG) {
- Log.i(TAG, "propagateFocusLossFromGain_syncAf empty stack");
}
if (mMultiAudioFocusEnabled && !mMultiAudioFocusList.isEmpty()) {
@@ -397,9 +370,6 @@
@GuardedBy("mAudioFocusLock")
private void removeFocusStackEntry(String clientToRemove, boolean signal,
boolean notifyFocusFollowers) {
- if (DEBUG) {
- Log.i(TAG, "removeFocusStackEntry client:" + clientToRemove);
- }
AudioFocusInfo abandonSource = null;
// is the current top of the focus stack abandoning focus? (because of request, not death)
if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))
@@ -1030,24 +1000,6 @@
}
synchronized(mAudioFocusLock) {
- // check whether a focus freeze is in place and filter
- if (isFocusFrozenForTest()) {
- int focusRequesterUid;
- if ((flags & AudioManager.AUDIOFOCUS_FLAG_TEST)
- == AudioManager.AUDIOFOCUS_FLAG_TEST) {
- focusRequesterUid = testUid;
- } else {
- focusRequesterUid = Binder.getCallingUid();
- }
- if (isFocusFrozenForTestForUid(focusRequesterUid)) {
- Log.i(TAG, "requestAudioFocus: focus frozen for test for uid:"
- + focusRequesterUid);
- return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
- }
- Log.i(TAG, "requestAudioFocus: focus frozen for test but uid:" + focusRequesterUid
- + " is exempt");
- }
-
if (mFocusStack.size() > MAX_STACK_SIZE) {
Log.e(TAG, "Max AudioFocus stack size reached, failing requestAudioFocus()");
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
@@ -1239,110 +1191,6 @@
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
- /**
- * Reference to the caller of {@link #enterAudioFocusFreezeForTest(IBinder, int[])}
- * Will be null when there is no focus freeze for test
- */
- @GuardedBy("mAudioFocusLock")
- @Nullable
- private IBinder mFocusFreezerForTest = null;
-
- /**
- * The death handler for {@link #mFocusFreezerForTest}
- * Will be null when there is no focus freeze for test
- */
- @GuardedBy("mAudioFocusLock")
- @Nullable
- private IBinder.DeathRecipient mFocusFreezerDeathHandler = null;
-
- /**
- * Array of UIDs exempt from focus freeze when focus is frozen for test, null during normal
- * operations.
- * Will be null when there is no focus freeze for test
- */
- @GuardedBy("mAudioFocusLock")
- @Nullable
- private int[] mFocusFreezeExemptUids = null;
-
- @GuardedBy("mAudioFocusLock")
- private boolean isFocusFrozenForTest() {
- return (mFocusFreezerForTest != null);
- }
-
- /**
- * Checks if the given UID can request focus when a focus freeze is in place for a test.
- * Focus can be requested if focus is not frozen or if it's frozen but the UID is exempt.
- * @param uidToCheck
- * @return true if that UID is barred from requesting focus, false if its focus request
- * can proceed being processed
- */
- @GuardedBy("mAudioFocusLock")
- private boolean isFocusFrozenForTestForUid(int uidToCheck) {
- if (isFocusFrozenForTest()) {
- return false;
- }
- // check the list of exempts (array is not null because we're in a freeze for test
- for (int uid : mFocusFreezeExemptUids) {
- if (uid == uidToCheck) {
- return false;
- }
- }
- // uid was not found in the exempt list, its focus request is denied
- return true;
- }
-
- protected boolean enterAudioFocusFreezeForTest(
- @NonNull IBinder cb, @NonNull int[] exemptedUids) {
- Log.i(TAG, "enterAudioFocusFreezeForTest UIDs exempt:" + Arrays.toString(exemptedUids));
- synchronized (mAudioFocusLock) {
- if (mFocusFreezerForTest != null) {
- Log.e(TAG, "Error enterAudioFocusFreezeForTest: focus already frozen");
- return false;
- }
- // new focus freeze, register death handler
- try {
- mFocusFreezerDeathHandler = new IBinder.DeathRecipient() {
- @Override
- public void binderDied() {
- Log.i(TAG, "Audio focus freezer died, exiting focus freeze for test");
- releaseFocusFreeze();
- }
- };
- cb.linkToDeath(mFocusFreezerDeathHandler, 0);
- mFocusFreezerForTest = cb;
- mFocusFreezeExemptUids = exemptedUids.clone();
- } catch (RemoteException e) {
- // client has already died!
- mFocusFreezerForTest = null;
- mFocusFreezeExemptUids = null;
- return false;
- }
- }
- return true;
- }
-
- protected boolean exitAudioFocusFreezeForTest(@NonNull IBinder cb) {
- synchronized (mAudioFocusLock) {
- if (mFocusFreezerForTest != cb) {
- Log.e(TAG, "Error exitAudioFocusFreezeForTest: "
- + ((mFocusFreezerForTest == null)
- ? "call to exit while not frozen"
- : "call to exit not coming from freeze owner"));
- return false;
- }
- mFocusFreezerForTest.unlinkToDeath(mFocusFreezerDeathHandler, 0);
- releaseFocusFreeze();
- }
- return true;
- }
-
- private void releaseFocusFreeze() {
- synchronized (mAudioFocusLock) {
- mFocusFreezerDeathHandler = null;
- mFocusFreezeExemptUids = null;
- mFocusFreezerForTest = null;
- }
- }
protected void unregisterAudioFocusClient(String clientId) {
synchronized(mAudioFocusLock) {
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 54fa6fb..23a0782 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -1208,17 +1208,6 @@
}
}
- protected @NonNull List<Integer> getFocusDuckedUids() {
- final ArrayList<Integer> duckedUids;
- synchronized (mPlayerLock) {
- duckedUids = new ArrayList(mDuckingManager.mDuckers.keySet());
- }
- if (DEBUG) {
- Log.i(TAG, "current ducked UIDs: " + duckedUids);
- }
- return duckedUids;
- }
-
//=================================================================
// For logging
private static final class PlayerEvent extends EventLogger.Event {
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index b5d5cbe..de4979a 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -61,6 +61,7 @@
private static final String PEOPLE_HELPER = "people";
private static final String APP_LOCALES_HELPER = "app_locales";
private static final String APP_GENDER_HELPER = "app_gender";
+ private static final String COMPANION_HELPER = "companion";
// These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME
// are also used in the full-backup file format, so must not change unless steps are
@@ -95,7 +96,8 @@
PERMISSION_HELPER,
NOTIFICATION_HELPER,
SYNC_SETTINGS_HELPER,
- APP_LOCALES_HELPER);
+ APP_LOCALES_HELPER,
+ COMPANION_HELPER);
/** Helpers that are enabled for full, non-system users. */
private static final Set<String> sEligibleHelpersForNonSystemUser =
@@ -132,6 +134,7 @@
addHelperIfEligibleForUser(APP_LOCALES_HELPER, new AppSpecificLocalesBackupHelper(mUserId));
addHelperIfEligibleForUser(APP_GENDER_HELPER,
new AppGrammaticalGenderBackupHelper(mUserId));
+ addHelperIfEligibleForUser(COMPANION_HELPER, new CompanionBackupHelper(mUserId));
}
@Override
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index da51569..1c1b69b 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -134,6 +134,13 @@
}
/**
+ * Helper methods to create builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
* A DisplayBrightnessState's builder class.
*/
public static class Builder {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 51a0ef6..c131226 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 @@
* <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 @@
* <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>
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 58f4d08..fe445a6 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1547,6 +1547,7 @@
if (displayId != Display.INVALID_DISPLAY && virtualDevice != null && dwpc != null) {
mDisplayWindowPolicyControllers.put(
displayId, Pair.create(virtualDevice, dwpc));
+ Slog.d(TAG, "Virtual Display: successfully created virtual display");
}
}
@@ -1593,19 +1594,25 @@
if (!getProjectionService().setContentRecordingSession(session, projection)) {
// Unable to start mirroring, so release VirtualDisplay. Projection service
// handles stopping the projection.
+ Slog.w(TAG, "Content Recording: failed to start mirroring - "
+ + "releasing virtual display " + displayId);
releaseVirtualDisplayInternal(callback.asBinder());
return Display.INVALID_DISPLAY;
} else if (projection != null) {
// Indicate that this projection has been used to record, and can't be used
// again.
+ Slog.d(TAG, "Content Recording: notifying MediaProjection of successful"
+ + " VirtualDisplay creation.");
projection.notifyVirtualDisplayCreated(displayId);
}
} catch (RemoteException e) {
Slog.e(TAG, "Unable to tell MediaProjectionManagerService to set the "
+ "content recording session", e);
+ return displayId;
}
+ Slog.d(TAG, "Virtual Display: successfully set up virtual display "
+ + displayId);
}
-
return displayId;
} finally {
Binder.restoreCallingIdentity(secondToken);
@@ -1629,10 +1636,13 @@
return -1;
}
+
+ Slog.d(TAG, "Virtual Display: creating DisplayDevice with VirtualDisplayAdapter");
DisplayDevice device = mVirtualDisplayAdapter.createVirtualDisplayLocked(
callback, projection, callingUid, packageName, surface, flags,
virtualDisplayConfig);
if (device == null) {
+ Slog.w(TAG, "Virtual Display: VirtualDisplayAdapter failed to create DisplayDevice");
return -1;
}
@@ -1710,6 +1720,7 @@
DisplayDevice device =
mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken);
+ Slog.d(TAG, "Virtual Display: Display Device released");
if (device != null) {
// TODO: multi-display - handle virtual displays the same as other display adapters.
mDisplayDeviceRepo.onDisplayDeviceEvent(device,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 5213d31..25e704b 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -349,7 +349,7 @@
private boolean mDozing;
private boolean mAppliedDimming;
- private boolean mAppliedLowPower;
+
private boolean mAppliedThrottling;
// Reason for which the brightness was last changed. See {@link BrightnessReason} for more
@@ -1467,24 +1467,13 @@
slowChange = false;
mAppliedDimming = false;
}
- // If low power mode is enabled, scale brightness by screenLowPowerBrightnessFactor
- // as long as it is above the minimum threshold.
- if (mPowerRequest.lowPowerMode) {
- if (brightnessState > PowerManager.BRIGHTNESS_MIN) {
- final float brightnessFactor =
- Math.min(mPowerRequest.screenLowPowerBrightnessFactor, 1);
- final float lowPowerBrightnessFloat = (brightnessState * brightnessFactor);
- brightnessState = Math.max(lowPowerBrightnessFloat, PowerManager.BRIGHTNESS_MIN);
- mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_LOW_POWER);
- }
- if (!mAppliedLowPower) {
- slowChange = false;
- }
- mAppliedLowPower = true;
- } else if (mAppliedLowPower) {
- slowChange = false;
- mAppliedLowPower = false;
- }
+
+ DisplayBrightnessState clampedState = mBrightnessClamperController.clamp(mPowerRequest,
+ brightnessState, slowChange);
+
+ brightnessState = clampedState.getBrightness();
+ slowChange = clampedState.isSlowChange();
+ mBrightnessReasonTemp.addModifier(clampedState.getBrightnessReason().getModifier());
// The current brightness to use has been calculated at this point, and HbmController should
// be notified so that it can accurately calculate HDR or HBM levels. We specifically do it
@@ -1542,8 +1531,6 @@
// allowed range.
float animateValue = clampScreenBrightness(brightnessState);
- animateValue = mBrightnessClamperController.clamp(animateValue);
-
// If there are any HDR layers on the screen, we have a special brightness value that we
// use instead. We still preserve the calculated brightness for Standard Dynamic Range
// (SDR) layers, but the main brightness value will be the one for HDR.
@@ -2411,7 +2398,6 @@
pw.println(" mPowerRequest=" + mPowerRequest);
pw.println(" mBrightnessReason=" + mBrightnessReason);
pw.println(" mAppliedDimming=" + mAppliedDimming);
- pw.println(" mAppliedLowPower=" + mAppliedLowPower);
pw.println(" mAppliedThrottling=" + mAppliedThrottling);
pw.println(" mDozing=" + mDozing);
pw.println(" mSkipRampState=" + skipRampStateToString(mSkipRampState));
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 4edc8bc..9c271ff5 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 @@
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/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 6191861..6936112 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -147,9 +147,12 @@
try {
if (projection != null) {
projection.registerCallback(mediaProjectionCallback);
+ Slog.d(TAG, "Virtual Display: registered media projection callback for new "
+ + "VirtualDisplayDevice");
}
appToken.linkToDeath(device, 0);
} catch (RemoteException ex) {
+ Slog.e(TAG, "Virtual Display: error while setting up VirtualDisplayDevice", ex);
mVirtualDisplayDevices.remove(appToken);
device.destroyLocked(false);
return null;
@@ -445,6 +448,7 @@
}
public void stopLocked() {
+ Slog.d(TAG, "Virtual Display: stopping device " + mName);
setSurfaceLocked(null);
mStopped = true;
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
index 9345a3d..54a280f 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
@@ -21,6 +21,9 @@
import java.io.PrintWriter;
+/**
+ * Provides max allowed brightness
+ */
abstract class BrightnessClamper<T> {
protected float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index d0f28c3..f19d00b 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.display.DisplayManagerInternal;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.PowerManager;
@@ -28,6 +29,7 @@
import android.util.IndentingPrintWriter;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.DisplayDeviceConfig;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
import com.android.server.display.feature.DeviceConfigParameterProvider;
@@ -42,7 +44,7 @@
*/
public class BrightnessClamperController {
- private static final boolean ENABLED = false;
+ private static final boolean THERMAL_ENABLED = false;
private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;
private final Handler mHandler;
@@ -50,6 +52,8 @@
private final Executor mExecutor;
private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers = new ArrayList<>();
+
+ private final List<BrightnessModifier> mModifiers = new ArrayList<>();
private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
@@ -77,11 +81,12 @@
}
};
- if (ENABLED) {
+ if (THERMAL_ENABLED) {
mClampers.add(
new BrightnessThermalClamper(handler, clamperChangeListenerInternal, data));
- start();
}
+ mModifiers.add(new BrightnessLowPowerModeModifier());
+ start();
}
/**
@@ -95,8 +100,18 @@
* Applies clamping
* Called in DisplayControllerHandler
*/
- public float clamp(float value) {
- return Math.min(value, mBrightnessCap);
+ public DisplayBrightnessState clamp(DisplayManagerInternal.DisplayPowerRequest request,
+ float brightnessValue, boolean slowChange) {
+ float cappedBrightness = Math.min(brightnessValue, mBrightnessCap);
+
+ DisplayBrightnessState.Builder builder = DisplayBrightnessState.builder();
+ builder.setIsSlowChange(slowChange);
+ builder.setBrightness(cappedBrightness);
+
+ for (int i = 0; i < mModifiers.size(); i++) {
+ mModifiers.get(i).apply(request, builder);
+ }
+ return builder.build();
}
/**
@@ -108,6 +123,7 @@
writer.println(" mClamperType: " + mClamperType);
IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " ");
mClampers.forEach(clamper -> clamper.dump(ipw));
+ mModifiers.forEach(modifier -> modifier.dump(ipw));
}
/**
@@ -144,8 +160,10 @@
}
private void start() {
- mDeviceConfigParameterProvider.addOnPropertiesChangedListener(
- mExecutor, mOnPropertiesChangedListener);
+ if (!mClampers.isEmpty()) {
+ mDeviceConfigParameterProvider.addOnPropertiesChangedListener(
+ mExecutor, mOnPropertiesChangedListener);
+ }
}
/**
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifier.java
new file mode 100644
index 0000000..f48ad2f
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifier.java
@@ -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.server.display.brightness.clamper;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+import java.io.PrintWriter;
+
+class BrightnessLowPowerModeModifier implements BrightnessModifier {
+
+ private boolean mAppliedLowPower = false;
+
+ @Override
+ public void apply(DisplayManagerInternal.DisplayPowerRequest request,
+ DisplayBrightnessState.Builder stateBuilder) {
+ // If low power mode is enabled, scale brightness by screenLowPowerBrightnessFactor
+ // as long as it is above the minimum threshold.
+ if (request.lowPowerMode) {
+ float value = stateBuilder.getBrightness();
+ if (value > PowerManager.BRIGHTNESS_MIN) {
+ final float brightnessFactor =
+ Math.min(request.screenLowPowerBrightnessFactor, 1);
+ final float lowPowerBrightnessFloat = Math.max((value * brightnessFactor),
+ PowerManager.BRIGHTNESS_MIN);
+ stateBuilder.setBrightness(lowPowerBrightnessFloat);
+ stateBuilder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_LOW_POWER);
+ }
+ if (!mAppliedLowPower) {
+ stateBuilder.setIsSlowChange(false);
+ }
+ mAppliedLowPower = true;
+ } else if (mAppliedLowPower) {
+ stateBuilder.setIsSlowChange(false);
+ mAppliedLowPower = false;
+ }
+ }
+
+ @Override
+ public void dump(PrintWriter pw) {
+ pw.println("BrightnessLowPowerModeModifier:");
+ pw.println(" mAppliedLowPower=" + mAppliedLowPower);
+ }
+}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
new file mode 100644
index 0000000..3a33df6
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.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 com.android.server.display.brightness.clamper;
+
+import android.hardware.display.DisplayManagerInternal;
+
+import com.android.server.display.DisplayBrightnessState;
+
+import java.io.PrintWriter;
+
+/**
+ * Modifies current brightness based on request
+ */
+interface BrightnessModifier {
+
+ void apply(DisplayManagerInternal.DisplayPowerRequest request,
+ DisplayBrightnessState.Builder builder);
+
+ void dump(PrintWriter pw);
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java b/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
index 7ae7d5c..53c0217 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
@@ -222,6 +222,21 @@
);
}
+ /**
+ * Writes a HdmiSoundbarModeStatusReported atom representing a Dynamic soundbar mode status
+ * change.
+ * @param isSupported Whether the hardware supports ARC.
+ * @param isEnabled Whether DSM is enabled.
+ * @param enumLogReason The event that triggered the log.
+ */
+ public void dsmStatusChanged(boolean isSupported, boolean isEnabled, int enumLogReason) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.HDMI_SOUNDBAR_MODE_STATUS_REPORTED,
+ isSupported,
+ isEnabled,
+ enumLogReason);
+ }
+
private int earcStateToEnum(int earcState) {
switch (earcState) {
case HDMI_EARC_STATUS_IDLE:
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index dd45307..5abb2b5 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -694,13 +694,13 @@
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED);
mSoundbarModeFeatureFlagEnabled = mDeviceConfig.getBoolean(
- Constants.DEVICE_CONFIG_FEATURE_FLAG_SOUNDBAR_MODE, false);
+ Constants.DEVICE_CONFIG_FEATURE_FLAG_SOUNDBAR_MODE, true);
mEarcTxFeatureFlagEnabled = mDeviceConfig.getBoolean(
- Constants.DEVICE_CONFIG_FEATURE_FLAG_ENABLE_EARC_TX, false);
+ Constants.DEVICE_CONFIG_FEATURE_FLAG_ENABLE_EARC_TX, true);
mTransitionFromArcToEarcTxEnabled = mDeviceConfig.getBoolean(
- Constants.DEVICE_CONFIG_FEATURE_FLAG_TRANSITION_ARC_TO_EARC_TX, false);
+ Constants.DEVICE_CONFIG_FEATURE_FLAG_TRANSITION_ARC_TO_EARC_TX, true);
mNumericSoundbarVolumeUiOnTvFeatureFlagEnabled = mDeviceConfig.getBoolean(
- Constants.DEVICE_CONFIG_FEATURE_FLAG_TV_NUMERIC_SOUNDBAR_VOLUME_UI, false);
+ Constants.DEVICE_CONFIG_FEATURE_FLAG_TV_NUMERIC_SOUNDBAR_VOLUME_UI, true);
synchronized (mLock) {
mEarcEnabled = (mHdmiCecConfig.getIntValue(
@@ -857,7 +857,7 @@
public void onPropertiesChanged(DeviceConfig.Properties properties) {
mEarcTxFeatureFlagEnabled = properties.getBoolean(
Constants.DEVICE_CONFIG_FEATURE_FLAG_ENABLE_EARC_TX,
- false);
+ true);
boolean earcEnabledSetting = mHdmiCecConfig.getIntValue(
HdmiControlManager.SETTING_NAME_EARC_ENABLED)
== EARC_FEATURE_ENABLED;
@@ -891,7 +891,7 @@
public void onPropertiesChanged(DeviceConfig.Properties properties) {
mSoundbarModeFeatureFlagEnabled = properties.getBoolean(
Constants.DEVICE_CONFIG_FEATURE_FLAG_SOUNDBAR_MODE,
- false);
+ true);
boolean soundbarModeSetting = mHdmiCecConfig.getIntValue(
HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE)
== SOUNDBAR_MODE_ENABLED;
@@ -917,7 +917,7 @@
public void onPropertiesChanged(DeviceConfig.Properties properties) {
mTransitionFromArcToEarcTxEnabled = properties.getBoolean(
Constants.DEVICE_CONFIG_FEATURE_FLAG_TRANSITION_ARC_TO_EARC_TX,
- false);
+ true);
}
});
@@ -927,7 +927,7 @@
public void onPropertiesChanged(DeviceConfig.Properties properties) {
mNumericSoundbarVolumeUiOnTvFeatureFlagEnabled = properties.getBoolean(
Constants.DEVICE_CONFIG_FEATURE_FLAG_TV_NUMERIC_SOUNDBAR_VOLUME_UI,
- false);
+ true);
checkAndUpdateAbsoluteVolumeBehavior();
}
});
@@ -1064,13 +1064,18 @@
*/
@VisibleForTesting
public void setSoundbarMode(final int settingValue) {
+ boolean isArcSupported = isArcSupported();
HdmiCecLocalDevicePlayback playback = playback();
HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
+ getAtomWriter().dsmStatusChanged(isArcSupported,
+ settingValue == SOUNDBAR_MODE_ENABLED,
+ HdmiStatsEnums.LOG_REASON_DSM_SETTING_TOGGLED);
+
if (playback == null) {
Slog.w(TAG, "Device type not compatible to change soundbar mode.");
return;
}
- if (!SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) {
+ if (!isArcSupported) {
Slog.w(TAG, "Device type doesn't support ARC.");
return;
}
@@ -1269,11 +1274,8 @@
@ServiceThreadOnly
private List<Integer> getCecLocalDeviceTypes() {
ArrayList<Integer> allLocalDeviceTypes = new ArrayList<>(mCecLocalDevices);
- if (mHdmiCecConfig.getIntValue(HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE)
- == SOUNDBAR_MODE_ENABLED
- && !allLocalDeviceTypes.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)
- && SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)
- && mSoundbarModeFeatureFlagEnabled) {
+ if (isDsmEnabled() && !allLocalDeviceTypes.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)
+ && isArcSupported() && mSoundbarModeFeatureFlagEnabled) {
allLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
}
return allLocalDeviceTypes;
@@ -3589,6 +3591,16 @@
}
}
+ private boolean isDsmEnabled() {
+ return mHdmiCecConfig.getIntValue(HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE)
+ == SOUNDBAR_MODE_ENABLED;
+ }
+
+ @VisibleForTesting
+ protected boolean isArcSupported() {
+ return SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true);
+ }
+
@ServiceThreadOnly
int getPowerStatus() {
assertRunOnServiceThread();
@@ -3705,6 +3717,9 @@
int earcStatus = getEarcStatus();
getAtomWriter().earcStatusChanged(isEarcSupported(), isEarcEnabled(),
earcStatus, earcStatus, HdmiStatsEnums.LOG_REASON_WAKE);
+ } else if (isPlaybackDevice()) {
+ getAtomWriter().dsmStatusChanged(isArcSupported(), isDsmEnabled(),
+ HdmiStatsEnums.LOG_REASON_DSM_WAKE);
}
// TODO: Initialize MHL local devices.
}
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 5bdf263..a5162c0 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 @@
@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 @@
@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 @@
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/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
index 4b30ae5..08e5977 100644
--- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -21,10 +21,10 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.ComponentName;
import android.content.Intent;
import android.hardware.input.KeyboardLayout;
import android.icu.util.ULocale;
+import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
@@ -41,9 +41,7 @@
import java.lang.annotation.Retention;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -59,6 +57,7 @@
@Retention(SOURCE)
@IntDef(prefix = {"LAYOUT_SELECTION_CRITERIA_"}, value = {
+ LAYOUT_SELECTION_CRITERIA_UNSPECIFIED,
LAYOUT_SELECTION_CRITERIA_USER,
LAYOUT_SELECTION_CRITERIA_DEVICE,
LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
@@ -67,23 +66,26 @@
public @interface LayoutSelectionCriteria {
}
+ /** Unspecified layout selection criteria */
+ public static final int LAYOUT_SELECTION_CRITERIA_UNSPECIFIED = 0;
+
/** Manual selection by user */
- public static final int LAYOUT_SELECTION_CRITERIA_USER = 0;
+ public static final int LAYOUT_SELECTION_CRITERIA_USER = 1;
/** Auto-detection based on device provided language tag and layout type */
- public static final int LAYOUT_SELECTION_CRITERIA_DEVICE = 1;
+ public static final int LAYOUT_SELECTION_CRITERIA_DEVICE = 2;
/** Auto-detection based on IME provided language tag and layout type */
- public static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD = 2;
+ public static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD = 3;
/** Default selection */
- public static final int LAYOUT_SELECTION_CRITERIA_DEFAULT = 3;
+ public static final int LAYOUT_SELECTION_CRITERIA_DEFAULT = 4;
@VisibleForTesting
- static final String DEFAULT_LAYOUT = "Default";
+ static final String DEFAULT_LAYOUT_NAME = "Default";
@VisibleForTesting
- static final String DEFAULT_LANGUAGE_TAG = "None";
+ public static final String DEFAULT_LANGUAGE_TAG = "None";
public enum KeyboardLogEvent {
UNSPECIFIED(
@@ -536,23 +538,23 @@
mLayoutSelectionCriteriaList.get(i);
InputMethodSubtype imeSubtype = mImeSubtypeList.get(i);
String keyboardLanguageTag = mInputDevice.getKeyboardLanguageTag();
- keyboardLanguageTag = keyboardLanguageTag == null ? DEFAULT_LANGUAGE_TAG
- : keyboardLanguageTag;
+ keyboardLanguageTag = TextUtils.isEmpty(keyboardLanguageTag)
+ ? DEFAULT_LANGUAGE_TAG : keyboardLanguageTag;
int keyboardLayoutType = KeyboardLayout.LayoutType.getLayoutTypeEnumValue(
mInputDevice.getKeyboardLayoutType());
ULocale pkLocale = imeSubtype.getPhysicalKeyboardHintLanguageTag();
- String canonicalizedLanguageTag =
- imeSubtype.getCanonicalizedLanguageTag().equals("")
- ? DEFAULT_LANGUAGE_TAG : imeSubtype.getCanonicalizedLanguageTag();
String imeLanguageTag = pkLocale != null ? pkLocale.toLanguageTag()
- : canonicalizedLanguageTag;
+ : imeSubtype.getCanonicalizedLanguageTag();
+ imeLanguageTag = TextUtils.isEmpty(imeLanguageTag) ? DEFAULT_LANGUAGE_TAG
+ : imeLanguageTag;
int imeLayoutType = KeyboardLayout.LayoutType.getLayoutTypeEnumValue(
imeSubtype.getPhysicalKeyboardHintLayoutType());
// Sanitize null values
String keyboardLayoutName =
- selectedLayout == null ? DEFAULT_LAYOUT : selectedLayout.getLabel();
+ selectedLayout == null ? DEFAULT_LAYOUT_NAME
+ : selectedLayout.getLabel();
configurationList.add(
new LayoutConfiguration(keyboardLayoutType, keyboardLanguageTag,
@@ -601,6 +603,8 @@
private static String getStringForSelectionCriteria(
@LayoutSelectionCriteria int layoutSelectionCriteria) {
switch (layoutSelectionCriteria) {
+ case LAYOUT_SELECTION_CRITERIA_UNSPECIFIED:
+ return "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED";
case LAYOUT_SELECTION_CRITERIA_USER:
return "LAYOUT_SELECTION_CRITERIA_USER";
case LAYOUT_SELECTION_CRITERIA_DEVICE:
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 7c3ef19..e7bd68e 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -225,6 +225,7 @@
mMediaRouter.rebindAsUser(to.getUserIdentifier());
synchronized (mLock) {
if (mProjectionGrant != null) {
+ Slog.d(TAG, "Content Recording: Stopped MediaProjection due to user switching");
mProjectionGrant.stop();
}
}
@@ -260,6 +261,8 @@
}
synchronized (mLock) {
+ Slog.d(TAG,
+ "Content Recording: Stopped MediaProjection due to foreground service change");
if (mProjectionGrant != null) {
mProjectionGrant.stop();
}
@@ -268,6 +271,8 @@
private void startProjectionLocked(final MediaProjection projection) {
if (mProjectionGrant != null) {
+ Slog.d(TAG, "Content Recording: Stopped MediaProjection to start new "
+ + "incoming projection");
mProjectionGrant.stop();
}
if (mMediaRouteInfo != null) {
@@ -279,6 +284,8 @@
}
private void stopProjectionLocked(final MediaProjection projection) {
+ Slog.d(TAG, "Content Recording: Stopped active MediaProjection and "
+ + "dispatching stop to callbacks");
mProjectionToken = null;
mProjectionGrant = null;
dispatchStop(projection);
@@ -351,6 +358,13 @@
if (!setSessionSucceeded) {
// Unable to start mirroring, so tear down this projection.
if (mProjectionGrant != null) {
+ String projectionType = incomingSession != null
+ ? ContentRecordingSession.recordContentToString(
+ incomingSession.getContentToRecord()) : "none";
+ Slog.w(TAG, "Content Recording: Stopped MediaProjection due to failing to set "
+ + "ContentRecordingSession - id= "
+ + mProjectionGrant.getVirtualDisplayId() + "type=" + projectionType);
+
mProjectionGrant.stop();
}
return false;
@@ -464,6 +478,9 @@
// The grant may now be null if setting the session failed.
if (mProjectionGrant != null) {
// Always stop the projection.
+ Slog.w(TAG, "Content Recording: Stopped MediaProjection due to user "
+ + "consent result of CANCEL - "
+ + "id= " + mProjectionGrant.getVirtualDisplayId());
mProjectionGrant.stop();
}
break;
@@ -666,6 +683,7 @@
try {
synchronized (mLock) {
if (mProjectionGrant != null) {
+ Slog.d(TAG, "Content Recording: Stopping active projection");
mProjectionGrant.stop();
}
}
@@ -864,6 +882,10 @@
MEDIA_PROJECTION_TOKEN_EVENT_CREATED);
}
+ int getVirtualDisplayId() {
+ return mVirtualDisplayId;
+ }
+
@Override // Binder call
public boolean canProjectVideo() {
return mType == MediaProjectionManager.TYPE_MIRRORING ||
@@ -937,12 +959,11 @@
registerCallback(mCallback);
try {
mToken = callback.asBinder();
- mDeathEater = new IBinder.DeathRecipient() {
- @Override
- public void binderDied() {
- mCallbackDelegate.remove(callback);
- stop();
- }
+ mDeathEater = () -> {
+ Slog.d(TAG, "Content Recording: MediaProjection stopped by Binder death - "
+ + "id= " + mVirtualDisplayId);
+ mCallbackDelegate.remove(callback);
+ stop();
};
mToken.linkToDeath(mDeathEater, 0);
} catch (RemoteException e) {
@@ -1012,6 +1033,9 @@
Binder.restoreCallingIdentity(token);
}
}
+ Slog.d(TAG, "Content Recording: handling stopping this projection token"
+ + " createTime= " + mCreateTimeMs
+ + " countStarts= " + mCountStarts);
stopProjectionLocked(this);
mToken.unlinkToDeath(mDeathEater, 0);
mToken = null;
@@ -1125,6 +1149,8 @@
if ((type & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
mMediaRouteInfo = info;
if (mProjectionGrant != null) {
+ Slog.d(TAG, "Content Recording: Stopped MediaProjection due to "
+ + "route type of REMOTE_DISPLAY not selected");
mProjectionGrant.stop();
}
}
@@ -1296,7 +1322,7 @@
try {
mCallback.onStart(mInfo);
} catch (RemoteException e) {
- Slog.w(TAG, "Failed to notify media projection has stopped", e);
+ Slog.w(TAG, "Failed to notify media projection has started", e);
}
}
}
@@ -1370,7 +1396,8 @@
return "TYPE_MIRRORING";
case MediaProjectionManager.TYPE_PRESENTATION:
return "TYPE_PRESENTATION";
+ default:
+ return Integer.toString(type);
}
- return Integer.toString(type);
}
}
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index bfc4f53..2da1a68 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -441,6 +441,58 @@
return info == null ? null : (IConditionProvider) info.service;
}
+ void resetDefaultFromConfig() {
+ synchronized (mDefaultsLock) {
+ mDefaultComponents.clear();
+ mDefaultPackages.clear();
+ }
+ loadDefaultsFromConfig();
+ }
+
+ boolean removeDefaultFromConfig(int userId) {
+ boolean removed = false;
+ String defaultDndDenied = mContext.getResources().getString(
+ R.string.config_defaultDndDeniedPackages);
+ if (defaultDndDenied != null) {
+ String[] dnds = defaultDndDenied.split(ManagedServices.ENABLED_SERVICES_SEPARATOR);
+ for (int i = 0; i < dnds.length; i++) {
+ if (TextUtils.isEmpty(dnds[i])) {
+ continue;
+ }
+ removed |= removePackageFromApprovedLists(userId, dnds[i], "remove from config");
+ }
+ }
+ return removed;
+ }
+
+ private boolean removePackageFromApprovedLists(int userId, String pkg, String reason) {
+ boolean removed = false;
+ synchronized (mApproved) {
+ final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.get(
+ userId);
+ if (approvedByType != null) {
+ int approvedByTypeSize = approvedByType.size();
+ for (int i = 0; i < approvedByTypeSize; i++) {
+ final ArraySet<String> approved = approvedByType.valueAt(i);
+ int approvedSize = approved.size();
+ for (int j = approvedSize - 1; j >= 0; j--) {
+ final String packageOrComponent = approved.valueAt(j);
+ final String packageName = getPackageName(packageOrComponent);
+ if (TextUtils.equals(pkg, packageName)) {
+ approved.removeAt(j);
+ removed = true;
+ if (DEBUG) {
+ Slog.v(TAG, "Removing " + packageOrComponent
+ + " from approved list; " + reason);
+ }
+ }
+ }
+ }
+ }
+ }
+ return removed;
+ }
+
private static class ConditionRecord {
public final Uri id;
public final ComponentName component;
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 8992878..0e76dfb 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -25,6 +25,7 @@
import android.content.IntentFilter;
import android.telecom.TelecomManager;
+import com.android.internal.os.BackgroundThread;
import com.android.internal.util.NotificationMessagingUtil;
import java.util.Comparator;
@@ -33,18 +34,23 @@
/**
* Sorts notifications individually into attention-relevant order.
*/
-public class NotificationComparator
- implements Comparator<NotificationRecord> {
+class NotificationComparator implements Comparator<NotificationRecord> {
private final Context mContext;
private final NotificationMessagingUtil mMessagingUtil;
private String mDefaultPhoneApp;
+ /**
+ * Lock that must be held during a sort() call that uses this {@link Comparator}, AND to make
+ * any changes to the state of this object that could affect the results of {@link #compare}.
+ */
+ public final Object mStateLock = new Object();
+
public NotificationComparator(Context context) {
mContext = context;
mContext.registerReceiver(mPhoneAppBroadcastReceiver,
new IntentFilter(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED));
- mMessagingUtil = new NotificationMessagingUtil(mContext);
+ mMessagingUtil = new NotificationMessagingUtil(mContext, mStateLock);
}
@Override
@@ -212,8 +218,13 @@
private final BroadcastReceiver mPhoneAppBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- mDefaultPhoneApp =
- intent.getStringExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME);
+ BackgroundThread.getExecutor().execute(() -> {
+ synchronized (mStateLock) {
+ mDefaultPhoneApp =
+ intent.getStringExtra(
+ TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME);
+ }
+ });
}
};
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
old mode 100644
new mode 100755
index 009e097..e782ea9
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -832,12 +832,32 @@
allowNotificationListener(userId, cn);
}
+ allowDndPackages(userId);
+
+ setDefaultAssistantForUser(userId);
+ }
+
+ @VisibleForTesting
+ void allowDndPackages(int userId) {
ArraySet<String> defaultDnds = mConditionProviders.getDefaultPackages();
for (int i = 0; i < defaultDnds.size(); i++) {
allowDndPackage(userId, defaultDnds.valueAt(i));
}
+ if (!isDNDMigrationDone(userId)) {
+ setDNDMigrationDone(userId);
+ }
+ }
- setDefaultAssistantForUser(userId);
+ @VisibleForTesting
+ boolean isDNDMigrationDone(int userId) {
+ return Settings.Secure.getIntForUser(getContext().getContentResolver(),
+ Settings.Secure.DND_CONFIGS_MIGRATED, 0, userId) == 1;
+ }
+
+ @VisibleForTesting
+ void setDNDMigrationDone(int userId) {
+ Settings.Secure.putIntForUser(getContext().getContentResolver(),
+ Settings.Secure.DND_CONFIGS_MIGRATED, 1, userId);
}
protected void migrateDefaultNAS() {
@@ -1024,6 +1044,24 @@
}
@VisibleForTesting
+ void resetDefaultDndIfNecessary() {
+ boolean removed = false;
+ final List<UserInfo> activeUsers = mUm.getAliveUsers();
+ for (UserInfo userInfo : activeUsers) {
+ int userId = userInfo.getUserHandle().getIdentifier();
+ if (isDNDMigrationDone(userId)) {
+ continue;
+ }
+ removed |= mConditionProviders.removeDefaultFromConfig(userId);
+ mConditionProviders.resetDefaultFromConfig();
+ allowDndPackages(userId);
+ }
+ if (removed) {
+ handleSavePolicyFile();
+ }
+ }
+
+ @VisibleForTesting
protected void loadPolicyFile() {
if (DBG) Slog.d(TAG, "loadPolicyFile");
synchronized (mPolicyFile) {
@@ -1031,6 +1069,13 @@
try {
infile = mPolicyFile.openRead();
readPolicyXml(infile, false /*forRestore*/, UserHandle.USER_ALL);
+
+ // We re-load the default dnd packages to allow the newly added and denined.
+ final boolean isWatch = mPackageManagerClient.hasSystemFeature(
+ PackageManager.FEATURE_WATCH);
+ if (isWatch) {
+ resetDefaultDndIfNecessary();
+ }
} catch (FileNotFoundException e) {
// No data yet
// Load default managed services approvals
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 1bb1092..3f799dc 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -2608,7 +2608,12 @@
for (NotificationChannel channel : r.channels.values()) {
if (!channel.isSoundRestored()) {
Uri uri = channel.getSound();
- Uri restoredUri = channel.restoreSoundUri(mContext, uri, true);
+ Uri restoredUri =
+ channel.restoreSoundUri(
+ mContext,
+ uri,
+ true,
+ channel.getAudioAttributes().getUsage());
if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(
restoredUri)) {
Log.w(TAG,
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index b4347e1..773d10b 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -103,8 +103,11 @@
notificationList.get(i).setGlobalSortKey(null);
}
- // rank each record individually
- Collections.sort(notificationList, mPreliminaryComparator);
+ // Rank each record individually.
+ // Lock comparator state for consistent compare() results.
+ synchronized (mPreliminaryComparator.mStateLock) {
+ notificationList.sort(mPreliminaryComparator);
+ }
synchronized (mProxyByGroupTmp) {
// record individual ranking result and nominate proxies for each group
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
index 5b7b0c1..f56a67c 100644
--- a/services/core/java/com/android/server/notification/ZenModeFiltering.java
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -55,7 +55,7 @@
public ZenModeFiltering(Context context) {
mContext = context;
- mMessagingUtil = new NotificationMessagingUtil(mContext);
+ mMessagingUtil = new NotificationMessagingUtil(mContext, null);
}
public ZenModeFiltering(Context context, NotificationMessagingUtil messagingUtil) {
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index dd3604e..5b05b48 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -24,6 +24,7 @@
import static android.content.Intent.ACTION_MAIN;
import static android.content.Intent.CATEGORY_DEFAULT;
import static android.content.Intent.CATEGORY_HOME;
+import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
import static android.content.pm.PackageManager.CERT_INPUT_RAW_X509;
import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
@@ -1514,6 +1515,11 @@
ai.uid = UserHandle.getUid(userId, ps.getAppId());
ai.primaryCpuAbi = ps.getPrimaryCpuAbiLegacy();
ai.secondaryCpuAbi = ps.getSecondaryCpuAbiLegacy();
+ ai.volumeUuid = ps.getVolumeUuid();
+ ai.storageUuid = StorageManager.convert(ai.volumeUuid);
+ if (ps.isDefaultToDeviceProtectedStorage()) {
+ ai.privateFlags |= PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
+ }
ai.setVersionCode(ps.getVersionCode());
ai.flags = ps.getFlags();
ai.privateFlags = ps.getPrivateFlags();
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index 4ba174d..04d1da6 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -113,6 +113,9 @@
},
{
"exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-filter": "android.content.pm.cts.PackageManagerShellCommandMultiUserTest"
}
]
}
@@ -134,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/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index ac52f9f..385dfcb8 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -4882,6 +4882,7 @@
UserManager.USER_OPERATION_ERROR_LOW_STORAGE);
}
+ final boolean isMainUser = (flags & UserInfo.FLAG_MAIN) != 0;
final boolean isProfile = userTypeDetails.isProfile();
final boolean isGuest = UserManager.isUserTypeGuest(userType);
final boolean isRestricted = UserManager.isUserTypeRestricted(userType);
@@ -5028,6 +5029,10 @@
}
} else {
userTypeDetails.addDefaultRestrictionsTo(restrictions);
+ if (isMainUser) {
+ restrictions.remove(UserManager.DISALLOW_OUTGOING_CALLS);
+ restrictions.remove(UserManager.DISALLOW_SMS);
+ }
}
synchronized (mRestrictionsLock) {
mBaseUserRestrictions.updateRestrictions(userId, restrictions);
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index f7967c0..d5231b5 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -295,7 +295,8 @@
.setMediaSharedWithParent(false)
.setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
.setCrossProfileIntentFilterAccessControl(
- UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM));
+ UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM)
+ .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT));
}
/**
diff --git a/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java b/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java
index 0bb969f..a2177e8 100644
--- a/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java
+++ b/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java
@@ -17,13 +17,13 @@
import android.content.pm.parsing.ApkLiteParseUtils;
import android.content.pm.parsing.PackageLite;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils.ParseFlags;
import android.content.res.ApkAssets;
import android.content.res.AssetManager;
import android.os.Build;
import com.android.internal.util.ArrayUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils.ParseFlags;
import libcore.io.IoUtils;
@@ -82,8 +82,8 @@
}
AssetManager assets = new AssetManager();
- assets.setConfiguration(0, 0, null, new String[0], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, Build.VERSION.RESOURCES_SDK_INT);
+ assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ Build.VERSION.RESOURCES_SDK_INT);
assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
mCachedAssetManager = assets;
diff --git a/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java b/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java
index 56d92fb..1a8c1996 100644
--- a/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java
+++ b/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java
@@ -80,8 +80,8 @@
private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) {
final AssetManager assets = new AssetManager();
- assets.setConfiguration(0, 0, null, new String[0], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, Build.VERSION.RESOURCES_SDK_INT);
+ assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ Build.VERSION.RESOURCES_SDK_INT);
assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
return assets;
}
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index f36ecf7..4698b6d 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -333,7 +333,6 @@
PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
userId);
List<TvInputInfo> inputList = new ArrayList<>();
- List<ComponentName> hardwareComponents = new ArrayList<>();
for (ResolveInfo ri : services) {
ServiceInfo si = ri.serviceInfo;
if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
@@ -344,17 +343,16 @@
ComponentName component = new ComponentName(si.packageName, si.name);
if (hasHardwarePermission(pm, component)) {
- hardwareComponents.add(component);
ServiceState serviceState = userState.serviceStateMap.get(component);
if (serviceState == null) {
// New hardware input found. Create a new ServiceState and connect to the
// service to populate the hardware list.
serviceState = new ServiceState(component, userId);
userState.serviceStateMap.put(component, serviceState);
- updateServiceConnectionLocked(component, userId);
} else {
inputList.addAll(serviceState.hardwareInputMap.values());
}
+ updateServiceConnectionLocked(component, userId);
} else {
try {
TvInputInfo info = new TvInputInfo.Builder(mContext, ri).build();
@@ -417,15 +415,6 @@
}
}
- // Clean up ServiceState corresponding to the removed hardware inputs
- Iterator<ServiceState> it = userState.serviceStateMap.values().iterator();
- while (it.hasNext()) {
- ServiceState serviceState = it.next();
- if (serviceState.isHardware && !hardwareComponents.contains(serviceState.component)) {
- it.remove();
- }
- }
-
userState.inputMap.clear();
userState.inputMap = inputMap;
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index a7849c1..59ed3d6 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -108,7 +108,6 @@
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.os.Process.SYSTEM_UID;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.Display.COLOR_MODE_DEFAULT;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
@@ -4703,26 +4702,11 @@
}
/**
- * @return Whether we are allowed to show non-starting windows at the moment. We disallow
- * showing windows while the transition animation is playing in case we have windows
- * that have wide-color-gamut color mode set to avoid jank in the middle of the
- * animation.
+ * @return Whether we are allowed to show non-starting windows at the moment.
*/
boolean canShowWindows() {
- final boolean drawn = mTransitionController.isShellTransitionsEnabled()
+ return mTransitionController.isShellTransitionsEnabled()
? mSyncState != SYNC_STATE_WAITING_FOR_DRAW : allDrawn;
- final boolean animating = mTransitionController.isShellTransitionsEnabled()
- ? mTransitionController.inPlayingTransition(this)
- : isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION);
- return drawn && !(animating && hasNonDefaultColorWindow());
- }
-
- /**
- * @return true if we have a window that has a non-default color mode set; false otherwise.
- */
- private boolean hasNonDefaultColorWindow() {
- return forAllWindows(ws -> ws.mAttrs.getColorMode() != COLOR_MODE_DEFAULT,
- true /* topToBottom */);
}
@Override
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 2ecbf8a..5aa7c97 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -87,7 +87,7 @@
private int mLastOrientation = ORIENTATION_UNDEFINED;
ContentRecorder(@NonNull DisplayContent displayContent) {
- this(displayContent, new RemoteMediaProjectionManagerWrapper());
+ this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId));
}
@VisibleForTesting
@@ -556,8 +556,14 @@
private static final class RemoteMediaProjectionManagerWrapper implements
MediaProjectionManagerWrapper {
+
+ private final int mDisplayId;
@Nullable private IMediaProjectionManager mIMediaProjectionManager = null;
+ RemoteMediaProjectionManagerWrapper(int displayId) {
+ mDisplayId = displayId;
+ }
+
@Override
public void stopActiveProjection() {
fetchMediaProjectionManager();
@@ -565,12 +571,15 @@
return;
}
try {
+ ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
+ "Content Recording: stopping active projection for display %d",
+ mDisplayId);
mIMediaProjectionManager.stopActiveProjection();
} catch (RemoteException e) {
ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
"Content Recording: Unable to tell MediaProjectionManagerService to stop "
- + "the active projection: %s",
- e);
+ + "the active projection for display %d: %s",
+ mDisplayId, e);
}
}
diff --git a/services/core/java/com/android/server/wm/ContentRecordingController.java b/services/core/java/com/android/server/wm/ContentRecordingController.java
index f24ba5a..b589085 100644
--- a/services/core/java/com/android/server/wm/ContentRecordingController.java
+++ b/services/core/java/com/android/server/wm/ContentRecordingController.java
@@ -117,10 +117,11 @@
}
incomingDisplayContent.setContentRecordingSession(incomingSession);
// Updating scenario: Explicitly ask ContentRecorder to update, since no config or
- // display change will trigger an update from the DisplayContent.
- if (hasSessionUpdatedWithConsent) {
- incomingDisplayContent.updateRecording();
- }
+ // display change will trigger an update from the DisplayContent. There exists a
+ // scenario where a DisplayContent is created, but it's ContentRecordingSession hasn't
+ // been set yet due to a race condition. On creation, updateRecording fails to start
+ // recording, so now this call guarantees recording will be started from somewhere.
+ incomingDisplayContent.updateRecording();
}
// Takeover and stopping scenario: stop recording on the pre-existing session.
if (mSession != null && !hasSessionUpdatedWithConsent) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 614493d..c909cbe 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 @@
}
/**
+ * @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 70edf3a..9ef25b6 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 @@
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/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 7d3c87a..ba242ec 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -63,6 +63,8 @@
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
@@ -215,6 +217,11 @@
@Nullable
private final Boolean mBooleanPropertyAllowForceResizeOverride;
+ @Nullable
+ private final Boolean mBooleanPropertyAllowUserAspectRatioOverride;
+ @Nullable
+ private final Boolean mBooleanPropertyAllowUserAspectRatioFullscreenOverride;
+
/*
* WindowContainerListener responsible to make translucent activities inherit
* constraints from the first opaque activity beneath them. It's null for not
@@ -335,6 +342,15 @@
/* gatingCondition */ null,
PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
+ mBooleanPropertyAllowUserAspectRatioOverride =
+ readComponentProperty(packageManager, mActivityRecord.packageName,
+ () -> mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled(),
+ PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE);
+ mBooleanPropertyAllowUserAspectRatioFullscreenOverride =
+ readComponentProperty(packageManager, mActivityRecord.packageName,
+ () -> mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled(),
+ PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE);
+
mIsOverrideAnyOrientationEnabled = isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION);
mIsOverrideToPortraitOrientationEnabled =
isCompatChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT);
@@ -1109,7 +1125,8 @@
}
boolean shouldApplyUserMinAspectRatioOverride() {
- if (!mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()
+ if (FALSE.equals(mBooleanPropertyAllowUserAspectRatioOverride)
+ || !mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()
|| mActivityRecord.mDisplayContent == null
|| !mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) {
return false;
@@ -1122,7 +1139,9 @@
}
boolean shouldApplyUserFullscreenOverride() {
- if (!mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()
+ if (FALSE.equals(mBooleanPropertyAllowUserAspectRatioOverride)
+ || FALSE.equals(mBooleanPropertyAllowUserAspectRatioFullscreenOverride)
+ || !mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()
|| mActivityRecord.mDisplayContent == null
|| !mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) {
return false;
@@ -1151,7 +1170,8 @@
}
}
- private int getUserMinAspectRatioOverrideCode() {
+ @VisibleForTesting
+ int getUserMinAspectRatioOverrideCode() {
try {
return mActivityRecord.mAtmService.getPackageManager()
.getUserMinAspectRatio(mActivityRecord.packageName, mActivityRecord.mUserId);
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 6630e20..c7fd147 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -156,7 +156,7 @@
linkFixedRotationTransform(wallpaperTarget.mToken);
}
}
- if (mTransitionController.isShellTransitionsEnabled()) {
+ if (mTransitionController.inTransition(this)) {
// If wallpaper is in transition, setVisible() will be called from commitVisibility()
// when finishing transition. Otherwise commitVisibility() is already called from above
// setVisibility().
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 029f46f..fc49700 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2334,6 +2334,8 @@
mDisplayContent.updateImeControlTarget(isImeLayeringTarget() /* updateImeParent */);
// Fix the starting window to task when Activity has changed.
if (mStartingData != null && mStartingData.mAssociatedTask == null
+ && mTempConfiguration.windowConfiguration.getRotation()
+ == selfConfiguration.windowConfiguration.getRotation()
&& !mTempConfiguration.windowConfiguration.getBounds().equals(getBounds())) {
mStartingData.mResizedFromTransfer = true;
// Lock the starting window to task, so it won't resize from transfer anymore.
diff --git a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
index b620407..f5360eb 100644
--- a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
+++ b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
@@ -30,6 +30,7 @@
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.app.prediction.AppTargetId;
+import android.content.ComponentName;
import android.content.Context;
import android.content.IntentFilter;
import android.content.pm.ShortcutInfo;
@@ -39,6 +40,7 @@
import android.util.Log;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ChooserActivity;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
@@ -63,6 +65,7 @@
private static final String REMOTE_APP_PREDICTOR_KEY = "remote_app_predictor";
private final IntentFilter mIntentFilter;
private final AppPredictor mRemoteAppPredictor;
+ @Nullable private final String mChooserActivity;
ShareTargetPredictor(@NonNull AppPredictionContext predictionContext,
@NonNull Consumer<List<AppTarget>> updatePredictionsMethod,
@@ -81,6 +84,9 @@
} else {
mRemoteAppPredictor = null;
}
+ ComponentName component = ComponentName.unflattenFromString(
+ context.getResources().getString(R.string.config_chooserActivity));
+ mChooserActivity = (component == null) ? null : component.getShortClassName();
}
/** Reports chosen history of direct/app share targets. */
@@ -138,7 +144,7 @@
SharesheetModelScorer.computeScoreForAppShare(shareTargets,
getShareEventType(mIntentFilter), getPredictionContext().getPredictedTargetCount(),
System.currentTimeMillis(), getDataManager(),
- mCallingUserId);
+ mCallingUserId, mChooserActivity);
Collections.sort(shareTargets, (t1, t2) -> -Float.compare(t1.getScore(), t2.getScore()));
List<AppTarget> appTargetList = new ArrayList<>();
for (ShareTarget shareTarget : shareTargets) {
diff --git a/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java b/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java
index c77843c..b2f1e21 100644
--- a/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java
+++ b/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java
@@ -26,7 +26,6 @@
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.ChooserActivity;
import com.android.server.people.data.AppUsageStatsData;
import com.android.server.people.data.DataManager;
import com.android.server.people.data.Event;
@@ -55,8 +54,6 @@
private static final float FREQUENTLY_USED_APP_SCORE_INITIAL_DECAY = 0.3F;
@VisibleForTesting
static final float FOREGROUND_APP_WEIGHT = 0F;
- @VisibleForTesting
- static final String CHOOSER_ACTIVITY = ChooserActivity.class.getSimpleName();
// Keep constructor private to avoid class being instantiated.
private SharesheetModelScorer() {
@@ -169,13 +166,14 @@
*/
static void computeScoreForAppShare(List<ShareTargetPredictor.ShareTarget> shareTargets,
int shareEventType, int targetsLimit, long now, @NonNull DataManager dataManager,
- @UserIdInt int callingUserId) {
+ @UserIdInt int callingUserId, @Nullable String chooserActivity) {
computeScore(shareTargets, shareEventType, now);
- postProcess(shareTargets, targetsLimit, dataManager, callingUserId);
+ postProcess(shareTargets, targetsLimit, dataManager, callingUserId, chooserActivity);
}
private static void postProcess(List<ShareTargetPredictor.ShareTarget> shareTargets,
- int targetsLimit, @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+ int targetsLimit, @NonNull DataManager dataManager, @UserIdInt int callingUserId,
+ @Nullable String chooserActivity) {
// Populates a map which key is package name and value is list of shareTargets descended
// on total score.
Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap = new ArrayMap<>();
@@ -192,7 +190,7 @@
}
targetsList.add(index, shareTarget);
}
- promoteForegroundApp(shareTargetMap, dataManager, callingUserId);
+ promoteForegroundApp(shareTargetMap, dataManager, callingUserId, chooserActivity);
promoteMostChosenAndFrequentlyUsedApps(shareTargetMap, targetsLimit, dataManager,
callingUserId);
}
@@ -272,9 +270,10 @@
*/
private static void promoteForegroundApp(
Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap,
- @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+ @NonNull DataManager dataManager, @UserIdInt int callingUserId,
+ @Nullable String chooserActivity) {
String sharingForegroundApp = findSharingForegroundApp(shareTargetMap, dataManager,
- callingUserId);
+ callingUserId, chooserActivity);
if (sharingForegroundApp != null) {
ShareTargetPredictor.ShareTarget target = shareTargetMap.get(sharingForegroundApp).get(
0);
@@ -297,7 +296,8 @@
@Nullable
private static String findSharingForegroundApp(
Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap,
- @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+ @NonNull DataManager dataManager, @UserIdInt int callingUserId,
+ @Nullable String chooserActivity) {
String sharingForegroundApp = null;
long now = System.currentTimeMillis();
List<UsageEvents.Event> events = dataManager.queryAppMovingToForegroundEvents(
@@ -306,8 +306,8 @@
for (int i = events.size() - 1; i >= 0; i--) {
String className = events.get(i).getClassName();
String packageName = events.get(i).getPackageName();
- if (packageName == null || (className != null && className.contains(CHOOSER_ACTIVITY))
- || packageName.contains(CHOOSER_ACTIVITY)) {
+ if (packageName == null || (className != null && chooserActivity != null
+ && className.contains(chooserActivity))) {
continue;
}
if (sourceApp == null) {
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 da7a6a1..d9338a9 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 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 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 @@
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 @@
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 @@
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 @@
assertEquals(throttlingDataMap,
mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId());
-
- assertNotNull(mDisplayDeviceConfig.getHostUsiVersion());
- assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMajorVersion(), 2);
- assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMinorVersion(), 0);
-
- // 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);
-
- // Todo: Add asserts for DensityMapping,
- // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
}
@Test
- public void testConfigValuesFromConfigResource() {
+ public void testAmbientLightSensorFromDisplayConfig() throws IOException {
+ setupDisplayDeviceConfigFromDisplayConfigFile();
+
+ 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);
+
+ assertArrayEquals(new int[]{ -1, 10, 20, 30, 40 },
+ mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux());
+ }
+
+ @Test
+ 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 @@
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 @@
+ "</refreshRateThrottlingMap>\n";
}
- private String getContent() {
- return getContent(getValidLuxThrottling());
+ private String getValidProxSensor() {
+ return "<proxSensor>\n"
+ + "<type>test_proximity_sensor</type>\n"
+ + "<name>Test Proximity Sensor</name>\n"
+ + "</proxSensor>\n";
}
- private String getContent(String brightnessCapConfig) {
+ private String getProxSensorWithEmptyValues() {
+ return "<proxSensor>\n"
+ + "<type></type>\n"
+ + "<name></name>\n"
+ + "</proxSensor>\n";
+ }
+
+ private String getContent() {
+ return getContent(getValidLuxThrottling(), getValidProxSensor());
+ }
+
+ 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 @@
+ "</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 @@
+ "<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 @@
+ "</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 @@
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 @@
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 bf23117..979676e 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 @@
}
@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 2065479..c0128ae 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 @@
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 @@
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/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifierTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifierTest.java
new file mode 100644
index 0000000..266f5c1
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifierTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.display.brightness.clamper;
+
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@SmallTest
+public class BrightnessLowPowerModeModifierTest {
+ private static final float FLOAT_TOLERANCE = 0.001f;
+ private static final float DEFAULT_BRIGHTNESS = 0.5f;
+ private static final float LOW_POWER_BRIGHTNESS_FACTOR = 0.8f;
+ private static final float EXPECTED_LOW_POWER_BRIGHTNESS =
+ DEFAULT_BRIGHTNESS * LOW_POWER_BRIGHTNESS_FACTOR;
+ private final DisplayPowerRequest mRequest = new DisplayPowerRequest();
+ private final DisplayBrightnessState.Builder mBuilder = prepareBuilder();
+ private BrightnessLowPowerModeModifier mClamper;
+
+ @Before
+ public void setUp() {
+ mClamper = new BrightnessLowPowerModeModifier();
+ mRequest.screenLowPowerBrightnessFactor = LOW_POWER_BRIGHTNESS_FACTOR;
+ mRequest.lowPowerMode = true;
+ }
+
+ @Test
+ public void testApply_lowPowerModeOff() {
+ mRequest.lowPowerMode = false;
+
+ mClamper.apply(mRequest, mBuilder);
+
+ assertEquals(DEFAULT_BRIGHTNESS, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+ assertEquals(0, mBuilder.getBrightnessReason().getModifier());
+ assertTrue(mBuilder.isSlowChange());
+ }
+
+ @Test
+ public void testApply_lowPowerModeOn() {
+ mClamper.apply(mRequest, mBuilder);
+
+ assertEquals(EXPECTED_LOW_POWER_BRIGHTNESS, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+ assertEquals(BrightnessReason.MODIFIER_LOW_POWER,
+ mBuilder.getBrightnessReason().getModifier());
+ assertFalse(mBuilder.isSlowChange());
+ }
+
+ @Test
+ public void testApply_lowPowerModeOnAndLowPowerBrightnessFactorHigh() {
+ mRequest.screenLowPowerBrightnessFactor = 1.1f;
+
+ mClamper.apply(mRequest, mBuilder);
+
+ assertEquals(DEFAULT_BRIGHTNESS, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+ assertEquals(BrightnessReason.MODIFIER_LOW_POWER,
+ mBuilder.getBrightnessReason().getModifier());
+ assertFalse(mBuilder.isSlowChange());
+ }
+
+ @Test
+ public void testApply_lowPowerModeOnAndMinBrightness() {
+ mBuilder.setBrightness(0.0f);
+ mClamper.apply(mRequest, mBuilder);
+
+ assertEquals(0.0f, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+ assertEquals(0, mBuilder.getBrightnessReason().getModifier());
+ assertFalse(mBuilder.isSlowChange());
+ }
+
+ @Test
+ public void testApply_lowPowerModeOnAndLowPowerAlreadyApplied() {
+ mClamper.apply(mRequest, mBuilder);
+ DisplayBrightnessState.Builder builder = prepareBuilder();
+
+ mClamper.apply(mRequest, builder);
+
+ assertEquals(EXPECTED_LOW_POWER_BRIGHTNESS, builder.getBrightness(), FLOAT_TOLERANCE);
+ assertEquals(BrightnessReason.MODIFIER_LOW_POWER,
+ builder.getBrightnessReason().getModifier());
+ assertTrue(builder.isSlowChange());
+ }
+
+ @Test
+ public void testApply_lowPowerModeOffAfterLowPowerOn() {
+ mClamper.apply(mRequest, mBuilder);
+ mRequest.lowPowerMode = false;
+ DisplayBrightnessState.Builder builder = prepareBuilder();
+
+ mClamper.apply(mRequest, builder);
+
+ assertEquals(DEFAULT_BRIGHTNESS, builder.getBrightness(), FLOAT_TOLERANCE);
+ assertEquals(0, builder.getBrightnessReason().getModifier());
+ assertFalse(builder.isSlowChange());
+ }
+
+ private DisplayBrightnessState.Builder prepareBuilder() {
+ DisplayBrightnessState.Builder builder = DisplayBrightnessState.builder();
+ builder.setBrightness(DEFAULT_BRIGHTNESS);
+ builder.setIsSlowChange(true);
+ return builder;
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index 88f3b2e..525bfd7 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.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.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 @@
private TelephonyManager mTelephonyManager;
@Mock
private Sensor mOffBodySensor;
+ @Mock
+ private WearModeManagerInternal mWearModeManagerInternal;
class InjectorForTest extends DeviceIdleController.Injector {
ConnectivityManager connectivityManager;
@@ -348,6 +350,9 @@
mAnyMotionDetector = new AnyMotionDetectorForTest();
mInjector = new InjectorForTest(getContext());
+ doReturn(mWearModeManagerInternal)
+ .when(() -> LocalServices.getService(WearModeManagerInternal.class));
+
setupDeviceIdleController();
}
@@ -2413,22 +2418,20 @@
}
@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 @@
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 @@
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/backup/SystemBackupAgentTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
index c6d8848..4095be7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
@@ -83,7 +83,8 @@
"slices",
"people",
"app_locales",
- "app_gender");
+ "app_gender",
+ "companion");
}
@Test
@@ -106,7 +107,8 @@
"account_manager",
"people",
"app_locales",
- "app_gender");
+ "app_gender",
+ "companion");
}
@Test
@@ -121,7 +123,8 @@
"account_sync_settings",
"notifications",
"permissions",
- "app_locales");
+ "app_locales",
+ "companion");
}
@Test
@@ -140,7 +143,8 @@
"app_locales",
"account_manager",
"usage_stats",
- "shortcut_manager");
+ "shortcut_manager",
+ "companion");
}
private class TestableSystemBackupAgent extends SystemBackupAgent {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 0664ab8..d32b6be 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -15,7 +15,10 @@
*/
package com.android.server.pm;
+import static android.os.UserManager.DISALLOW_OUTGOING_CALLS;
+import static android.os.UserManager.DISALLOW_SMS;
import static android.os.UserManager.DISALLOW_USER_SWITCH;
+import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -490,6 +493,17 @@
assertThat(mUms.isUserSwitcherEnabled(USER_ID)).isTrue();
}
+ @Test
+ public void testMainUser_hasNoCallsOrSMSRestrictionsByDefault() {
+ UserInfo mainUser = mUms.createUserWithThrow("main user", USER_TYPE_FULL_SECONDARY,
+ UserInfo.FLAG_FULL | UserInfo.FLAG_MAIN);
+
+ assertThat(mUms.hasUserRestriction(DISALLOW_OUTGOING_CALLS, mainUser.id))
+ .isFalse();
+ assertThat(mUms.hasUserRestriction(DISALLOW_SMS, mainUser.id))
+ .isFalse();
+ }
+
private void resetUserSwitcherEnabled() {
mUms.putUserInfo(new UserInfo(USER_ID, "Test User", 0));
mUms.setUserRestriction(DISALLOW_USER_SWITCH, false, USER_ID);
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 4512cc02..bcbbcd4 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 @@
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/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
index ffe088c..a621055 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
@@ -19,14 +19,17 @@
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.Constants.PATH_RELATIONSHIP_ANCESTOR;
+import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -307,4 +310,60 @@
HdmiCecAtomWriter.FEATURE_ABORT_OPCODE_UNKNOWN,
HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN);
}
+
+ @Test
+ public void testDsmStatusChanged_toggleDsmStatus_ArcSupported_writesAtom() {
+ doReturn(true).when(mHdmiControlServiceSpy).isArcSupported();
+ mHdmiControlServiceSpy.setSoundbarMode(HdmiControlManager.SOUNDBAR_MODE_ENABLED);
+ mTestLooper.dispatchAll();
+
+ verify(mHdmiCecAtomWriterSpy, times(1))
+ .dsmStatusChanged(eq(true), eq(true),
+ eq(HdmiStatsEnums.LOG_REASON_DSM_SETTING_TOGGLED));
+ }
+
+ @Test
+ public void testDsmStatusChanged_toggleDsmStatus_ArcNotSupported_writesAtom() {
+ doReturn(false).when(mHdmiControlServiceSpy).isArcSupported();
+ mHdmiControlServiceSpy.setSoundbarMode(HdmiControlManager.SOUNDBAR_MODE_ENABLED);
+ mTestLooper.dispatchAll();
+
+ verify(mHdmiCecAtomWriterSpy, times(1))
+ .dsmStatusChanged(eq(false), eq(true),
+ eq(HdmiStatsEnums.LOG_REASON_DSM_SETTING_TOGGLED));
+ }
+
+ @Test
+ public void testDsmStatusChanged_onWakeUp_ArcSupported_writesAtom_logReasonWake() {
+ mHdmiControlServiceSpy.setSoundbarMode(HdmiControlManager.SOUNDBAR_MODE_DISABLED);
+ Mockito.clearInvocations(mHdmiCecAtomWriterSpy);
+
+ doReturn(true).when(mHdmiControlServiceSpy).isArcSupported();
+ mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+
+ verify(mHdmiCecAtomWriterSpy, times(1))
+ .dsmStatusChanged(eq(true), eq(false),
+ eq(HdmiStatsEnums.LOG_REASON_DSM_WAKE));
+ verify(mHdmiCecAtomWriterSpy, never())
+ .dsmStatusChanged(anyBoolean(), anyBoolean(),
+ eq(HdmiStatsEnums.LOG_REASON_DSM_SETTING_TOGGLED));
+ }
+
+ @Test
+ public void testDsmStatusChanged_onWakeUp_ArcNotSupported_writesAtom_logReasonWake() {
+ mHdmiControlServiceSpy.setSoundbarMode(HdmiControlManager.SOUNDBAR_MODE_DISABLED);
+ Mockito.clearInvocations(mHdmiCecAtomWriterSpy);
+
+ doReturn(false).when(mHdmiControlServiceSpy).isArcSupported();
+ mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+
+ verify(mHdmiCecAtomWriterSpy, times(1))
+ .dsmStatusChanged(eq(false), eq(false),
+ eq(HdmiStatsEnums.LOG_REASON_DSM_WAKE));
+ verify(mHdmiCecAtomWriterSpy, never())
+ .dsmStatusChanged(anyBoolean(), anyBoolean(),
+ eq(HdmiStatsEnums.LOG_REASON_DSM_SETTING_TOGGLED));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java b/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java
index 45fff48..2cd9198 100644
--- a/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java
@@ -57,6 +57,7 @@
private static final String PACKAGE_3 = "pkg3";
private static final String CLASS_1 = "cls1";
private static final String CLASS_2 = "cls2";
+ private static final String CHOOSER_ACTIVITY = "ChooserActivity";
private static final double DELTA = 1e-6;
private static final long NOW = System.currentTimeMillis();
private static final Range<Long> WITHIN_ONE_DAY = new Range(
@@ -246,7 +247,7 @@
SharesheetModelScorer.computeScoreForAppShare(
List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
mShareTarget6),
- Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+ Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
// Verification
assertEquals(0.514f, mShareTarget1.getScore(), DELTA);
@@ -278,7 +279,7 @@
SharesheetModelScorer.computeScoreForAppShare(
List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
mShareTarget6),
- Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+ Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
verify(mDataManager, times(1)).queryAppUsageStats(anyInt(), anyLong(), anyLong(),
anySet());
@@ -311,7 +312,7 @@
SharesheetModelScorer.computeScoreForAppShare(
List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
mShareTarget6),
- Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+ Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
verify(mDataManager, times(1)).queryAppUsageStats(anyInt(), anyLong(), anyLong(),
anySet());
@@ -349,7 +350,7 @@
SharesheetModelScorer.computeScoreForAppShare(
List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
mShareTarget6),
- Event.TYPE_SHARE_TEXT, 4, NOW, mDataManager, USER_ID);
+ Event.TYPE_SHARE_TEXT, 4, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
verify(mDataManager, never()).queryAppUsageStats(anyInt(), anyLong(), anyLong(),
anySet());
@@ -377,7 +378,7 @@
anyLong())).thenReturn(
List.of(createUsageEvent(PACKAGE_2),
createUsageEvent(PACKAGE_3),
- createUsageEvent(SharesheetModelScorer.CHOOSER_ACTIVITY),
+ createUsageEvent(CHOOSER_ACTIVITY),
createUsageEvent(PACKAGE_3),
createUsageEvent(PACKAGE_3))
);
@@ -385,7 +386,7 @@
SharesheetModelScorer.computeScoreForAppShare(
List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
mShareTarget6),
- Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+ Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
verify(mDataManager, times(1)).queryAppMovingToForegroundEvents(anyInt(), anyLong(),
anyLong());
@@ -413,7 +414,7 @@
anyLong())).thenReturn(
List.of(createUsageEvent(PACKAGE_3),
createUsageEvent(PACKAGE_3),
- createUsageEvent(SharesheetModelScorer.CHOOSER_ACTIVITY),
+ createUsageEvent(CHOOSER_ACTIVITY),
createUsageEvent(PACKAGE_3),
createUsageEvent(PACKAGE_3))
);
@@ -421,7 +422,7 @@
SharesheetModelScorer.computeScoreForAppShare(
List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
mShareTarget6),
- Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+ Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
verify(mDataManager, times(1)).queryAppMovingToForegroundEvents(anyInt(), anyLong(),
anyLong());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java
index b94ed01..d04c518 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java
@@ -18,10 +18,14 @@
import static com.google.common.truth.Truth.assertThat;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.content.ServiceConnection;
@@ -51,6 +55,7 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ mContext.ensureTestableResources();
mProviders = new ConditionProviders(mContext, mUserProfiles, mIpm);
mProviders.setCallback(mCallback);
@@ -131,4 +136,21 @@
verify(mCallback).onConditionChanged(eq(Uri.parse("b")), eq(conditionsToNotify[3]));
verifyNoMoreInteractions(mCallback);
}
+
+ @Test
+ public void testRemoveDefaultFromConfig() {
+ final int userId = 0;
+ ComponentName oldDefaultComponent = ComponentName.unflattenFromString("package/Component1");
+
+ when(mContext.getResources().getString(
+ com.android.internal.R.string.config_defaultDndDeniedPackages))
+ .thenReturn("package");
+ mProviders.setPackageOrComponentEnabled(oldDefaultComponent.flattenToString(),
+ userId, true, true /*enabled*/, false /*userSet*/);
+ assertEquals("package", mProviders.getApproved(userId, true));
+
+ mProviders.removeDefaultFromConfig(userId);
+
+ assertTrue(mProviders.getApproved(userId, true).isEmpty());
+ }
}
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 22b1127..7b16500 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -15,15 +15,20 @@
*/
package com.android.server.notification;
+import static android.telecom.TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME;
+
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Notification;
@@ -31,8 +36,10 @@
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Person;
+import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
@@ -54,12 +61,14 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -335,6 +344,73 @@
assertTrue(comp.isImportantPeople(mRecordContact));
}
+ @Test
+ public void testChangeDialerPackageWhileSorting() throws InterruptedException {
+ final int halfList = 100;
+ int userId = UserHandle.myUserId();
+ when(mTm.getDefaultDialerPackage()).thenReturn("B");
+
+ ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(
+ BroadcastReceiver.class);
+ NotificationComparator comparator = new NotificationComparator(mMockContext);
+ verify(mMockContext).registerReceiver(broadcastReceiverCaptor.capture(), any());
+ BroadcastReceiver dialerChangedBroadcastReceiver = broadcastReceiverCaptor.getValue();
+
+ ArrayList<NotificationRecord> records = new ArrayList<>();
+ for (int i = 0; i < halfList; i++) {
+ Notification notifCallFromPkgA = new Notification.Builder(mMockContext, TEST_CHANNEL_ID)
+ .setCategory(Notification.CATEGORY_CALL)
+ .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
+ .build();
+ records.add(new NotificationRecord(mMockContext,
+ new StatusBarNotification("A", "A", 2 * i, "callA", callUid, callUid,
+ notifCallFromPkgA, new UserHandle(userId), "", 0),
+ getDefaultChannel()));
+
+ Notification notifCallFromPkgB = new Notification.Builder(mMockContext, TEST_CHANNEL_ID)
+ .setCategory(Notification.CATEGORY_CALL)
+ .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
+ .build();
+ records.add(new NotificationRecord(mMockContext,
+ new StatusBarNotification("B", "B", 2 * i + 1, "callB", callUid, callUid,
+ notifCallFromPkgB, new UserHandle(userId), "", 0),
+ getDefaultChannel()));
+ }
+
+ CountDownLatch allDone = new CountDownLatch(2);
+ new Thread(() -> {
+ // The lock prevents the other thread from changing the dialer package mid-sort, so:
+ // 1) Results should be "all B before all A" (asserted below).
+ // 2) No "IllegalArgumentException: Comparison method violates its general contract!"
+ synchronized (comparator.mStateLock) {
+ records.sort(comparator);
+ allDone.countDown();
+ }
+ }).start();
+
+ new Thread(() -> {
+ String nextDialer = "A";
+ while (allDone.getCount() == 2) {
+ Intent dialerChangedIntent = new Intent();
+ dialerChangedIntent.putExtra(EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, nextDialer);
+ dialerChangedBroadcastReceiver.onReceive(mMockContext, dialerChangedIntent);
+ nextDialer = nextDialer.equals("A") ? "B" : "A";
+ }
+ allDone.countDown();
+ }).start();
+
+ allDone.await();
+
+ for (int i = 0; i < halfList; i++) {
+ assertWithMessage("Wrong element in position #" + i)
+ .that(records.get(i).getSbn().getPackageName()).isEqualTo("B");
+ }
+ for (int i = halfList; i < 2 * halfList; i++) {
+ assertWithMessage("Wrong element in position #" + i)
+ .that(records.get(i).getSbn().getPackageName()).isEqualTo("A");
+ }
+ }
+
private NotificationChannel getDefaultChannel() {
return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name",
NotificationManager.IMPORTANCE_LOW);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index bdee99b..e1f3c2b 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -117,6 +117,7 @@
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
@@ -12431,6 +12432,21 @@
verify(mDevicePolicyManager).isActiveDeviceOwner(uid);
}
+ @Test
+ public void testResetDefaultDnd() {
+ TestableNotificationManagerService service = spy(mService);
+ UserInfo user = new UserInfo(0, "owner", 0);
+ when(mUm.getAliveUsers()).thenReturn(List.of(user));
+ doReturn(false).when(service).isDNDMigrationDone(anyInt());
+
+ service.resetDefaultDndIfNecessary();
+
+ verify(mConditionProviders, times(1)).removeDefaultFromConfig(user.id);
+ verify(mConditionProviders, times(1)).resetDefaultFromConfig();
+ verify(service, times(1)).allowDndPackages(user.id);
+ verify(service, times(1)).setDNDMigrationDone(user.id);
+ }
+
private static <T extends Parcelable> T parcelAndUnparcel(T source,
Parcelable.Creator<T> creator) {
Parcel parcel = Parcel.obtain();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index c242554..81d939f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -600,7 +600,7 @@
NotificationChannel channel2 =
new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
channel2.setDescription("descriptions for all");
- channel2.setSound(SOUND_URI, mAudioAttributes);
+ channel2.setSound(CANONICAL_SOUND_URI, mAudioAttributes);
channel2.enableLights(true);
channel2.setBypassDnd(true);
channel2.setLockscreenVisibility(VISIBILITY_SECRET);
@@ -1374,6 +1374,8 @@
.when(mTestIContentProvider).uncanonicalize(any(), eq(CANONICAL_SOUND_URI));
doReturn(localUri)
.when(mTestIContentProvider).uncanonicalize(any(), eq(canonicalBasedOnLocal));
+ doReturn(canonicalBasedOnLocal)
+ .when(mTestIContentProvider).canonicalize(any(), eq(localUri));
NotificationChannel channel =
new NotificationChannel("id", "name", IMPORTANCE_LOW);
@@ -1387,7 +1389,7 @@
NotificationChannel actualChannel = mHelper.getNotificationChannel(
PKG_N_MR1, UID_N_MR1, channel.getId(), false);
- assertEquals(localUri, actualChannel.getSound());
+ assertEquals(canonicalBasedOnLocal, actualChannel.getSound());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 3db53eb..3eed0b7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2939,6 +2939,9 @@
// transform to activity1.
int rotation = (mDisplayContent.getRotation() + 1) % 4;
mDisplayContent.setFixedRotationLaunchingApp(activity, rotation);
+ // The configuration with rotation change should not trigger task-association.
+ assertNotNull(activity.mStartingData);
+ assertNull(activity.mStartingData.mAssociatedTask);
doReturn(rotation).when(mDisplayContent)
.rotationForActivityInDifferentOrientation(topActivity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
index 52226c2..4473a31 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
@@ -123,6 +123,7 @@
controller.setContentRecordingSessionLocked(mWaitingDisplaySession, mWm);
verify(mVirtualDisplayContent, atLeastOnce()).setContentRecordingSession(
mWaitingDisplaySession);
+ verify(mVirtualDisplayContent).updateRecording();
// WHEN updating the session on the same display, so no longer waiting to record.
ContentRecordingSession sessionUpdate = ContentRecordingSession.createTaskSession(
@@ -135,7 +136,7 @@
// THEN the session was accepted.
assertThat(resultingSession).isEqualTo(sessionUpdate);
verify(mVirtualDisplayContent, atLeastOnce()).setContentRecordingSession(sessionUpdate);
- verify(mVirtualDisplayContent).updateRecording();
+ verify(mVirtualDisplayContent, atLeastOnce()).updateRecording();
}
@Test
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 939ff97..c4302db 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 @@
}
@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/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 72ab18d..2ad9fa0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -37,6 +37,8 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
@@ -48,6 +50,8 @@
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
@@ -807,6 +811,108 @@
/* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT);
}
+ // shouldApplyUser...Override
+ @Test
+ public void testShouldApplyUserFullscreenOverride_trueProperty_returnsFalse() throws Exception {
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE,
+ /* value */ true);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+ doReturn(false).when(mLetterboxConfiguration).isUserAppAspectRatioFullscreenEnabled();
+
+ assertFalse(mController.shouldApplyUserFullscreenOverride());
+ }
+
+ @Test
+ public void testShouldApplyUserFullscreenOverride_falseFullscreenProperty_returnsFalse()
+ throws Exception {
+ prepareActivityThatShouldApplyUserFullscreenOverride();
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE,
+ /* value */ false);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldApplyUserFullscreenOverride());
+ }
+
+ @Test
+ public void testShouldApplyUserFullscreenOverride_falseSettingsProperty_returnsFalse()
+ throws Exception {
+ prepareActivityThatShouldApplyUserFullscreenOverride();
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldApplyUserFullscreenOverride());
+ }
+
+ @Test
+ public void testShouldApplyUserFullscreenOverride_disabledIgnoreOrientationRequest() {
+ prepareActivityThatShouldApplyUserFullscreenOverride();
+ mDisplayContent.setIgnoreOrientationRequest(false);
+
+ assertFalse(mController.shouldApplyUserFullscreenOverride());
+ }
+
+ @Test
+ public void testShouldApplyUserFullscreenOverride_returnsTrue() {
+ prepareActivityThatShouldApplyUserFullscreenOverride();
+
+ assertTrue(mController.shouldApplyUserFullscreenOverride());
+ }
+
+ @Test
+ public void testShouldApplyUserMinAspectRatioOverride_falseProperty_returnsFalse()
+ throws Exception {
+ prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+ }
+
+ @Test
+ public void testShouldApplyUserMinAspectRatioOverride_trueProperty_returnsFalse()
+ throws Exception {
+ doReturn(false).when(mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled();
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+ }
+
+ @Test
+ public void testShouldApplyUserMinAspectRatioOverride_disabledIgnoreOrientationRequest() {
+ prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+ mDisplayContent.setIgnoreOrientationRequest(false);
+
+ assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+ }
+
+ @Test
+ public void testShouldApplyUserMinAspectRatioOverride_returnsTrue() {
+ prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+
+ assertTrue(mController.shouldApplyUserMinAspectRatioOverride());
+ }
+
+ private void prepareActivityThatShouldApplyUserMinAspectRatioOverride() {
+ spyOn(mController);
+ doReturn(true).when(mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled();
+ mDisplayContent.setIgnoreOrientationRequest(true);
+ doReturn(USER_MIN_ASPECT_RATIO_3_2).when(mController).getUserMinAspectRatioOverrideCode();
+ }
+
+ private void prepareActivityThatShouldApplyUserFullscreenOverride() {
+ spyOn(mController);
+ doReturn(true).when(mLetterboxConfiguration).isUserAppAspectRatioFullscreenEnabled();
+ mDisplayContent.setIgnoreOrientationRequest(true);
+ doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN).when(mController)
+ .getUserMinAspectRatioOverrideCode();
+ }
+
// shouldUseDisplayLandscapeNaturalOrientation
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java
index 9a93746..e3f8e8c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java
@@ -125,6 +125,7 @@
@Test
public void testSurfaceChangedOnRotation() {
+ CommonUtils.dismissKeyguard();
final Instrumentation instrumentation = getInstrumentation();
final Context context = instrumentation.getContext();
final Intent intent = new Intent().setComponent(
@@ -137,6 +138,8 @@
instrumentation.runOnMainSync(() -> activity.setContentView(sv));
sv.getHolder().addCallback(new SurfaceHolder.Callback() {
int mInitialTransformHint = -1;
+ int mInitialW;
+ int mInitialH;
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
@@ -148,7 +151,10 @@
sv.getViewRootImpl().getSurfaceControl().getTransformHint();
if (mInitialTransformHint == -1) {
mInitialTransformHint = transformHint;
- } else if (mInitialTransformHint == transformHint) {
+ mInitialW = width;
+ mInitialH = height;
+ } else if (mInitialTransformHint == transformHint
+ && (width > height) != (mInitialW > mInitialH)) {
// For example, the initial hint is from portrait, so the later changes from
// landscape should not receive the same hint.
unexpectedTransformHint[0] = true;
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 0d88a0d..befc4a0 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -364,8 +364,10 @@
if (appInfo.isSystemApp() && !appInfo.isUpdatedSystemApp()) {
// We don't count code baked into system image
} else {
- codePaths = ArrayUtils.appendElement(String.class, codePaths,
+ if (appInfo.getCodePath() != null) {
+ codePaths = ArrayUtils.appendElement(String.class, codePaths,
appInfo.getCodePath());
+ }
}
final PackageStats stats = new PackageStats(TAG);
@@ -418,8 +420,10 @@
if (appInfo.isSystemApp() && !appInfo.isUpdatedSystemApp()) {
// We don't count code baked into system image
} else {
- codePaths = ArrayUtils.appendElement(String.class, codePaths,
- appInfo.getCodePath());
+ if (appInfo.getCodePath() != null) {
+ codePaths = ArrayUtils.appendElement(String.class, codePaths,
+ appInfo.getCodePath());
+ }
}
} catch (NameNotFoundException e) {
throw new ParcelableException(e);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 314150b..4907134 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9418,6 +9418,7 @@
* <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 6258b9c..631013f 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -170,7 +170,7 @@
@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 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 @@
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/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 040c5b0..0af973a 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -85,11 +85,11 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -1318,10 +1318,24 @@
private final Context mContext;
- // Cache of Resource that has been created in getResourcesForSubId. Key is a Pair containing
- // the Context and subId.
- private static final Map<Pair<Context, Integer>, Resources> sResourcesCache =
- new ConcurrentHashMap<>();
+ /**
+ * In order to prevent the overflow of the heap size due to an indiscriminate increase in the
+ * cache, the heap size of the resource cache is set sufficiently large.
+ */
+ private static final int MAX_RESOURCE_CACHE_ENTRY_COUNT = 10_000;
+
+ /**
+ * Cache of Resources that has been created in getResourcesForSubId. Key contains package name,
+ * and Configuration of Resources. If more than the maximum number of resources are stored in
+ * this cache, the least recently used Resources will be removed to maintain the maximum size.
+ */
+ private static final Map<Pair<String, Configuration>, Resources> sResourcesCache =
+ Collections.synchronizedMap(new LinkedHashMap<>(16, 0.75f, true) {
+ @Override
+ protected boolean removeEldestEntry(Entry eldest) {
+ return size() > MAX_RESOURCE_CACHE_ENTRY_COUNT;
+ }
+ });
/**
* A listener class for monitoring changes to {@link SubscriptionInfo} records.
@@ -2807,14 +2821,20 @@
@NonNull
public static Resources getResourcesForSubId(Context context, int subId,
boolean useRootLocale) {
- // Check if resources for this context and subId already exist in the resource cache.
- // Resources that use the root locale are not cached.
- Pair<Context, Integer> cacheKey = null;
- if (isValidSubscriptionId(subId) && !useRootLocale) {
- cacheKey = Pair.create(context, subId);
- if (sResourcesCache.containsKey(cacheKey)) {
+ // Check if the Resources already exists in the cache based on the given context. Find a
+ // Resource that match Configuration.
+ Pair<String, Configuration> cacheKey = null;
+ if (isValidSubscriptionId(subId)) {
+ Configuration configurationKey =
+ new Configuration(context.getResources().getConfiguration());
+ if (useRootLocale) {
+ configurationKey.setLocale(Locale.ROOT);
+ }
+ cacheKey = Pair.create(context.getPackageName(), configurationKey);
+ Resources cached = sResourcesCache.get(cacheKey);
+ if (cached != null) {
// Cache hit. Use cached Resources.
- return sResourcesCache.get(cacheKey);
+ return cached;
}
}
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 0000000..2a2b70e
--- /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 3a784ff..ec792ca2 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 @@
@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 @@
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 0000000..1fdef3c
--- /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/test-apps/flickerapp/Android.bp b/tests/FlickerTests/test-apps/flickerapp/Android.bp
index 75e35ee..e3b23b9 100644
--- a/tests/FlickerTests/test-apps/flickerapp/Android.bp
+++ b/tests/FlickerTests/test-apps/flickerapp/Android.bp
@@ -24,6 +24,9 @@
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 ff9799a..704798e 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 0000000..19205d4
--- /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 e51ed29..9b742d9 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 2795a6c..7c5e9a3 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 @@
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 0000000..0323286
--- /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/BitmapsAlphaActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapsAlphaActivity.java
index ef49c7f..cb16191 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapsAlphaActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapsAlphaActivity.java
@@ -48,15 +48,15 @@
BitmapsView(Context c) {
super(c);
- Log.d("OpenGLRenderer", "Loading sunset1, default options");
+ Log.d("HWUI", "Loading sunset1, default options");
mBitmap1 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset1);
- Log.d("OpenGLRenderer", "Loading sunset2, default options");
+ Log.d("HWUI", "Loading sunset2, default options");
mBitmap2 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset2);
- Log.d("OpenGLRenderer", "Loading sunset3, forcing ARGB-8888");
+ Log.d("HWUI", "Loading sunset3, forcing ARGB-8888");
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
mBitmap3 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset3, opts);
- Log.d("OpenGLRenderer", " has bitmap alpha? " + mBitmap3.hasAlpha());
+ Log.d("HWUI", " has bitmap alpha? " + mBitmap3.hasAlpha());
mBitmapPaint = new Paint();
}
@@ -65,7 +65,7 @@
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
- Log.d("OpenGLRenderer", "================= Draw");
+ Log.d("HWUI", "================= Draw");
canvas.translate(120.0f, 50.0f);
canvas.drawBitmap(mBitmap1, 0.0f, 0.0f, mBitmapPaint);
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ClearActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ClearActivity.java
index 1c82e9b..dbfb4ca 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/ClearActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ClearActivity.java
@@ -77,7 +77,7 @@
canvas.drawPath(mPath, mClearPaint);
}
canvas.restore();
- canvas.drawText("OpenGLRenderer", 50.0f, 50.0f, mClearPaint);
+ canvas.drawText("HWUI", 50.0f, 50.0f, mClearPaint);
mClearPaint.setColor(0xff000000);
canvas.drawRect(800.0f, 100.0f, 900.0f, 200.0f, mClearPaint);
mClearPaint.setColor(0x0000ff00);
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/QuickRejectActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/QuickRejectActivity.java
index 5192bfe..11a2a41 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/QuickRejectActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/QuickRejectActivity.java
@@ -53,30 +53,30 @@
super.onDraw(canvas);
int count = canvas.getSaveCount();
- Log.d("OpenGLRenderer", "count=" + count);
+ Log.d("HWUI", "count=" + count);
count = canvas.save();
- Log.d("OpenGLRenderer", "count after save=" + count);
+ Log.d("HWUI", "count after save=" + count);
count = canvas.getSaveCount();
- Log.d("OpenGLRenderer", "getSaveCount after save=" + count);
+ Log.d("HWUI", "getSaveCount after save=" + count);
canvas.restore();
count = canvas.getSaveCount();
- Log.d("OpenGLRenderer", "count after restore=" + count);
+ Log.d("HWUI", "count after restore=" + count);
canvas.save();
- Log.d("OpenGLRenderer", "count after save=" + canvas.getSaveCount());
+ Log.d("HWUI", "count after save=" + canvas.getSaveCount());
canvas.save();
- Log.d("OpenGLRenderer", "count after save=" + canvas.getSaveCount());
+ Log.d("HWUI", "count after save=" + canvas.getSaveCount());
canvas.save();
- Log.d("OpenGLRenderer", "count after save=" + canvas.getSaveCount());
+ Log.d("HWUI", "count after save=" + canvas.getSaveCount());
canvas.restoreToCount(count);
count = canvas.getSaveCount();
- Log.d("OpenGLRenderer", "count after restoreToCount=" + count);
+ Log.d("HWUI", "count after restoreToCount=" + count);
count = canvas.saveLayer(0, 0, 10, 10, mBitmapPaint, Canvas.ALL_SAVE_FLAG);
- Log.d("OpenGLRenderer", "count after saveLayer=" + count);
+ Log.d("HWUI", "count after saveLayer=" + count);
count = canvas.getSaveCount();
- Log.d("OpenGLRenderer", "getSaveCount after saveLayer=" + count);
+ Log.d("HWUI", "getSaveCount after saveLayer=" + count);
canvas.restore();
count = canvas.getSaveCount();
- Log.d("OpenGLRenderer", "count after restore=" + count);
+ Log.d("HWUI", "count after restore=" + count);
canvas.save();
canvas.clipRect(0.0f, 0.0f, 40.0f, 40.0f);
diff --git a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
index 56b0b9a..b39c932 100644
--- a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
@@ -123,10 +123,6 @@
createImeSubtype(3, ULocale.forLanguageTag("en-US"), "qwerty"),
KeyboardLayout(null, "German", null, 0, null, 0, 0, 0),
KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
- ).addLayoutSelection(
- createImeSubtype(4, null, "qwerty"), // Default language tag
- KeyboardLayout(null, "German", null, 0, null, 0, 0, 0),
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
).setIsFirstTimeConfiguration(true).build()
assertEquals(
@@ -142,8 +138,8 @@
assertTrue(event.isFirstConfiguration)
assertEquals(
- "KeyboardConfigurationEvent should contain 4 configurations provided",
- 4,
+ "KeyboardConfigurationEvent should contain 3 configurations provided",
+ 3,
event.layoutConfigurations.size
)
assertExpectedLayoutConfiguration(
@@ -159,7 +155,7 @@
event.layoutConfigurations[1],
"de-CH",
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
- KeyboardMetricsCollector.DEFAULT_LAYOUT,
+ KeyboardMetricsCollector.DEFAULT_LAYOUT_NAME,
KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER,
"en-US",
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("azerty"),
@@ -173,10 +169,29 @@
"en-US",
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
)
+ }
+
+ @Test
+ fun testCreateKeyboardConfigurationEvent_withDefaultLanguageTag() {
+ val builder = KeyboardMetricsCollector.KeyboardConfigurationEvent.Builder(
+ createKeyboard(
+ DEVICE_ID,
+ DEFAULT_VENDOR_ID,
+ DEFAULT_PRODUCT_ID,
+ "und", // Undefined language tag
+ "azerty"
+ )
+ )
+ val event = builder.addLayoutSelection(
+ createImeSubtype(4, null, "qwerty"), // Default language tag
+ KeyboardLayout(null, "German", null, 0, null, 0, 0, 0),
+ KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
+ ).build()
+
assertExpectedLayoutConfiguration(
- event.layoutConfigurations[3],
- "de-CH",
- KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
+ event.layoutConfigurations[0],
+ KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+ KeyboardLayout.LayoutType.getLayoutTypeEnumValue("azerty"),
"German",
KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,