Merge "Implement FSI suppression logic" into main
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 8d480e5..de07fdde6 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -16834,6 +16834,10 @@
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteCapabilities> CREATOR;
}
+ @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteCapabilitiesCallback {
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteCapabilitiesChanged(@NonNull android.telephony.satellite.SatelliteCapabilities);
+ }
+
@FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class SatelliteDatagram implements android.os.Parcelable {
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents();
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public byte[] getSatelliteDatagram();
@@ -16852,6 +16856,7 @@
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback);
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteCapabilitiesChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteCapabilitiesCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteProvisionStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
@@ -16872,6 +16877,7 @@
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void startSatelliteTransmissionUpdates(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>, @NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void stopSatelliteTransmissionUpdates(@NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrengthCallback);
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteCapabilitiesChanged(@NonNull android.telephony.satellite.SatelliteCapabilitiesCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteDatagram(@NonNull android.telephony.satellite.SatelliteDatagramCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteModemStateChanged(@NonNull android.telephony.satellite.SatelliteStateCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteProvisionStateChanged(@NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index 35509237..fd13174 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -575,8 +575,9 @@
extras.putString(Downloads.DIR_TYPE, dirType);
client.call(Downloads.CALL_CREATE_EXTERNAL_PUBLIC_DIR, null, extras);
} catch (RemoteException e) {
- throw new IllegalStateException("Unable to create directory: "
- + file.getAbsolutePath());
+ throw new IllegalStateException(
+ "Unable to create directory: " + file.getAbsolutePath(),
+ e);
}
} else {
if (file.exists()) {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 4c70c91..3df11f6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -81,6 +81,9 @@
import android.app.IServiceConnection;
import android.app.KeyguardManager;
import android.app.admin.SecurityLog.SecurityEvent;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -9118,6 +9121,19 @@
}
/**
+ * For apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above, the
+ * {@link #isDeviceOwnerApp} method will use the user contained within the
+ * context.
+ * For apps targeting an SDK version <em>below</em> this, the user of the calling process will
+ * be used (Process.myUserHandle()).
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public static final long IS_DEVICE_OWNER_USER_AWARE = 307233716L;
+
+ /**
* Used to determine if a particular package has been registered as a Device Owner app.
* A device owner app is a special device admin that cannot be deactivated by the user, once
* activated as a device admin. It also cannot be uninstalled. To check whether a particular
@@ -9130,8 +9146,13 @@
* app, if any.
* @return whether or not the package is registered as the device owner app.
*/
+ @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
public boolean isDeviceOwnerApp(String packageName) {
throwIfParentInstance("isDeviceOwnerApp");
+ if (android.permission.flags.Flags.roleControllerInSystemServer()
+ && CompatChanges.isChangeEnabled(IS_DEVICE_OWNER_USER_AWARE)) {
+ return isDeviceOwnerAppOnContextUser(packageName);
+ }
return isDeviceOwnerAppOnCallingUser(packageName);
}
@@ -9192,6 +9213,24 @@
return packageName.equals(deviceOwner.getPackageName());
}
+ private boolean isDeviceOwnerAppOnContextUser(String packageName) {
+ if (packageName == null) {
+ return false;
+ }
+ ComponentName deviceOwner = null;
+ if (mService != null) {
+ try {
+ deviceOwner = mService.getDeviceOwnerComponentOnUser(myUserId());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ if (deviceOwner == null) {
+ return false;
+ }
+ return packageName.equals(deviceOwner.getPackageName());
+ }
+
private ComponentName getDeviceOwnerComponentInner(boolean callingUserOnly) {
if (mService != null) {
try {
@@ -9608,6 +9647,7 @@
* @param packageName The package name of the app to compare with the registered profile owner.
* @return Whether or not the package is registered as the profile owner.
*/
+ @UserHandleAware
public boolean isProfileOwnerApp(String packageName) {
throwIfParentInstance("isProfileOwnerApp");
if (mService != null) {
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 6fe40be..575fa4c 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -179,6 +179,7 @@
boolean setDeviceOwner(in ComponentName who, int userId, boolean setProfileOwnerOnCurrentUserIfNecessary);
ComponentName getDeviceOwnerComponent(boolean callingUserOnly);
+ ComponentName getDeviceOwnerComponentOnUser(int userId);
boolean hasDeviceOwner();
String getDeviceOwnerName();
void clearDeviceOwner(String packageName);
diff --git a/core/java/android/content/pm/SigningInfo.java b/core/java/android/content/pm/SigningInfo.java
index 543703e..a407704 100644
--- a/core/java/android/content/pm/SigningInfo.java
+++ b/core/java/android/content/pm/SigningInfo.java
@@ -190,9 +190,11 @@
mSigningDetails.writeToParcel(dest, parcelableFlags);
}
- /* @hide */
+ /**
+ * @hide
+ */
@NonNull
- SigningDetails getSigningDetails() {
+ public SigningDetails getSigningDetails() {
return mSigningDetails;
}
diff --git a/core/res/OWNERS b/core/res/OWNERS
index 0df7c20..f24c3f5 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -1,5 +1,6 @@
adamp@google.com
asc@google.com
+austindelgado@google.com
cinek@google.com
dsandler@android.com
dsandler@google.com
@@ -8,6 +9,7 @@
hackbod@google.com
ilyamaty@google.com
jaggies@google.com
+jbolinger@google.com
jsharkey@android.com
jsharkey@google.com
juliacr@google.com
diff --git a/media/java/android/media/tv/ad/TvAdServiceInfo.aidl b/media/java/android/media/tv/ad/TvAdServiceInfo.aidl
new file mode 100644
index 0000000..a5e631f
--- /dev/null
+++ b/media/java/android/media/tv/ad/TvAdServiceInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+parcelable TvAdServiceInfo;
diff --git a/media/java/android/media/tv/ad/TvAdServiceInfo.java b/media/java/android/media/tv/ad/TvAdServiceInfo.java
new file mode 100644
index 0000000..ed04f1f
--- /dev/null
+++ b/media/java/android/media/tv/ad/TvAdServiceInfo.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Xml;
+
+import androidx.annotation.NonNull;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class is used to specify meta information of a TV AD service.
+ * @hide
+ */
+public final class TvAdServiceInfo implements Parcelable {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "TvAdServiceInfo";
+
+ private static final String XML_START_TAG_NAME = "tv-ad-service";
+
+ private final ResolveInfo mService;
+ private final String mId;
+ private final List<String> mTypes = new ArrayList<>();
+
+ /**
+ * Constructs a TvAdServiceInfo object.
+ *
+ * @param context the application context
+ * @param component the component name of the TvAdService
+ */
+ public TvAdServiceInfo(@NonNull Context context, @NonNull ComponentName component) {
+ if (context == null) {
+ throw new IllegalArgumentException("context cannot be null.");
+ }
+ // TODO: use a constant
+ Intent intent = new Intent("android.media.tv.ad.TvAdService").setComponent(component);
+ ResolveInfo resolveInfo = context.getPackageManager().resolveService(
+ intent, PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+ if (resolveInfo == null) {
+ throw new IllegalArgumentException("Invalid component. Can't find the service.");
+ }
+
+ ComponentName componentName = new ComponentName(resolveInfo.serviceInfo.packageName,
+ resolveInfo.serviceInfo.name);
+ String id;
+ id = generateAdServiceId(componentName);
+ List<String> types = new ArrayList<>();
+ parseServiceMetadata(resolveInfo, context, types);
+
+ mService = resolveInfo;
+ mId = id;
+ }
+
+ private TvAdServiceInfo(ResolveInfo service, String id, List<String> types) {
+ mService = service;
+ mId = id;
+ mTypes.addAll(types);
+ }
+
+ private TvAdServiceInfo(@NonNull Parcel in) {
+ mService = ResolveInfo.CREATOR.createFromParcel(in);
+ mId = in.readString();
+ in.readStringList(mTypes);
+ }
+
+ public static final Creator<TvAdServiceInfo> CREATOR = new Creator<TvAdServiceInfo>() {
+ @Override
+ public TvAdServiceInfo createFromParcel(Parcel in) {
+ return new TvAdServiceInfo(in);
+ }
+
+ @Override
+ public TvAdServiceInfo[] newArray(int size) {
+ return new TvAdServiceInfo[size];
+ }
+ };
+
+ /**
+ * Returns a unique ID for this TV AD service. The ID is generated from the package and class
+ * name implementing the TV AD service.
+ */
+ @NonNull
+ public String getId() {
+ return mId;
+ }
+
+ /**
+ * Returns the component of the TV AD service.
+ * @hide
+ */
+ public ComponentName getComponent() {
+ return new ComponentName(mService.serviceInfo.packageName, mService.serviceInfo.name);
+ }
+
+ /**
+ * Returns the information of the service that implements this AD service.
+ */
+ @Nullable
+ public ServiceInfo getServiceInfo() {
+ return mService.serviceInfo;
+ }
+
+ /**
+ * Gets supported TV AD types.
+ */
+ @NonNull
+ public List<String> getSupportedTypes() {
+ return mTypes;
+ }
+
+ private static String generateAdServiceId(ComponentName name) {
+ return name.flattenToShortString();
+ }
+
+ private static void parseServiceMetadata(
+ ResolveInfo resolveInfo, Context context, List<String> types) {
+ ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+ PackageManager pm = context.getPackageManager();
+ // TODO: use constant for the metadata
+ try (XmlResourceParser parser =
+ serviceInfo.loadXmlMetaData(pm, "android.media.tv.ad.service")) {
+ if (parser == null) {
+ throw new IllegalStateException(
+ "No " + "android.media.tv.ad.service"
+ + " meta-data found for " + serviceInfo.name);
+ }
+
+ Resources resources = pm.getResourcesForApplication(serviceInfo.applicationInfo);
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ // move to the START_TAG
+ }
+
+ String nodeName = parser.getName();
+ if (!XML_START_TAG_NAME.equals(nodeName)) {
+ throw new IllegalStateException("Meta-data does not start with "
+ + XML_START_TAG_NAME + " tag for " + serviceInfo.name);
+ }
+
+ // TODO: parse attributes
+ } catch (IOException | XmlPullParserException e) {
+ throw new IllegalStateException(
+ "Failed reading meta-data for " + serviceInfo.packageName, e);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new IllegalStateException("No resources found for " + serviceInfo.packageName, e);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ mService.writeToParcel(dest, flags);
+ dest.writeString(mId);
+ dest.writeStringList(mTypes);
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index 33c084e..3928767 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -78,7 +78,10 @@
import androidx.compose.ui.unit.times
import com.android.compose.PlatformButton
import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneKey as SceneTransitionLayoutSceneKey
import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.animation.scene.transitions
import com.android.compose.modifiers.thenIf
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
@@ -90,6 +93,8 @@
import com.android.systemui.common.shared.model.Text.Companion.loadText
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.fold.ui.composable.FoldPosture
+import com.android.systemui.fold.ui.composable.foldPosture
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.SceneKey
@@ -159,28 +164,27 @@
when (layout) {
Layout.STANDARD ->
- Bouncer(
+ StandardLayout(
viewModel = viewModel,
dialogFactory = dialogFactory,
- userInputAreaVisibility = UserInputAreaVisibility.FULL,
modifier = childModifier,
)
Layout.SIDE_BY_SIDE ->
- SideBySide(
+ SideBySideLayout(
viewModel = viewModel,
dialogFactory = dialogFactory,
isUserSwitcherVisible = isFullScreenUserSwitcherEnabled,
modifier = childModifier,
)
Layout.STACKED ->
- Stacked(
+ StackedLayout(
viewModel = viewModel,
dialogFactory = dialogFactory,
isUserSwitcherVisible = isFullScreenUserSwitcherEnabled,
modifier = childModifier,
)
Layout.SPLIT ->
- Split(
+ SplitLayout(
viewModel = viewModel,
dialogFactory = dialogFactory,
modifier = childModifier,
@@ -194,59 +198,150 @@
* authentication attempt, including all messaging UI (directives, reasoning, errors, etc.).
*/
@Composable
-private fun Bouncer(
+private fun StandardLayout(
viewModel: BouncerViewModel,
dialogFactory: BouncerSceneDialogFactory,
- userInputAreaVisibility: UserInputAreaVisibility,
+ modifier: Modifier = Modifier,
+ outputOnly: Boolean = false,
+) {
+ val foldPosture: FoldPosture by foldPosture()
+ val isSplitAroundTheFoldRequired by viewModel.isFoldSplitRequired.collectAsState()
+ val isSplitAroundTheFold =
+ foldPosture == FoldPosture.Tabletop && !outputOnly && isSplitAroundTheFoldRequired
+ val currentSceneKey by
+ remember(isSplitAroundTheFold) {
+ mutableStateOf(
+ if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey
+ )
+ }
+
+ SceneTransitionLayout(
+ currentScene = currentSceneKey,
+ onChangeScene = {},
+ transitions = SceneTransitions,
+ modifier = modifier,
+ ) {
+ scene(SceneKeys.ContiguousSceneKey) {
+ FoldSplittable(
+ viewModel = viewModel,
+ dialogFactory = dialogFactory,
+ outputOnly = outputOnly,
+ isSplit = false,
+ )
+ }
+
+ scene(SceneKeys.SplitSceneKey) {
+ FoldSplittable(
+ viewModel = viewModel,
+ dialogFactory = dialogFactory,
+ outputOnly = outputOnly,
+ isSplit = true,
+ )
+ }
+ }
+}
+
+/**
+ * Renders the "standard" layout of the bouncer, where the bouncer is rendered on its own (no user
+ * switcher UI) and laid out vertically, centered horizontally.
+ *
+ * If [isSplit] is `true`, the top and bottom parts of the bouncer are split such that they don't
+ * render across the location of the fold hardware when the device is fully or part-way unfolded
+ * with the fold hinge in a horizontal position.
+ *
+ * If [outputOnly] is `true`, only the "output" part of the UI is shown (where the entered PIN
+ * "shapes" appear), if `false`, the entire UI is shown, including the area where the user can enter
+ * their PIN or pattern.
+ */
+@Composable
+private fun SceneScope.FoldSplittable(
+ viewModel: BouncerViewModel,
+ dialogFactory: BouncerSceneDialogFactory,
+ outputOnly: Boolean,
+ isSplit: Boolean,
modifier: Modifier = Modifier,
) {
val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState()
val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState()
var dialog: Dialog? by remember { mutableStateOf(null) }
val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState()
+ val splitRatio =
+ LocalContext.current.resources.getFloat(
+ R.dimen.motion_layout_half_fold_bouncer_height_ratio
+ )
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- modifier = modifier.padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 0.dp)
- ) {
- Crossfade(
- targetState = message,
- label = "Bouncer message",
- animationSpec = if (message.isUpdateAnimated) tween() else snap(),
- ) { message ->
- Text(
- text = message.text,
- color = MaterialTheme.colorScheme.onSurface,
- style = MaterialTheme.typography.bodyLarge,
- )
- }
+ Column(modifier = modifier.padding(horizontal = 32.dp)) {
+ // Content above the fold, when split on a foldable device in a "table top" posture:
+ Box(
+ modifier =
+ Modifier.element(SceneElements.AboveFold).fillMaxWidth().thenIf(isSplit) {
+ Modifier.weight(splitRatio)
+ },
+ ) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.fillMaxWidth().padding(top = 92.dp),
+ ) {
+ Crossfade(
+ targetState = message,
+ label = "Bouncer message",
+ animationSpec = if (message.isUpdateAnimated) tween() else snap(),
+ ) { message ->
+ Text(
+ text = message.text,
+ color = MaterialTheme.colorScheme.onSurface,
+ style = MaterialTheme.typography.bodyLarge,
+ )
+ }
- Spacer(Modifier.heightIn(min = 21.dp, max = 48.dp))
+ Spacer(Modifier.heightIn(min = 21.dp, max = 48.dp))
- Box(Modifier.weight(1f)) {
- UserInputArea(
- viewModel = viewModel,
- visibility = userInputAreaVisibility,
- modifier = Modifier.align(Alignment.Center),
- )
- }
-
- Spacer(Modifier.heightIn(min = 21.dp, max = 48.dp))
-
- val actionButtonModifier = Modifier.height(56.dp)
-
- actionButton.let { actionButtonViewModel ->
- if (actionButtonViewModel != null) {
- BouncerActionButton(
- viewModel = actionButtonViewModel,
- modifier = actionButtonModifier,
+ UserInputArea(
+ viewModel = viewModel,
+ visibility = UserInputAreaVisibility.OUTPUT_ONLY,
)
- } else {
- Spacer(modifier = actionButtonModifier)
}
}
- Spacer(Modifier.height(48.dp))
+ // Content below the fold, when split on a foldable device in a "table top" posture:
+ Box(
+ modifier =
+ Modifier.element(SceneElements.BelowFold).fillMaxWidth().thenIf(isSplit) {
+ Modifier.weight(1 - splitRatio)
+ },
+ ) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ if (!outputOnly) {
+ Box(Modifier.weight(1f)) {
+ UserInputArea(
+ viewModel = viewModel,
+ visibility = UserInputAreaVisibility.INPUT_ONLY,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
+ }
+
+ Spacer(Modifier.heightIn(min = 21.dp, max = 48.dp))
+
+ val actionButtonModifier = Modifier.height(56.dp)
+
+ actionButton.let { actionButtonViewModel ->
+ if (actionButtonViewModel != null) {
+ BouncerActionButton(
+ viewModel = actionButtonViewModel,
+ modifier = actionButtonModifier,
+ )
+ } else {
+ Spacer(modifier = actionButtonModifier)
+ }
+ }
+
+ Spacer(Modifier.height(48.dp))
+ }
+ }
if (dialogMessage != null) {
if (dialog == null) {
@@ -288,8 +383,8 @@
when (val nonNullViewModel = authMethodViewModel) {
is PinBouncerViewModel ->
when (visibility) {
- UserInputAreaVisibility.FULL ->
- PinBouncer(
+ UserInputAreaVisibility.OUTPUT_ONLY ->
+ PinInputDisplay(
viewModel = nonNullViewModel,
modifier = modifier,
)
@@ -298,34 +393,21 @@
viewModel = nonNullViewModel,
modifier = modifier,
)
- UserInputAreaVisibility.OUTPUT_ONLY ->
- PinInputDisplay(
- viewModel = nonNullViewModel,
- modifier = modifier,
- )
- UserInputAreaVisibility.NONE -> {}
}
is PasswordBouncerViewModel ->
- when (visibility) {
- UserInputAreaVisibility.FULL,
- UserInputAreaVisibility.INPUT_ONLY ->
- PasswordBouncer(
- viewModel = nonNullViewModel,
- modifier = modifier,
- )
- else -> {}
+ if (visibility == UserInputAreaVisibility.INPUT_ONLY) {
+ PasswordBouncer(
+ viewModel = nonNullViewModel,
+ modifier = modifier,
+ )
}
is PatternBouncerViewModel ->
- when (visibility) {
- UserInputAreaVisibility.FULL,
- UserInputAreaVisibility.INPUT_ONLY ->
- PatternBouncer(
- viewModel = nonNullViewModel,
- modifier =
- Modifier.aspectRatio(1f, matchHeightConstraintsFirst = false)
- .then(modifier)
- )
- else -> {}
+ if (visibility == UserInputAreaVisibility.INPUT_ONLY) {
+ PatternBouncer(
+ viewModel = nonNullViewModel,
+ modifier =
+ Modifier.aspectRatio(1f, matchHeightConstraintsFirst = false).then(modifier)
+ )
}
else -> Unit
}
@@ -492,17 +574,17 @@
* by double-tapping on the side.
*/
@Composable
-private fun Split(
+private fun SplitLayout(
viewModel: BouncerViewModel,
dialogFactory: BouncerSceneDialogFactory,
modifier: Modifier = Modifier,
) {
SwappableLayout(
startContent = { startContentModifier ->
- Bouncer(
+ StandardLayout(
viewModel = viewModel,
dialogFactory = dialogFactory,
- userInputAreaVisibility = UserInputAreaVisibility.OUTPUT_ONLY,
+ outputOnly = true,
modifier = startContentModifier,
)
},
@@ -595,7 +677,7 @@
* rendering of the bouncer will be used instead of the side-by-side layout.
*/
@Composable
-private fun SideBySide(
+private fun SideBySideLayout(
viewModel: BouncerViewModel,
dialogFactory: BouncerSceneDialogFactory,
isUserSwitcherVisible: Boolean,
@@ -615,10 +697,9 @@
}
},
endContent = { endContentModifier ->
- Bouncer(
+ StandardLayout(
viewModel = viewModel,
dialogFactory = dialogFactory,
- userInputAreaVisibility = UserInputAreaVisibility.FULL,
modifier = endContentModifier,
)
},
@@ -628,7 +709,7 @@
/** Arranges the bouncer contents and user switcher contents one on top of the other, vertically. */
@Composable
-private fun Stacked(
+private fun StackedLayout(
viewModel: BouncerViewModel,
dialogFactory: BouncerSceneDialogFactory,
isUserSwitcherVisible: Boolean,
@@ -644,10 +725,9 @@
)
}
- Bouncer(
+ StandardLayout(
viewModel = viewModel,
dialogFactory = dialogFactory,
- userInputAreaVisibility = UserInputAreaVisibility.FULL,
modifier = Modifier.fillMaxWidth().weight(1f),
)
}
@@ -708,11 +788,6 @@
/** Enumerates all supported user-input area visibilities. */
private enum class UserInputAreaVisibility {
/**
- * The entire user input area is shown, including where the user enters input and where it's
- * reflected to the user.
- */
- FULL,
- /**
* Only the area where the user enters the input is shown; the area where the input is reflected
* back to the user is not shown.
*/
@@ -722,8 +797,6 @@
* input is entered by the user is not shown.
*/
OUTPUT_ONLY,
- /** The entire user input area is hidden. */
- NONE,
}
/**
@@ -758,3 +831,17 @@
private val SelectedUserImageSize = 190.dp
private val UserSwitcherDropdownWidth = SelectedUserImageSize + 2 * 29.dp
private val UserSwitcherDropdownHeight = 60.dp
+
+private object SceneKeys {
+ val ContiguousSceneKey = SceneTransitionLayoutSceneKey("default")
+ val SplitSceneKey = SceneTransitionLayoutSceneKey("split")
+}
+
+private object SceneElements {
+ val AboveFold = ElementKey("above_fold")
+ val BelowFold = ElementKey("below_fold")
+}
+
+private val SceneTransitions = transitions {
+ from(SceneKeys.ContiguousSceneKey, to = SceneKeys.SplitSceneKey) { spec = tween() }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
index 5b9ad4d..fb50f69 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
@@ -24,14 +24,10 @@
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
-import androidx.compose.foundation.gestures.awaitEachGesture
-import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.heightIn
-import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.sizeIn
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -69,34 +65,13 @@
import kotlinx.coroutines.launch
@Composable
-internal fun PinBouncer(
+fun PinPad(
viewModel: PinBouncerViewModel,
modifier: Modifier = Modifier,
) {
// Report that the UI is shown to let the view-model run some logic.
LaunchedEffect(Unit) { viewModel.onShown() }
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- modifier =
- modifier.pointerInput(Unit) {
- awaitEachGesture {
- awaitFirstDown()
- viewModel.onDown()
- }
- }
- ) {
- PinInputDisplay(viewModel)
- Spacer(Modifier.heightIn(min = 34.dp, max = 48.dp))
- PinPad(viewModel)
- }
-}
-
-@Composable
-fun PinPad(
- viewModel: PinBouncerViewModel,
- modifier: Modifier = Modifier,
-) {
val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
val backspaceButtonAppearance by viewModel.backspaceButtonAppearance.collectAsState()
val confirmButtonAppearance by viewModel.confirmButtonAppearance.collectAsState()
@@ -298,7 +273,8 @@
contentAlignment = Alignment.Center,
modifier =
modifier
- .size(pinButtonSize)
+ .sizeIn(maxWidth = pinButtonSize, maxHeight = pinButtonSize)
+ .aspectRatio(1f)
.drawBehind {
drawRoundRect(
color = containerColor,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt
new file mode 100644
index 0000000..1c993cf
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.fold.ui.composable
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.produceState
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import androidx.window.layout.FoldingFeature
+import androidx.window.layout.WindowInfoTracker
+
+sealed interface FoldPosture {
+ /** A foldable device that's fully closed/folded or a device that doesn't support folding. */
+ data object Folded : FoldPosture
+ /** A foldable that's halfway open with the hinge held vertically. */
+ data object Book : FoldPosture
+ /** A foldable that's halfway open with the hinge held horizontally. */
+ data object Tabletop : FoldPosture
+ /** A foldable that's fully unfolded / flat. */
+ data object FullyUnfolded : FoldPosture
+}
+
+/** Returns the [FoldPosture] of the device currently. */
+@Composable
+fun foldPosture(): State<FoldPosture> {
+ val context = LocalContext.current
+ val infoTracker = remember(context) { WindowInfoTracker.getOrCreate(context) }
+ val layoutInfo by infoTracker.windowLayoutInfo(context).collectAsState(initial = null)
+
+ return produceState<FoldPosture>(
+ initialValue = FoldPosture.Folded,
+ key1 = layoutInfo,
+ ) {
+ value =
+ layoutInfo
+ ?.displayFeatures
+ ?.firstNotNullOfOrNull { it as? FoldingFeature }
+ .let { foldingFeature ->
+ when (foldingFeature?.state) {
+ null -> FoldPosture.Folded
+ FoldingFeature.State.HALF_OPENED ->
+ foldingFeature.orientation.toHalfwayPosture()
+ FoldingFeature.State.FLAT ->
+ if (foldingFeature.isSeparating) {
+ // Dual screen device.
+ foldingFeature.orientation.toHalfwayPosture()
+ } else {
+ FoldPosture.FullyUnfolded
+ }
+ else -> error("Unsupported state \"${foldingFeature.state}\"")
+ }
+ }
+ }
+}
+
+private fun FoldingFeature.Orientation.toHalfwayPosture(): FoldPosture {
+ return when (this) {
+ FoldingFeature.Orientation.HORIZONTAL -> FoldPosture.Tabletop
+ FoldingFeature.Orientation.VERTICAL -> FoldPosture.Book
+ else -> error("Unsupported orientation \"$this\"")
+ }
+}
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8780f58..9a3c6d5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3266,4 +3266,9 @@
<string name="privacy_dialog_active_app_usage_2">In use by <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> (<xliff:g id="attribution_label" example="For Wallet">%2$s</xliff:g> \u2022 <xliff:g id="proxy_label" example="Speech services">%3$s</xliff:g>)</string>
<!-- Label for recent app usage of a phone sensor with sub-attribution and proxy label in the privacy dialog [CHAR LIMIT=NONE] -->
<string name="privacy_dialog_recent_app_usage_2">Recently used by <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> (<xliff:g id="attribution_label" example="For Wallet">%2$s</xliff:g> \u2022 <xliff:g id="proxy_label" example="Speech services">%3$s</xliff:g>)</string>
+
+ <!-- Content description for keyboard backlight brightness dialog [CHAR LIMIT=NONE] -->
+ <string name="keyboard_backlight_dialog_title">Keyboard backlight</string>
+ <!-- Content description for keyboard backlight brightness value [CHAR LIMIT=NONE] -->
+ <string name="keyboard_backlight_value">Level %1$d of %2$d</string>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index 80be008..e8b16db 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -65,7 +65,7 @@
* Note that the length of the PIN is also important to take into consideration, please see
* [hintedPinLength].
*/
- val isAutoConfirmEnabled: StateFlow<Boolean>
+ val isAutoConfirmFeatureEnabled: StateFlow<Boolean>
/**
* The exact length a PIN should be for us to enable PIN length hinting.
@@ -152,14 +152,14 @@
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
- private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>,
@Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>,
private val userRepository: UserRepository,
private val lockPatternUtils: LockPatternUtils,
broadcastDispatcher: BroadcastDispatcher,
) : AuthenticationRepository {
- override val isAutoConfirmEnabled: StateFlow<Boolean> =
+ override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> =
refreshingFlow(
initialValue = false,
getFreshValue = lockPatternUtils::isAutoPinConfirmEnabled,
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 345f15c..22b44d5 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
@@ -42,6 +42,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.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -103,9 +104,29 @@
initialValue = throttling.value.remainingMs > 0,
)
+ /**
+ * Whether the auto confirm feature is enabled for the currently-selected user.
+ *
+ * Note that the length of the PIN is also important to take into consideration, please see
+ * [hintedPinLength].
+ *
+ * During throttling, this is always disabled (`false`).
+ */
+ val isAutoConfirmEnabled: StateFlow<Boolean> =
+ combine(repository.isAutoConfirmFeatureEnabled, isThrottled) { featureEnabled, isThrottled
+ ->
+ // Disable auto-confirm during throttling.
+ featureEnabled && !isThrottled
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
/** The length of the hinted PIN, or `null` if pin length hint should not be shown. */
val hintedPinLength: StateFlow<Int?> =
- repository.isAutoConfirmEnabled
+ isAutoConfirmEnabled
.map { isAutoConfirmEnabled ->
repository.getPinLength().takeIf {
isAutoConfirmEnabled && it == repository.hintedPinLength
@@ -119,9 +140,6 @@
initialValue = null,
)
- /** Whether the auto confirm feature is enabled for the currently-selected user. */
- val isAutoConfirmEnabled: StateFlow<Boolean> = repository.isAutoConfirmEnabled
-
/** Whether the pattern should be visible for the currently-selected user. */
val isPatternVisible: StateFlow<Boolean> = repository.isPatternVisible
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 73d15f0..4767e21 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -180,6 +180,19 @@
initialValue = isSideBySideSupported(authMethodViewModel.value),
)
+ /**
+ * Whether the splitting the UI around the fold seam (where the hinge is on a foldable device)
+ * is required.
+ */
+ val isFoldSplitRequired: StateFlow<Boolean> =
+ authMethodViewModel
+ .map { authMethod -> isFoldSplitRequired(authMethod) }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = isFoldSplitRequired(authMethodViewModel.value),
+ )
+
init {
if (flags.isEnabled()) {
applicationScope.launch {
@@ -212,6 +225,10 @@
return isUserSwitcherVisible || authMethod !is PasswordBouncerViewModel
}
+ private fun isFoldSplitRequired(authMethod: AuthMethodBouncerViewModel?): Boolean {
+ return authMethod !is PasswordBouncerViewModel
+ }
+
private fun toMessageViewModel(
message: String?,
isThrottled: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index 6b27ce0..f7fee96 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -18,13 +18,11 @@
import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
-import android.appwidget.AppWidgetProviderInfo
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
-import android.content.pm.PackageManager
import android.os.UserManager
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -32,13 +30,10 @@
import com.android.systemui.communal.data.db.CommunalWidgetDao
import com.android.systemui.communal.data.db.CommunalWidgetItem
import com.android.systemui.communal.shared.CommunalWidgetHost
-import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
@@ -58,9 +53,6 @@
/** Encapsulates the state of widgets for communal mode. */
interface CommunalWidgetRepository {
- /** A flow of provider info for the stopwatch widget, or null if widget is unavailable. */
- val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?>
-
/** A flow of information about active communal widgets stored in database. */
val communalWidgets: Flow<List<CommunalWidgetContentModel>>
@@ -84,15 +76,12 @@
communalRepository: CommunalRepository,
private val communalWidgetHost: CommunalWidgetHost,
private val communalWidgetDao: CommunalWidgetDao,
- private val packageManager: PackageManager,
private val userManager: UserManager,
private val userTracker: UserTracker,
@CommunalLog logBuffer: LogBuffer,
- featureFlags: FeatureFlagsClassic,
) : CommunalWidgetRepository {
companion object {
const val TAG = "CommunalWidgetRepository"
- const val WIDGET_LABEL = "Stopwatch"
}
private val logger = Logger(logBuffer, TAG)
@@ -100,9 +89,6 @@
// Whether the [AppWidgetHost] is listening for updates.
private var isHostListening = false
- // Widgets that should be rendered in communal mode.
- private val widgets: HashMap<Int, CommunalAppWidgetInfo> = hashMapOf()
-
private val isUserUnlocked: Flow<Boolean> =
callbackFlow {
if (!communalRepository.isCommunalEnabled) {
@@ -149,25 +135,6 @@
}
}
- override val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> =
- isHostActive.map { isHostActive ->
- if (!isHostActive || !featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)) {
- return@map null
- }
-
- val providerInfo =
- appWidgetManager.installedProviders.find {
- it.loadLabel(packageManager).equals(WIDGET_LABEL)
- }
-
- if (providerInfo == null) {
- logger.w("Cannot find app widget: $WIDGET_LABEL")
- return@map null
- }
-
- return@map addStopWatchWidget(providerInfo)
- }
-
override val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
isHostActive.flatMapLatest { isHostActive ->
if (!isHostActive) {
@@ -226,21 +193,4 @@
appWidgetHost.stopListening()
isHostListening = false
}
-
- // TODO(b/306471933): remove this prototype that shows a stopwatch in the communal blueprint
- private fun addStopWatchWidget(providerInfo: AppWidgetProviderInfo): CommunalAppWidgetInfo {
- val existing = widgets.values.firstOrNull { it.providerInfo == providerInfo }
- if (existing != null) {
- return existing
- }
-
- val appWidgetId = appWidgetHost.allocateAppWidgetId()
- val widget =
- CommunalAppWidgetInfo(
- providerInfo,
- appWidgetId,
- )
- widgets[appWidgetId] = widget
- return widget
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index eb36b19..508f52c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -23,7 +23,6 @@
import com.android.systemui.communal.data.repository.CommunalRepository
import com.android.systemui.communal.data.repository.CommunalWidgetRepository
import com.android.systemui.communal.domain.model.CommunalContentModel
-import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.dagger.SysUISingleton
@@ -55,9 +54,6 @@
val isCommunalEnabled: Boolean
get() = communalRepository.isCommunalEnabled
- /** A flow of info about the widget to be displayed, or null if widget is unavailable. */
- val appWidgetInfo: Flow<CommunalAppWidgetInfo?> = widgetRepository.stopwatchAppWidgetInfo
-
/**
* Target scene as requested by the underlying [SceneTransitionLayout] or through
* [onSceneChanged].
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalWidgetViewBinder.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalWidgetViewBinder.kt
deleted file mode 100644
index 65bf4b3..0000000
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalWidgetViewBinder.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.communal.ui.binder
-
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.res.R
-import com.android.systemui.communal.ui.adapter.CommunalWidgetViewAdapter
-import com.android.systemui.communal.ui.view.CommunalWidgetWrapper
-import com.android.systemui.communal.ui.viewmodel.CommunalWidgetViewModel
-import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
-import com.android.systemui.keyguard.ui.view.KeyguardRootView
-import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.launch
-
-/** Binds [CommunalWidgetViewModel] to the keyguard root view. */
-object CommunalWidgetViewBinder {
-
- @JvmStatic
- fun bind(
- rootView: KeyguardRootView,
- viewModel: CommunalWidgetViewModel,
- adapter: CommunalWidgetViewAdapter,
- keyguardBlueprintInteractor: KeyguardBlueprintInteractor,
- ) {
- rootView.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
- adapter.adapt(viewModel.appWidgetInfo).collect {
- val oldView =
- rootView.findViewById<CommunalWidgetWrapper>(
- R.id.communal_widget_wrapper
- )
- var dirty = false
-
- if (oldView != null) {
- rootView.removeView(oldView)
- dirty = true
- }
-
- if (it != null) {
- rootView.addView(it)
- dirty = true
- }
-
- if (dirty) {
- keyguardBlueprintInteractor.refreshBlueprint()
- }
- }
- }
-
- launch { viewModel.alpha.collect { rootView.alpha = it } }
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt
index 702554a..9198c7b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt
@@ -17,7 +17,6 @@
package com.android.systemui.communal.ui.view.layout.blueprints
import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalHubSection
-import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalWidgetSection
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
@@ -30,13 +29,11 @@
@Inject
constructor(
defaultCommunalHubSection: DefaultCommunalHubSection,
- defaultCommunalWidgetSection: DefaultCommunalWidgetSection,
) : KeyguardBlueprint {
override val id: String = COMMUNAL
override val sections: List<KeyguardSection> =
listOf(
defaultCommunalHubSection,
- defaultCommunalWidgetSection,
)
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalWidgetSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalWidgetSection.kt
deleted file mode 100644
index c3e8a96..0000000
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalWidgetSection.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.communal.ui.view.layout.sections
-
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
-import androidx.constraintlayout.widget.ConstraintSet.END
-import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
-import com.android.systemui.res.R
-import com.android.systemui.communal.ui.adapter.CommunalWidgetViewAdapter
-import com.android.systemui.communal.ui.binder.CommunalWidgetViewBinder
-import com.android.systemui.communal.ui.viewmodel.CommunalWidgetViewModel
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardSection
-import com.android.systemui.keyguard.ui.view.KeyguardRootView
-import dagger.Lazy
-import javax.inject.Inject
-
-class DefaultCommunalWidgetSection
-@Inject
-constructor(
- private val featureFlags: FeatureFlags,
- private val keyguardRootView: KeyguardRootView,
- private val communalWidgetViewModel: CommunalWidgetViewModel,
- private val communalWidgetViewAdapter: CommunalWidgetViewAdapter,
- private val keyguardBlueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
-) : KeyguardSection() {
- private val widgetAreaViewId = R.id.communal_widget_wrapper
-
- override fun addViews(constraintLayout: ConstraintLayout) {}
-
- override fun bindData(constraintLayout: ConstraintLayout) {
- if (!featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)) {
- return
- }
-
- CommunalWidgetViewBinder.bind(
- keyguardRootView,
- communalWidgetViewModel,
- communalWidgetViewAdapter,
- keyguardBlueprintInteractor.get(),
- )
- }
-
- override fun applyConstraints(constraintSet: ConstraintSet) {
- constraintSet.apply {
- constrainWidth(widgetAreaViewId, WRAP_CONTENT)
- constrainHeight(widgetAreaViewId, WRAP_CONTENT)
- connect(widgetAreaViewId, BOTTOM, PARENT_ID, BOTTOM)
- connect(widgetAreaViewId, END, PARENT_ID, END)
- }
- }
-
- override fun removeViews(constraintLayout: ConstraintLayout) {}
-}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt
deleted file mode 100644
index d7bbea6..0000000
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.communal.ui.viewmodel
-
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-@SysUISingleton
-class CommunalWidgetViewModel
-@Inject
-constructor(
- communalInteractor: CommunalInteractor,
- keyguardBottomAreaViewModel: KeyguardBottomAreaViewModel,
-) {
- /** An observable for the alpha level for the communal widget area. */
- val alpha: Flow<Float> = keyguardBottomAreaViewModel.alpha
-
- /** A flow of info about the widget to be displayed, or null if widget is unavailable. */
- val appWidgetInfo: Flow<CommunalAppWidgetInfo?> = communalInteractor.appWidgetInfo
-}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index b237b87..d64a131 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -239,8 +239,7 @@
/** Provide new auth messages on the bouncer. */
// TODO(b/277961132): Tracking bug.
- @JvmField val REVAMPED_BOUNCER_MESSAGES = unreleasedFlag("revamped_bouncer_messages",
- teamfood = true)
+ @JvmField val REVAMPED_BOUNCER_MESSAGES = unreleasedFlag("revamped_bouncer_messages")
/** Keyguard Migration */
@@ -255,9 +254,6 @@
// TODO(b/287268101): Tracking bug.
@JvmField val TRANSIT_CLOCK = releasedFlag("lockscreen_custom_transit_clock")
- // TODO(b/288276738): Tracking bug.
- @JvmField val WIDGET_ON_KEYGUARD = unreleasedFlag("widget_on_keyguard")
-
/** Migrate the NSSL to the a sibling to both the panel and keyguard root view. */
// TODO(b/288074305): Tracking bug.
@JvmField val MIGRATE_NSSL = unreleasedFlag("migrate_nssl")
@@ -776,9 +772,4 @@
@JvmField
val COMMUNAL_SERVICE_ENABLED = resourceBooleanFlag(R.bool.config_communalServiceEnabled,
"communal_service_enabled")
-
- // TODO(b/303131306): Tracking Bug
- /** Whether communal hub features are enabled. */
- @JvmField
- val COMMUNAL_HUB = unreleasedFlag("communal_hub")
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
index e16bb0b..1e9be09 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
@@ -30,6 +30,7 @@
import android.view.ViewGroup.MarginLayoutParams
import android.view.Window
import android.view.WindowManager
+import android.view.accessibility.AccessibilityEvent
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
@@ -78,23 +79,29 @@
private lateinit var stepProperties: StepViewProperties
@ColorInt
- var filledRectangleColor = getColorFromStyle(com.android.internal.R.attr.materialColorPrimary)
- @ColorInt
- var emptyRectangleColor =
- getColorFromStyle(com.android.internal.R.attr.materialColorOutlineVariant)
- @ColorInt
- var backgroundColor = getColorFromStyle(com.android.internal.R.attr.materialColorSurfaceBright)
- @ColorInt
- var defaultIconColor = getColorFromStyle(com.android.internal.R.attr.materialColorOnPrimary)
- @ColorInt
- var defaultIconBackgroundColor =
+ private val filledRectangleColor =
getColorFromStyle(com.android.internal.R.attr.materialColorPrimary)
@ColorInt
- var dimmedIconColor = getColorFromStyle(com.android.internal.R.attr.materialColorOnSurface)
+ private val emptyRectangleColor =
+ getColorFromStyle(com.android.internal.R.attr.materialColorOutlineVariant)
@ColorInt
- var dimmedIconBackgroundColor =
+ private val backgroundColor =
+ getColorFromStyle(com.android.internal.R.attr.materialColorSurfaceBright)
+ @ColorInt
+ private val defaultIconColor =
+ getColorFromStyle(com.android.internal.R.attr.materialColorOnPrimary)
+ @ColorInt
+ private val defaultIconBackgroundColor =
+ getColorFromStyle(com.android.internal.R.attr.materialColorPrimary)
+ @ColorInt
+ private val dimmedIconColor =
+ getColorFromStyle(com.android.internal.R.attr.materialColorOnSurface)
+ @ColorInt
+ private val dimmedIconBackgroundColor =
getColorFromStyle(com.android.internal.R.attr.materialColorSurfaceDim)
+ private val levelContentDescription = context.getString(R.string.keyboard_backlight_value)
+
init {
currentLevel = initialCurrentLevel
maxLevel = initialMaxLevel
@@ -103,6 +110,8 @@
override fun onCreate(savedInstanceState: Bundle?) {
setUpWindowProperties(this)
setWindowPosition()
+ // title is used for a11y announcement
+ window?.setTitle(context.getString(R.string.keyboard_backlight_dialog_title))
updateResources()
rootView = buildRootView()
setContentView(rootView)
@@ -159,6 +168,12 @@
currentLevel = current
updateIconTile()
updateStepColors()
+ updateAccessibilityInfo()
+ }
+
+ private fun updateAccessibilityInfo() {
+ rootView.contentDescription = String.format(levelContentDescription, currentLevel, maxLevel)
+ rootView.sendAccessibilityEvent(AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION)
}
private fun updateIconTile() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
index 30b87cc..f9e0b16 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
@@ -18,7 +18,10 @@
import android.content.Context
import android.service.quicksettings.Tile
+import android.view.View
+import android.widget.Switch
import com.android.systemui.common.shared.model.Icon
+import kotlin.reflect.KClass
/**
* Represents current a state of the tile to be displayed in on the view. Consider using
@@ -111,7 +114,7 @@
var stateDescription: CharSequence? = null
var sideViewIcon: SideViewIcon = SideViewIcon.None
var enabledState: EnabledState = EnabledState.ENABLED
- var expandedAccessibilityClassName: String? = null
+ var expandedAccessibilityClass: KClass<out View>? = Switch::class
fun build(): QSTileState =
QSTileState(
@@ -124,7 +127,7 @@
stateDescription,
sideViewIcon,
enabledState,
- expandedAccessibilityClassName,
+ expandedAccessibilityClass?.qualifiedName,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt
index ffbc10a..2336a8e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt
@@ -16,6 +16,9 @@
package com.android.systemui.util.kotlin
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
class Utils {
companion object {
fun <A, B, C> toTriple(a: A, bc: Pair<B, C>) = Triple(a, bc.first, bc.second)
@@ -27,6 +30,45 @@
fun <A, B, C, D, E> toQuint(a: A, bcde: Quad<B, C, D, E>) =
Quint(a, bcde.first, bcde.second, bcde.third, bcde.fourth)
+
+ /**
+ * Samples the provided flows, emitting a tuple of the original flow's value as well as each
+ * of the combined flows' values.
+ *
+ * Flow<A>.sample(Flow<B>, Flow<C>) -> (A, B, C)
+ */
+ fun <A, B, C> Flow<A>.sample(b: Flow<B>, c: Flow<C>): Flow<Triple<A, B, C>> {
+ return this.sample(combine(b, c, ::Pair), ::toTriple)
+ }
+
+ /**
+ * Samples the provided flows, emitting a tuple of the original flow's value as well as each
+ * of the combined flows' values.
+ *
+ * Flow<A>.sample(Flow<B>, Flow<C>, Flow<D>) -> (A, B, C, D)
+ */
+ fun <A, B, C, D> Flow<A>.sample(
+ b: Flow<B>,
+ c: Flow<C>,
+ d: Flow<D>
+ ): Flow<Quad<A, B, C, D>> {
+ return this.sample(combine(b, c, d, ::Triple), ::toQuad)
+ }
+
+ /**
+ * Samples the provided flows, emitting a tuple of the original flow's value as well as each
+ * of the combined flows' values.
+ *
+ * Flow<A>.sample(Flow<B>, Flow<C>, Flow<D>, Flow<E>) -> (A, B, C, D, E)
+ */
+ fun <A, B, C, D, E> Flow<A>.sample(
+ b: Flow<B>,
+ c: Flow<C>,
+ d: Flow<D>,
+ e: Flow<E>,
+ ): Flow<Quint<A, B, C, D, E>> {
+ return this.sample(combine(b, c, d, e, ::Quad), ::toQuint)
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
index 6ac84bc..0c06808 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
@@ -100,12 +100,12 @@
}
@Test
- fun isAutoConfirmEnabled() =
+ fun isAutoConfirmFeatureEnabled() =
testScope.runTest {
whenever(lockPatternUtils.isAutoPinConfirmEnabled(USER_INFOS[0].id)).thenReturn(true)
whenever(lockPatternUtils.isAutoPinConfirmEnabled(USER_INFOS[1].id)).thenReturn(false)
- val values by collectValues(underTest.isAutoConfirmEnabled)
+ val values by collectValues(underTest.isAutoConfirmFeatureEnabled)
assertThat(values.first()).isFalse()
assertThat(values.last()).isTrue()
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 2f5f460..103f2b8 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
@@ -213,11 +213,14 @@
@Test
fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNullAndHasNoEffect() =
testScope.runTest {
+ val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
val isThrottled by collectLastValue(underTest.isThrottled)
utils.authenticationRepository.apply {
setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
- setAutoConfirmEnabled(true)
+ setAutoConfirmFeatureEnabled(true)
}
+ assertThat(isAutoConfirmEnabled).isTrue()
+
assertThat(
underTest.authenticate(
FakeAuthenticationRepository.DEFAULT_PIN.toMutableList().apply {
@@ -233,10 +236,13 @@
@Test
fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalseAndDoesNotUnlockDevice() =
testScope.runTest {
+ val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
utils.authenticationRepository.apply {
setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
- setAutoConfirmEnabled(true)
+ setAutoConfirmFeatureEnabled(true)
}
+ assertThat(isAutoConfirmEnabled).isTrue()
+
assertThat(
underTest.authenticate(
FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 },
@@ -251,10 +257,13 @@
@Test
fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalseAndDoesNotUnlockDevice() =
testScope.runTest {
+ val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
utils.authenticationRepository.apply {
setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
- setAutoConfirmEnabled(true)
+ setAutoConfirmFeatureEnabled(true)
}
+ assertThat(isAutoConfirmEnabled).isTrue()
+
assertThat(
underTest.authenticate(
FakeAuthenticationRepository.DEFAULT_PIN + listOf(7),
@@ -269,10 +278,13 @@
@Test
fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrueAndUnlocksDevice() =
testScope.runTest {
+ val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
utils.authenticationRepository.apply {
setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
- setAutoConfirmEnabled(true)
+ setAutoConfirmFeatureEnabled(true)
}
+ assertThat(isAutoConfirmEnabled).isTrue()
+
assertThat(
underTest.authenticate(
FakeAuthenticationRepository.DEFAULT_PIN,
@@ -285,11 +297,35 @@
}
@Test
+ fun tryAutoConfirm_withAutoConfirmCorrectPinButDuringThrottling_returnsNullAndHasNoEffects() =
+ testScope.runTest {
+ val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
+ val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked)
+ val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+ utils.authenticationRepository.apply {
+ setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+ setAutoConfirmFeatureEnabled(true)
+ setThrottleDuration(42)
+ }
+
+ val authResult =
+ underTest.authenticate(
+ FakeAuthenticationRepository.DEFAULT_PIN,
+ tryAutoConfirm = true
+ )
+
+ assertThat(authResult).isEqualTo(AuthenticationResult.SKIPPED)
+ assertThat(isAutoConfirmEnabled).isFalse()
+ assertThat(isUnlocked).isFalse()
+ assertThat(hintedPinLength).isNull()
+ }
+
+ @Test
fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNullAndHasNoEffects() =
testScope.runTest {
utils.authenticationRepository.apply {
setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
- setAutoConfirmEnabled(false)
+ setAutoConfirmFeatureEnabled(false)
}
assertThat(
underTest.authenticate(
@@ -416,7 +452,7 @@
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
utils.authenticationRepository.apply {
setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
- setAutoConfirmEnabled(false)
+ setAutoConfirmFeatureEnabled(false)
}
assertThat(hintedPinLength).isNull()
@@ -433,7 +469,7 @@
repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) }
}
)
- setAutoConfirmEnabled(true)
+ setAutoConfirmFeatureEnabled(true)
}
assertThat(hintedPinLength).isNull()
@@ -445,7 +481,7 @@
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
utils.authenticationRepository.apply {
setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
- setAutoConfirmEnabled(true)
+ setAutoConfirmFeatureEnabled(true)
overrideCredential(
buildList {
repeat(utils.authenticationRepository.hintedPinLength) { add(it + 1) }
@@ -467,7 +503,7 @@
repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) }
}
)
- setAutoConfirmEnabled(true)
+ setAutoConfirmFeatureEnabled(true)
}
assertThat(hintedPinLength).isNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 915661b..5775396 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -110,12 +110,14 @@
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(underTest.message)
+ val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
runCurrent()
- utils.authenticationRepository.setAutoConfirmEnabled(true)
+ utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
utils.deviceEntryRepository.setUnlocked(false)
underTest.showOrUnlockDevice()
+ assertThat(isAutoConfirmEnabled).isTrue()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
underTest.clearMessage()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index 6ef518e..6357a1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -226,6 +226,23 @@
assertThat(isSideBySideSupported).isFalse()
}
+ @Test
+ fun isFoldSplitRequired() =
+ testScope.runTest {
+ val isFoldSplitRequired by collectLastValue(underTest.isFoldSplitRequired)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ assertThat(isFoldSplitRequired).isTrue()
+ utils.authenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Password
+ )
+ assertThat(isFoldSplitRequired).isFalse()
+
+ utils.authenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pattern
+ )
+ assertThat(isFoldSplitRequired).isTrue()
+ }
+
private fun authMethodsToTest(): List<DomainLayerAuthenticationMethodModel> {
return listOf(
DomainLayerAuthenticationMethodModel.None,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 3ddac7e..e07c0b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -247,7 +247,7 @@
fun onAutoConfirm_whenCorrect() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
- utils.authenticationRepository.setAutoConfirmEnabled(true)
+ utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
lockDeviceAndOpenPinBouncer()
FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
@@ -263,7 +263,7 @@
val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
- utils.authenticationRepository.setAutoConfirmEnabled(true)
+ utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
lockDeviceAndOpenPinBouncer()
FakeAuthenticationRepository.DEFAULT_PIN.dropLast(1).forEach { digit ->
@@ -323,7 +323,7 @@
testScope.runTest {
val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setAutoConfirmEnabled(true)
+ utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden)
}
@@ -333,7 +333,7 @@
testScope.runTest {
val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setAutoConfirmEnabled(true)
+ utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
underTest.onPinButtonClicked(1)
@@ -355,7 +355,7 @@
testScope.runTest {
val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setAutoConfirmEnabled(true)
+ utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index ca8316d..28fae81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -21,7 +21,6 @@
import android.appwidget.AppWidgetProviderInfo
import android.content.BroadcastReceiver
import android.content.ComponentName
-import android.content.pm.PackageManager
import android.os.UserHandle
import android.os.UserManager
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -34,8 +33,6 @@
import com.android.systemui.communal.shared.CommunalWidgetHost
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.FakeLogBuffer
import com.android.systemui.res.R
@@ -72,16 +69,12 @@
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
- @Mock private lateinit var packageManager: PackageManager
-
@Mock private lateinit var userManager: UserManager
@Mock private lateinit var userHandle: UserHandle
@Mock private lateinit var userTracker: UserTracker
- @Mock private lateinit var featureFlags: FeatureFlagsClassic
-
@Mock private lateinit var stopwatchProviderInfo: AppWidgetProviderInfo
@Mock private lateinit var providerInfoA: AppWidgetProviderInfo
@@ -113,13 +106,13 @@
communalRepository = FakeCommunalRepository()
communalEnabled(true)
- widgetOnKeyguardEnabled(true)
setAppWidgetIds(emptyList())
overrideResource(R.array.config_communalWidgetAllowlist, fakeAllowlist.toTypedArray())
whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch")
whenever(userTracker.userHandle).thenReturn(userHandle)
+ whenever(communalWidgetDao.getWidgets()).thenReturn(flowOf(emptyMap()))
}
@Test
@@ -213,7 +206,7 @@
testScope.runTest {
communalEnabled(false)
val repository = initCommunalWidgetRepository()
- collectLastValue(repository.stopwatchAppWidgetInfo)()
+ collectLastValue(repository.communalWidgets)()
verifyBroadcastReceiverNeverRegistered()
}
@@ -222,7 +215,7 @@
testScope.runTest {
userUnlocked(true)
val repository = initCommunalWidgetRepository()
- collectLastValue(repository.stopwatchAppWidgetInfo)()
+ collectLastValue(repository.communalWidgets)()
verifyBroadcastReceiverNeverRegistered()
}
@@ -231,7 +224,7 @@
testScope.runTest {
userUnlocked(false)
val repository = initCommunalWidgetRepository()
- collectLastValue(repository.stopwatchAppWidgetInfo)()
+ collectLastValue(repository.communalWidgets)()
verifyBroadcastReceiverRegistered()
}
@@ -241,7 +234,7 @@
userUnlocked(false)
val repository = initCommunalWidgetRepository()
- val job = launch { repository.stopwatchAppWidgetInfo.collect() }
+ val job = launch { repository.communalWidgets.collect() }
runCurrent()
val receiver = broadcastReceiverUpdate()
@@ -252,53 +245,16 @@
}
@Test
- fun stopwatch_whenUserUnlocks_receiveProviderInfo() =
- testScope.runTest {
- userUnlocked(false)
- val repository = initCommunalWidgetRepository()
- val lastStopwatchProviderInfo = collectLastValue(repository.stopwatchAppWidgetInfo)
- assertThat(lastStopwatchProviderInfo()).isNull()
-
- userUnlocked(true)
- installedProviders(listOf(stopwatchProviderInfo))
- broadcastReceiverUpdate()
-
- assertThat(lastStopwatchProviderInfo()?.providerInfo).isEqualTo(stopwatchProviderInfo)
- }
-
- @Test
- fun stopwatch_userUnlockedButWidgetNotInstalled_noProviderInfo() =
- testScope.runTest {
- userUnlocked(true)
- installedProviders(listOf())
-
- val repository = initCommunalWidgetRepository()
-
- val lastStopwatchProviderInfo = collectLastValue(repository.stopwatchAppWidgetInfo)
- assertThat(lastStopwatchProviderInfo()).isNull()
- }
-
- @Test
- fun appWidgetId_providerInfoAvailable_allocateAppWidgetId() =
- testScope.runTest {
- userUnlocked(true)
- installedProviders(listOf(stopwatchProviderInfo))
- val repository = initCommunalWidgetRepository()
- collectLastValue(repository.stopwatchAppWidgetInfo)()
- verify(appWidgetHost).allocateAppWidgetId()
- }
-
- @Test
fun appWidgetHost_userUnlocked_startListening() =
testScope.runTest {
userUnlocked(false)
val repository = initCommunalWidgetRepository()
- collectLastValue(repository.stopwatchAppWidgetInfo)()
+ collectLastValue(repository.communalWidgets)()
verify(appWidgetHost, Mockito.never()).startListening()
userUnlocked(true)
broadcastReceiverUpdate()
- collectLastValue(repository.stopwatchAppWidgetInfo)()
+ collectLastValue(repository.communalWidgets)()
verify(appWidgetHost).startListening()
}
@@ -308,18 +264,18 @@
testScope.runTest {
userUnlocked(false)
val repository = initCommunalWidgetRepository()
- collectLastValue(repository.stopwatchAppWidgetInfo)()
+ collectLastValue(repository.communalWidgets)()
userUnlocked(true)
broadcastReceiverUpdate()
- collectLastValue(repository.stopwatchAppWidgetInfo)()
+ collectLastValue(repository.communalWidgets)()
verify(appWidgetHost).startListening()
verify(appWidgetHost, Mockito.never()).stopListening()
userUnlocked(false)
broadcastReceiverUpdate()
- collectLastValue(repository.stopwatchAppWidgetInfo)()
+ collectLastValue(repository.communalWidgets)()
verify(appWidgetHost).stopListening()
}
@@ -334,11 +290,9 @@
communalRepository,
communalWidgetHost,
communalWidgetDao,
- packageManager,
userManager,
userTracker,
logBuffer,
- featureFlags,
)
}
@@ -385,10 +339,6 @@
communalRepository.setIsCommunalEnabled(enabled)
}
- private fun widgetOnKeyguardEnabled(enabled: Boolean) {
- whenever(featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)).thenReturn(enabled)
- }
-
private fun userUnlocked(userUnlocked: Boolean) {
whenever(userManager.isUserUnlockingOrUnlocked(userHandle)).thenReturn(userUnlocked)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 08d54c0..a6c4f19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -29,7 +29,6 @@
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
import com.android.systemui.communal.domain.model.CommunalContentModel
-import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.coroutines.collectLastValue
@@ -45,7 +44,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations
@@ -53,8 +51,6 @@
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class CommunalInteractorTest : SysuiTestCase() {
- @Mock private lateinit var stopwatchAppWidgetInfo: CommunalAppWidgetInfo
-
private lateinit var testScope: TestScope
private lateinit var tutorialRepository: FakeCommunalTutorialRepository
@@ -85,18 +81,6 @@
}
@Test
- fun appWidgetInfoFlow() =
- testScope.runTest {
- val lastAppWidgetInfo = collectLastValue(underTest.appWidgetInfo)
- runCurrent()
- assertThat(lastAppWidgetInfo()).isNull()
-
- widgetRepository.setStopwatchAppWidgetInfo(stopwatchAppWidgetInfo)
- runCurrent()
- assertThat(lastAppWidgetInfo()).isEqualTo(stopwatchAppWidgetInfo)
- }
-
- @Test
fun communalEnabled() =
testScope.runTest {
communalRepository.setIsCommunalEnabled(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt
index 33a6667..a496292 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt
@@ -7,7 +7,6 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalHubSection
-import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalWidgetSection
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -20,14 +19,13 @@
@SmallTest
class DefaultCommunalBlueprintTest : SysuiTestCase() {
@Mock private lateinit var hubSection: DefaultCommunalHubSection
- @Mock private lateinit var widgetSection: DefaultCommunalWidgetSection
private lateinit var blueprint: DefaultCommunalBlueprint
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- blueprint = DefaultCommunalBlueprint(hubSection, widgetSection)
+ blueprint = DefaultCommunalBlueprint(hubSection)
}
@Test
@@ -35,7 +33,6 @@
val constraintLayout = ConstraintLayout(context, null)
blueprint.replaceViews(null, constraintLayout)
verify(hubSection).addViews(constraintLayout)
- verify(widgetSection).addViews(constraintLayout)
}
@Test
@@ -43,6 +40,5 @@
val cs = ConstraintSet()
blueprint.applyConstraints(cs)
verify(hubSection).applyConstraints(cs)
- verify(widgetSection).applyConstraints(cs)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
index b5e7e2c..92c2d74 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
@@ -130,7 +130,7 @@
"sd=null, " +
"svi=None, " +
"enabled=ENABLED, " +
- "a11y=null" +
+ "a11y=android.widget.Switch" +
"], " +
"data=test_data"
)
@@ -154,7 +154,7 @@
"sd=null, " +
"svi=None, " +
"enabled=ENABLED, " +
- "a11y=null], " +
+ "a11y=android.widget.Switch], " +
"data=test_data"
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index bc28ccb..f4c05e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -376,6 +376,7 @@
mFeatureFlags.set(Flags.MIGRATE_NSSL, false);
mFeatureFlags.set(Flags.QS_USER_DETAIL_SHORTCUT, false);
mFeatureFlags.set(Flags.ONE_WAY_HAPTICS_API_MIGRATION, false);
+ mFeatureFlags.set(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT, false);
mMainDispatcher = getMainDispatcher();
KeyguardInteractorFactory.WithDependencies keyguardInteractorDeps =
KeyguardInteractorFactory.create();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
index ddfe79a..81c5d9c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -41,8 +41,9 @@
private val currentTime: () -> Long,
) : AuthenticationRepository {
- private val _isAutoConfirmEnabled = MutableStateFlow(false)
- override val isAutoConfirmEnabled: StateFlow<Boolean> = _isAutoConfirmEnabled.asStateFlow()
+ private val _isAutoConfirmFeatureEnabled = MutableStateFlow(false)
+ override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> =
+ _isAutoConfirmFeatureEnabled.asStateFlow()
override val hintedPinLength: Int = HINTING_PIN_LENGTH
@@ -98,8 +99,8 @@
_throttling.value = throttlingModel
}
- fun setAutoConfirmEnabled(isEnabled: Boolean) {
- _isAutoConfirmEnabled.value = isEnabled
+ fun setAutoConfirmFeatureEnabled(isEnabled: Boolean) {
+ _isAutoConfirmFeatureEnabled.value = isEnabled
}
override suspend fun setThrottleDuration(durationMs: Int) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index 8a2ff50..c6f12e2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -1,22 +1,14 @@
package com.android.systemui.communal.data.repository
-import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
/** Fake implementation of [CommunalWidgetRepository] */
class FakeCommunalWidgetRepository : CommunalWidgetRepository {
- private val _stopwatchAppWidgetInfo = MutableStateFlow<CommunalAppWidgetInfo?>(null)
- override val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> = _stopwatchAppWidgetInfo
-
private val _communalWidgets = MutableStateFlow<List<CommunalWidgetContentModel>>(emptyList())
override val communalWidgets: Flow<List<CommunalWidgetContentModel>> = _communalWidgets
- fun setStopwatchAppWidgetInfo(appWidgetInfo: CommunalAppWidgetInfo) {
- _stopwatchAppWidgetInfo.value = appWidgetInfo
- }
-
fun setCommunalWidgets(inventory: List<CommunalWidgetContentModel>) {
_communalWidgets.value = inventory
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 052b0c2..e5f7637 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2391,7 +2391,8 @@
long token = Binder.clearCallingIdentity();
try {
// Permissions are managed by UIDs, but unfortunately a package name is required in API.
- String packageName = ArrayUtils.firstOrNull(packageManager.getPackagesForUid(uid));
+ String packageName = ArrayUtils.firstOrNull(ArrayUtils.defeatNullable(
+ packageManager.getPackagesForUid(uid)));
if (packageName == null) {
return false;
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 1ae67dd..8171e71 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -20,6 +20,7 @@
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
import static android.os.IServiceManager.DUMP_FLAG_PROTO;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS;
import static android.provider.Settings.Secure.STYLUS_HANDWRITING_DEFAULT_VALUE;
import static android.provider.Settings.Secure.STYLUS_HANDWRITING_ENABLED;
import static android.server.inputmethod.InputMethodManagerServiceProto.BACK_DISPOSITION;
@@ -6459,6 +6460,11 @@
if (!userHasDebugPriv(userId, shellCommand)) {
continue;
}
+ // Skip on headless user
+ if (USER_TYPE_SYSTEM_HEADLESS.equals(
+ mUserManagerInternal.getUserInfo(userId).userType)) {
+ continue;
+ }
final String nextIme;
final List<InputMethodInfo> nextEnabledImes;
if (userId == mSettings.getCurrentUserId()) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index 86e417b..3b5ef4c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -77,13 +77,14 @@
void showInputMethodMenu(boolean showAuxSubtypes, int displayId) {
if (DEBUG) Slog.v(TAG, "Show switching menu. showAuxSubtypes=" + showAuxSubtypes);
- final boolean isScreenLocked = isScreenLocked();
-
- final String lastInputMethodId = mSettings.getSelectedInputMethod();
- int lastInputMethodSubtypeId = mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
- if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
-
synchronized (ImfLock.class) {
+ final boolean isScreenLocked = mWindowManagerInternal.isKeyguardLocked()
+ && mWindowManagerInternal.isKeyguardSecure(mSettings.getCurrentUserId());
+ final String lastInputMethodId = mSettings.getSelectedInputMethod();
+ int lastInputMethodSubtypeId =
+ mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
+ if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
+
final List<ImeSubtypeListItem> imList = mSwitchingController
.getSortedInputMethodAndSubtypeListForImeMenuLocked(
showAuxSubtypes, isScreenLocked);
@@ -200,12 +201,8 @@
mService.updateSystemUiLocked();
mService.sendOnNavButtonFlagsChangedLocked();
mSwitchingDialog.show();
- }
- }
- private boolean isScreenLocked() {
- return mWindowManagerInternal.isKeyguardLocked()
- && mWindowManagerInternal.isKeyguardSecure(mSettings.getCurrentUserId());
+ }
}
void updateKeyboardFromSettingsLocked() {
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index cba605e..2617703 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -60,6 +60,7 @@
import static com.android.server.pm.PackageManagerServiceUtils.compareSignatureArrays;
import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
import static com.android.server.pm.PackageManagerServiceUtils.isSystemOrRootOrShell;
+import static com.android.server.pm.parsing.PackageInfoUtils.getDeprecatedSignatures;
import static com.android.server.pm.resolution.ComponentResolver.RESOLVE_PRIORITY_SORTER;
import android.Manifest;
@@ -1531,6 +1532,7 @@
pi.applicationInfo = PackageInfoUtils.generateDelegateApplicationInfo(
ai, flags, state, userId);
pi.signingInfo = ps.getSigningInfo();
+ pi.signatures = getDeprecatedSignatures(pi.signingInfo.getSigningDetails(), flags);
if (DEBUG_PACKAGE_INFO) {
Log.v(TAG, "ps.pkg is n/a for ["
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index c26cf1c..38cde3e 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -238,21 +238,7 @@
}
final SigningDetails signingDetails = pkg.getSigningDetails();
- // deprecated method of getting signing certificates
- if ((flags & PackageManager.GET_SIGNATURES) != 0) {
- if (signingDetails.hasPastSigningCertificates()) {
- // Package has included signing certificate rotation information. Return the oldest
- // cert so that programmatic checks keep working even if unaware of key rotation.
- info.signatures = new Signature[1];
- info.signatures[0] = signingDetails.getPastSigningCertificates()[0];
- } else if (signingDetails.hasSignatures()) {
- // otherwise keep old behavior
- int numberOfSigs = signingDetails.getSignatures().length;
- info.signatures = new Signature[numberOfSigs];
- System.arraycopy(signingDetails.getSignatures(), 0, info.signatures, 0,
- numberOfSigs);
- }
- }
+ info.signatures = getDeprecatedSignatures(signingDetails, flags);
// replacement for GET_SIGNATURES
if ((flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) {
@@ -358,6 +344,30 @@
return info;
}
+ /**
+ * Retrieve the deprecated {@link PackageInfo.signatures} field of signing certificates
+ */
+ public static Signature[] getDeprecatedSignatures(SigningDetails signingDetails, long flags) {
+ if ((flags & PackageManager.GET_SIGNATURES) == 0) {
+ return null;
+ }
+ if (signingDetails.hasPastSigningCertificates()) {
+ // Package has included signing certificate rotation information. Return the oldest
+ // cert so that programmatic checks keep working even if unaware of key rotation.
+ Signature[] signatures = new Signature[1];
+ signatures[0] = signingDetails.getPastSigningCertificates()[0];
+ return signatures;
+ } else if (signingDetails.hasSignatures()) {
+ // otherwise keep old behavior
+ int numberOfSigs = signingDetails.getSignatures().length;
+ Signature[] signatures = new Signature[numberOfSigs];
+ System.arraycopy(signingDetails.getSignatures(), 0, signatures, 0,
+ numberOfSigs);
+ return signatures;
+ }
+ return null;
+ }
+
private static void updateApplicationInfo(ApplicationInfo ai, long flags,
PackageUserState state) {
if ((flags & PackageManager.GET_META_DATA) == 0) {
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 1b5631f5..a2f5a38 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -256,13 +256,24 @@
mOriginatingPendingIntent = originatingPendingIntent;
mIntent = intent;
mRealCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid);
+ if (originatingPendingIntent == null) {
+ // grant creator BAL privileges unless explicitly opted out
+ mBalAllowedByPiCreator =
+ checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+ == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
+ ? BackgroundStartPrivileges.NONE
+ : BackgroundStartPrivileges.ALLOW_BAL;
+ } else {
+ // for PendingIntents we restrict creator BAL based on target_sdk
+ mBalAllowedByPiCreator =
+ checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+ == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
+ ? BackgroundStartPrivileges.NONE
+ : BackgroundStartPrivileges.ALLOW_BAL;
+ }
mBalAllowedByPiSender =
- PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller(checkedOptions,
- realCallingUid, mRealCallingPackage);
- mBalAllowedByPiCreator =
- checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
- == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
- ? BackgroundStartPrivileges.NONE : BackgroundStartPrivileges.ALLOW_BAL;
+ PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller(
+ checkedOptions, realCallingUid, mRealCallingPackage);
mAppSwitchState = mService.getBalAppSwitchesState();
mCallingUidProcState = mService.mActiveUids.getUidState(callingUid);
mIsCallingUidPersistentSystemProcess =
@@ -306,10 +317,14 @@
return name + "[debugOnly]";
}
- private boolean isPendingIntent() {
+ private boolean hasRealCaller() {
return mRealCallingUid != NO_PROCESS_UID;
}
+ private boolean isPendingIntent() {
+ return mOriginatingPendingIntent != null;
+ }
+
private String dump(BalVerdict resultIfPiCreatorAllowsBal) {
Preconditions.checkState(!isPendingIntent());
return dump(resultIfPiCreatorAllowsBal, null);
@@ -334,7 +349,9 @@
sb.append("; isCallingUidPersistentSystemProcess: ")
.append(mIsCallingUidPersistentSystemProcess);
sb.append("; balAllowedByPiCreator: ").append(mBalAllowedByPiCreator);
- if (isPendingIntent()) {
+ sb.append("; hasRealCaller: ").append(hasRealCaller());
+ sb.append("; isPendingIntent: ").append(isPendingIntent());
+ if (hasRealCaller()) {
sb.append("; balAllowedByPiSender: ").append(mBalAllowedByPiSender);
sb.append("; realCallingPackage: ")
.append(getDebugPackageName(mRealCallingPackage, mRealCallingUid));
@@ -351,13 +368,13 @@
sb.append("; mForcedBalByPiSender: ").append(mForcedBalByPiSender);
sb.append("; intent: ").append(mIntent);
sb.append("; callerApp: ").append(mCallerApp);
- if (isPendingIntent()) {
+ if (hasRealCaller()) {
sb.append("; realCallerApp: ").append(mRealCallerApp);
}
if (mCallerApp != null) {
sb.append("; inVisibleTask: ").append(mCallerApp.hasActivityInVisibleTask());
}
- if (isPendingIntent()) {
+ if (hasRealCaller()) {
if (mRealCallerApp != null) {
sb.append("; realInVisibleTask: ")
.append(mRealCallerApp.hasActivityInVisibleTask());
@@ -484,7 +501,7 @@
BalVerdict resultForCaller = checkBackgroundActivityStartAllowedByCaller(state);
- if (!state.isPendingIntent()) {
+ if (!state.hasRealCaller()) {
if (resultForCaller.allows()) {
if (DEBUG_ACTIVITY_STARTS) {
Slog.d(TAG, "Background activity start allowed. "
diff --git a/services/core/jni/tvinput/JTvInputHal.cpp b/services/core/jni/tvinput/JTvInputHal.cpp
index 80427b3..505421e 100644
--- a/services/core/jni/tvinput/JTvInputHal.cpp
+++ b/services/core/jni/tvinput/JTvInputHal.cpp
@@ -494,12 +494,21 @@
::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::setCallback(
const std::shared_ptr<TvInputCallbackWrapper>& in_callback) {
if (mIsHidl) {
- in_callback->aidlTvInputCallback = nullptr;
- return hidlSetCallback(in_callback == nullptr ? nullptr : in_callback->hidlTvInputCallback);
+ if (in_callback == nullptr) {
+ return hidlSetCallback(nullptr);
+ }
+ else {
+ in_callback->aidlTvInputCallback = nullptr;
+ return hidlSetCallback(in_callback->hidlTvInputCallback);
+ }
} else {
- in_callback->hidlTvInputCallback = nullptr;
- return mAidlTvInput->setCallback(in_callback == nullptr ? nullptr
- : in_callback->aidlTvInputCallback);
+ if (in_callback == nullptr) {
+ return mAidlTvInput->setCallback(nullptr);
+ }
+ else {
+ in_callback->hidlTvInputCallback = nullptr;
+ return mAidlTvInput->setCallback(in_callback->aidlTvInputCallback);
+ }
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 34d6755..9b62a2c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -9667,6 +9667,26 @@
}
}
+ @Override
+ public ComponentName getDeviceOwnerComponentOnUser(int userId) {
+ if (!mHasFeature) {
+ return null;
+ }
+ if (mInjector.userHandleGetCallingUserId() != userId) {
+ Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity())
+ || hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS));
+ }
+ synchronized (getLockObject()) {
+ // There is only ever one device owner on a device so if the passed userId is the same
+ // as the device owner userId we know that the componentName returned by
+ // getDeviceOwnerComponent will be the correct one.
+ if (mOwners.getDeviceOwnerUserId() == userId || userId == UserHandle.USER_ALL) {
+ return mOwners.getDeviceOwnerComponent();
+ }
+ }
+ return null;
+ }
+
private int getDeviceOwnerUserIdUncheckedLocked() {
return mOwners.hasDeviceOwner() ? mOwners.getDeviceOwnerUserId() : UserHandle.USER_NULL;
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
index 15a5859..7db09f9 100644
--- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
@@ -16,7 +16,9 @@
package com.android.server.permission.access.permission
+import android.Manifest
import android.permission.PermissionManager
+import android.permission.flags.Flags
import android.util.Slog
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
@@ -273,7 +275,12 @@
/** These permissions are supported for virtual devices. */
// TODO: b/298661870 - Use new API to get the list of device aware permissions.
- val DEVICE_AWARE_PERMISSIONS = emptySet<String>()
+ val DEVICE_AWARE_PERMISSIONS =
+ if (Flags.deviceAwarePermissionApis()) {
+ setOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
+ } else {
+ emptySet<String>()
+ }
}
/**
diff --git a/telephony/java/android/telephony/satellite/ISatelliteCapabilitiesCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteCapabilitiesCallback.aidl
new file mode 100644
index 0000000..4c37a6d
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/ISatelliteCapabilitiesCallback.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.telephony.satellite.SatelliteCapabilities;
+
+/**
+ * Interface for satellite capabilities change callback.
+ * @hide
+ */
+oneway interface ISatelliteCapabilitiesCallback {
+ /**
+ * Called when satellite capability has changed.
+ *
+ * @param capabilities The new satellite capability.
+ */
+ void onSatelliteCapabilitiesChanged(in SatelliteCapabilities capabilities);
+}
+
diff --git a/telephony/java/android/telephony/satellite/SatelliteCapabilitiesCallback.java b/telephony/java/android/telephony/satellite/SatelliteCapabilitiesCallback.java
new file mode 100644
index 0000000..b68dd5a
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteCapabilitiesCallback.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.telephony.flags.Flags;
+
+/**
+ * A callback class for satellite capabilities change events.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+public interface SatelliteCapabilitiesCallback {
+ /**
+ * Called when satellite capability has changed.
+ * @param capabilities The new satellite capabilities.
+ */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ void onSatelliteCapabilitiesChanged(@NonNull SatelliteCapabilities capabilities);
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index f18cbea..71786b3 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -81,6 +81,9 @@
new ConcurrentHashMap<>();
private static final ConcurrentHashMap<NtnSignalStrengthCallback, INtnSignalStrengthCallback>
sNtnSignalStrengthCallbackMap = new ConcurrentHashMap<>();
+ private static final ConcurrentHashMap<SatelliteCapabilitiesCallback,
+ ISatelliteCapabilitiesCallback>
+ sSatelliteCapabilitiesCallbackMap = new ConcurrentHashMap<>();
private final int mSubId;
@@ -2018,7 +2021,7 @@
* {@link TelephonyManager#unregisterTelephonyCallback(TelephonyCallback)}..
* </p>
*
- * @param callback The callback that was passed to
+ * @param callback The callback that was passed to.
* {@link #registerForNtnSignalStrengthChanged(Executor, NtnSignalStrengthCallback)}.
*
* @throws SecurityException if the caller doesn't have required permission.
@@ -2046,9 +2049,84 @@
loge("unregisterForNtnSignalStrengthChanged() RemoteException: " + ex);
ex.rethrowFromSystemServer();
}
-
}
+ /**
+ * Registers for satellite capabilities change event from the satellite service.
+ *
+ * @param executor The executor on which the callback will be called.
+ * @param callback The callback to handle the satellite capabilities changed event.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @SatelliteResult public int registerForSatelliteCapabilitiesChanged(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull SatelliteCapabilitiesCallback callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ ISatelliteCapabilitiesCallback internalCallback =
+ new ISatelliteCapabilitiesCallback.Stub() {
+ @Override
+ public void onSatelliteCapabilitiesChanged(
+ SatelliteCapabilities capabilities) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onSatelliteCapabilitiesChanged(
+ capabilities)));
+ }
+ };
+ sSatelliteCapabilitiesCallbackMap.put(callback, internalCallback);
+ return telephony.registerForSatelliteCapabilitiesChanged(mSubId, internalCallback);
+ } else {
+ throw new IllegalStateException("Telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("registerForSatelliteCapabilitiesChanged() RemoteException: " + ex);
+ ex.rethrowFromSystemServer();
+ }
+ return SATELLITE_RESULT_REQUEST_FAILED;
+ }
+
+ /**
+ * Unregisters for satellite capabilities change event from the satellite service.
+ * If callback was not registered before, the request will be ignored.
+ *
+ * @param callback The callback that was passed to.
+ * {@link #registerForSatelliteCapabilitiesChanged(Executor, SatelliteCapabilitiesCallback)}.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public void unregisterForSatelliteCapabilitiesChanged(
+ @NonNull SatelliteCapabilitiesCallback callback) {
+ Objects.requireNonNull(callback);
+ ISatelliteCapabilitiesCallback internalCallback =
+ sSatelliteCapabilitiesCallbackMap.remove(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ if (internalCallback != null) {
+ telephony.unregisterForSatelliteCapabilitiesChanged(mSubId, internalCallback);
+ } else {
+ loge("unregisterForSatelliteCapabilitiesChanged: No internal callback.");
+ }
+ } else {
+ throw new IllegalStateException("Telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("unregisterForSatelliteCapabilitiesChanged() RemoteException: " + ex);
+ ex.rethrowFromSystemServer();
+ }
+ }
private static ITelephony getITelephony() {
ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
diff --git a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
index d44ddfa..ccca5ad 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
@@ -19,6 +19,7 @@
import android.telephony.satellite.stub.NtnSignalStrength;
import android.telephony.satellite.stub.NTRadioTechnology;
import android.telephony.satellite.stub.PointingInfo;
+import android.telephony.satellite.stub.SatelliteCapabilities;
import android.telephony.satellite.stub.SatelliteDatagram;
import android.telephony.satellite.stub.SatelliteModemState;
@@ -62,7 +63,15 @@
/**
* Called when NTN signal strength changes.
+ *
* @param ntnSignalStrength The new NTN signal strength.
*/
void onNtnSignalStrengthChanged(in NtnSignalStrength ntnSignalStrength);
+
+ /**
+ * Called when satellite capabilities of the satellite service have changed.
+ *
+ * @param SatelliteCapabilities The current satellite capabilities.
+ */
+ void onSatelliteCapabilitiesChanged(in SatelliteCapabilities capabilities);
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index c212e35..15a20cb 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -68,6 +68,7 @@
import android.telephony.ims.aidl.IImsRegistrationCallback;
import android.telephony.ims.aidl.IRcsConfigCallback;
import android.telephony.satellite.INtnSignalStrengthCallback;
+import android.telephony.satellite.ISatelliteCapabilitiesCallback;
import android.telephony.satellite.ISatelliteDatagramCallback;
import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
import android.telephony.satellite.ISatelliteProvisionStateCallback;
@@ -3123,4 +3124,27 @@
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
void unregisterForNtnSignalStrengthChanged(int subId,
in INtnSignalStrengthCallback callback);
+
+ /**
+ * Registers for satellite capabilities change event from the satellite service.
+ *
+ * @param executor The executor on which the callback will be called.
+ * @param callback The callback to handle the satellite capabilities changed event.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ int registerForSatelliteCapabilitiesChanged(int subId,
+ in ISatelliteCapabilitiesCallback callback);
+
+ /**
+ * Unregisters for satellite capabilities change event from the satellite service.
+ * If callback was not registered before, the request will be ignored.
+ *
+ * @param callback The callback that was passed to.
+ * {@link #registerForSatelliteCapabilitiesChanged(Executor, SatelliteCapabilitiesCallback)}.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ void unregisterForSatelliteCapabilitiesChanged(int subId,
+ in ISatelliteCapabilitiesCallback callback);
}