Merge "fs-verity: fix broken test testInstallEverything"
diff --git a/core/api/current.txt b/core/api/current.txt
index 2b94694..3af4394 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -20899,8 +20899,11 @@
method public void onRoutingChanged(android.media.AudioRouting);
}
- public final class AudioTimestamp {
+ public final class AudioTimestamp implements android.os.Parcelable {
ctor public AudioTimestamp();
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioTimestamp> CREATOR;
field public static final int TIMEBASE_BOOTTIME = 1; // 0x1
field public static final int TIMEBASE_MONOTONIC = 0; // 0x0
field public long framePosition;
@@ -49747,7 +49750,7 @@
method public static void request(@NonNull android.view.Surface, @Nullable android.graphics.Rect, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler);
method public static void request(@NonNull android.view.Window, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler);
method public static void request(@NonNull android.view.Window, @Nullable android.graphics.Rect, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler);
- method public static void request(@NonNull android.view.PixelCopy.Request);
+ method public static void request(@NonNull android.view.PixelCopy.Request, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.Result>);
field public static final int ERROR_DESTINATION_INVALID = 5; // 0x5
field public static final int ERROR_SOURCE_INVALID = 4; // 0x4
field public static final int ERROR_SOURCE_NO_DATA = 3; // 0x3
@@ -49763,14 +49766,14 @@
public static final class PixelCopy.Request {
method @Nullable public android.graphics.Bitmap getDestinationBitmap();
method @Nullable public android.graphics.Rect getSourceRect();
- method @NonNull public static android.view.PixelCopy.Request.Builder ofSurface(@NonNull android.view.Surface, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.Result>);
- method @NonNull public static android.view.PixelCopy.Request.Builder ofSurface(@NonNull android.view.SurfaceView, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.Result>);
- method @NonNull public static android.view.PixelCopy.Request.Builder ofWindow(@NonNull android.view.Window, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.Result>);
- method @NonNull public static android.view.PixelCopy.Request.Builder ofWindow(@NonNull android.view.View, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.Result>);
}
public static final class PixelCopy.Request.Builder {
method @NonNull public android.view.PixelCopy.Request build();
+ method @NonNull public static android.view.PixelCopy.Request.Builder ofSurface(@NonNull android.view.Surface);
+ method @NonNull public static android.view.PixelCopy.Request.Builder ofSurface(@NonNull android.view.SurfaceView);
+ method @NonNull public static android.view.PixelCopy.Request.Builder ofWindow(@NonNull android.view.Window);
+ method @NonNull public static android.view.PixelCopy.Request.Builder ofWindow(@NonNull android.view.View);
method @NonNull public android.view.PixelCopy.Request.Builder setDestinationBitmap(@Nullable android.graphics.Bitmap);
method @NonNull public android.view.PixelCopy.Request.Builder setSourceRect(@Nullable android.graphics.Rect);
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 8ff9f87..99be1a2 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -79,6 +79,7 @@
field public static final String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
field public static final String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE";
field public static final String BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE = "android.permission.BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE";
+ field public static final String BIND_WEARABLE_SENSING_SERVICE = "android.permission.BIND_WEARABLE_SENSING_SERVICE";
field public static final String BLUETOOTH_MAP = "android.permission.BLUETOOTH_MAP";
field public static final String BRICK = "android.permission.BRICK";
field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
@@ -199,6 +200,7 @@
field public static final String MANAGE_USER_OEM_UNLOCK_STATE = "android.permission.MANAGE_USER_OEM_UNLOCK_STATE";
field public static final String MANAGE_WALLPAPER_EFFECTS_GENERATION = "android.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION";
field public static final String MANAGE_WEAK_ESCROW_TOKEN = "android.permission.MANAGE_WEAK_ESCROW_TOKEN";
+ field public static final String MANAGE_WEARABLE_SENSING_SERVICE = "android.permission.MANAGE_WEARABLE_SENSING_SERVICE";
field public static final String MANAGE_WIFI_COUNTRY_CODE = "android.permission.MANAGE_WIFI_COUNTRY_CODE";
field public static final String MARK_DEVICE_ORGANIZATION_OWNED = "android.permission.MARK_DEVICE_ORGANIZATION_OWNED";
field public static final String MEDIA_RESOURCE_OVERRIDE_PID = "android.permission.MEDIA_RESOURCE_OVERRIDE_PID";
@@ -2876,6 +2878,21 @@
}
+package android.app.wearable {
+
+ public class WearableSensingManager {
+ method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideData(@NonNull android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideDataStream(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ field public static final int STATUS_ACCESS_DENIED = 5; // 0x5
+ field public static final int STATUS_SERVICE_UNAVAILABLE = 3; // 0x3
+ field public static final int STATUS_SUCCESS = 1; // 0x1
+ field public static final int STATUS_UNKNOWN = 0; // 0x0
+ field public static final int STATUS_UNSUPPORTED = 2; // 0x2
+ field public static final int STATUS_WEARABLE_UNAVAILABLE = 4; // 0x4
+ }
+
+}
+
package android.apphibernation {
public class AppHibernationManager {
@@ -3095,6 +3112,7 @@
field public static final String APP_HIBERNATION_SERVICE = "app_hibernation";
field public static final String APP_INTEGRITY_SERVICE = "app_integrity";
field public static final String APP_PREDICTION_SERVICE = "app_prediction";
+ field public static final String AUDIO_DEVICE_VOLUME_SERVICE = "audio_device_volume";
field public static final String BACKUP_SERVICE = "backup";
field public static final String BATTERY_STATS_SERVICE = "batterystats";
field public static final int BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS = 1048576; // 0x100000
@@ -3130,6 +3148,7 @@
field public static final String UWB_SERVICE = "uwb";
field public static final String VR_SERVICE = "vrmanager";
field public static final String WALLPAPER_EFFECTS_GENERATION_SERVICE = "wallpaper_effects_generation";
+ field public static final String WEARABLE_SENSING_SERVICE = "wearable_sensing";
field public static final String WIFI_NL80211_SERVICE = "wifinl80211";
field @Deprecated public static final String WIFI_RTT_SERVICE = "rttmanager";
field public static final String WIFI_SCANNING_SERVICE = "wifiscanner";
@@ -6293,7 +6312,7 @@
}
public class AudioDeviceVolumeManager {
- ctor public AudioDeviceVolumeManager(@NonNull android.content.Context);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.VolumeInfo getDeviceVolume(@NonNull android.media.VolumeInfo, @NonNull android.media.AudioDeviceAttributes);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setDeviceVolume(@NonNull android.media.VolumeInfo, @NonNull android.media.AudioDeviceAttributes);
}
@@ -6631,8 +6650,9 @@
method public int getMaxVolumeIndex();
method public int getMinVolumeIndex();
method public int getStreamType();
- method @Nullable public android.media.audiopolicy.AudioVolumeGroup getVolumeGroup();
+ method @NonNull public android.media.audiopolicy.AudioVolumeGroup getVolumeGroup();
method public int getVolumeIndex();
+ method public boolean hasMuteCommand();
method public boolean hasStreamType();
method public boolean hasVolumeGroup();
method public boolean isMuted();
@@ -10574,6 +10594,7 @@
field public static final String NAMESPACE_TEXTCLASSIFIER = "textclassifier";
field public static final String NAMESPACE_TRANSPARENCY_METADATA = "transparency_metadata";
field public static final String NAMESPACE_UWB = "uwb";
+ field public static final String NAMESPACE_WEARABLE_SENSING = "wearable_sensing";
field public static final String NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT = "window_manager_native_boot";
}
@@ -12356,6 +12377,18 @@
}
+package android.service.wearable {
+
+ public abstract class WearableSensingService extends android.app.Service {
+ ctor public WearableSensingService();
+ method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
+ method @BinderThread public abstract void onDataProvided(@NonNull android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @BinderThread public abstract void onDataStreamProvided(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ field public static final String SERVICE_INTERFACE = "android.service.wearable.WearableSensingService";
+ }
+
+}
+
package android.telecom {
@Deprecated public class AudioState implements android.os.Parcelable {
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 8ec313ec..f2eced3 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -11,7 +11,7 @@
per-file ApplicationThreadConstants.java = file:/services/core/java/com/android/server/am/OWNERS
per-file BroadcastOptions.java = file:/services/core/java/com/android/server/am/OWNERS
per-file ContentProviderHolder* = file:/services/core/java/com/android/server/am/OWNERS
-per-file ForegroundService* = file:/services/core/java/com/android/server/am/OWNERS
+per-file *ForegroundService* = file:/services/core/java/com/android/server/am/OWNERS
per-file IActivityController.aidl = file:/services/core/java/com/android/server/am/OWNERS
per-file IActivityManager.aidl = file:/services/core/java/com/android/server/am/OWNERS
per-file IApplicationThread.aidl = file:/services/core/java/com/android/server/am/OWNERS
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index e5cb0ea..6857984 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -118,6 +118,7 @@
import android.location.ICountryDetector;
import android.location.ILocationManager;
import android.location.LocationManager;
+import android.media.AudioDeviceVolumeManager;
import android.media.AudioManager;
import android.media.MediaFrameworkInitializer;
import android.media.MediaFrameworkPlatformInitializer;
@@ -343,6 +344,13 @@
return new AudioManager(ctx);
}});
+ registerService(Context.AUDIO_DEVICE_VOLUME_SERVICE, AudioDeviceVolumeManager.class,
+ new CachedServiceFetcher<AudioDeviceVolumeManager>() {
+ @Override
+ public AudioDeviceVolumeManager createService(ContextImpl ctx) {
+ return new AudioDeviceVolumeManager(ctx);
+ }});
+
registerService(Context.MEDIA_ROUTER_SERVICE, MediaRouter.class,
new CachedServiceFetcher<MediaRouter>() {
@Override
diff --git a/core/java/android/app/wearable/IWearableSensingManager.aidl b/core/java/android/app/wearable/IWearableSensingManager.aidl
new file mode 100644
index 0000000..ff37bd8
--- /dev/null
+++ b/core/java/android/app/wearable/IWearableSensingManager.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.wearable;
+
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.SharedMemory;
+
+/**
+ * Interface for a WearableSensingManager for managing wearable sensing service.
+ *
+ * @hide
+ */
+interface IWearableSensingManager {
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
+ void provideDataStream(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
+ void provideData(in PersistableBundle data, in SharedMemory sharedMemory, in RemoteCallback callback);
+}
\ No newline at end of file
diff --git a/core/java/android/app/wearable/OWNERS b/core/java/android/app/wearable/OWNERS
new file mode 100644
index 0000000..073e2d7
--- /dev/null
+++ b/core/java/android/app/wearable/OWNERS
@@ -0,0 +1,3 @@
+charliewang@google.com
+oni@google.com
+volnov@google.com
\ No newline at end of file
diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java
new file mode 100644
index 0000000..a71590b
--- /dev/null
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.wearable;
+
+import android.Manifest;
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.app.ambientcontext.AmbientContextEvent;
+import android.content.Context;
+import android.os.Binder;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.SharedMemory;
+import android.service.wearable.WearableSensingService;
+import android.system.OsConstants;
+
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Allows granted apps to manage the WearableSensingService.
+ * Applications are responsible for managing the connection to Wearables. Applications can choose
+ * to provide a data stream to the WearableSensingService to use for
+ * computing {@link AmbientContextEvent}s. Applications can also optionally provide their own
+ * defined data to power the detection of {@link AmbientContextEvent}s.
+ * Methods on this class requires the caller to hold and be granted the
+ * {@link Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE}.
+ *
+ * @hide
+ */
+
+@SystemApi
+@SystemService(Context.WEARABLE_SENSING_SERVICE)
+public class WearableSensingManager {
+ /**
+ * The bundle key for the service status query result, used in
+ * {@code RemoteCallback#sendResult}.
+ *
+ * @hide
+ */
+ public static final String STATUS_RESPONSE_BUNDLE_KEY =
+ "android.app.wearable.WearableSensingStatusBundleKey";
+
+
+ /**
+ * An unknown status.
+ */
+ public static final int STATUS_UNKNOWN = 0;
+
+ /**
+ * The value of the status code that indicates success.
+ */
+ public static final int STATUS_SUCCESS = 1;
+
+ /**
+ * The value of the status code that indicates one or more of the
+ * requested events are not supported.
+ */
+ public static final int STATUS_UNSUPPORTED = 2;
+
+ /**
+ * The value of the status code that indicates service not available.
+ */
+ public static final int STATUS_SERVICE_UNAVAILABLE = 3;
+
+ /**
+ * The value of the status code that there's no connection to the wearable.
+ */
+ public static final int STATUS_WEARABLE_UNAVAILABLE = 4;
+
+ /**
+ * The value of the status code that the app is not granted access.
+ */
+ public static final int STATUS_ACCESS_DENIED = 5;
+
+ /** @hide */
+ @IntDef(prefix = { "STATUS_" }, value = {
+ STATUS_UNKNOWN,
+ STATUS_SUCCESS,
+ STATUS_UNSUPPORTED,
+ STATUS_SERVICE_UNAVAILABLE,
+ STATUS_WEARABLE_UNAVAILABLE,
+ STATUS_ACCESS_DENIED
+ }) public @interface StatusCode {}
+
+ private final Context mContext;
+ private final IWearableSensingManager mService;
+
+ /**
+ * {@hide}
+ */
+ public WearableSensingManager(Context context, IWearableSensingManager service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Provides a data stream to the WearableSensingService that's backed by the
+ * parcelFileDescriptor, and sends the result to the {@link Consumer} right after the call.
+ * This is used by applications that will also provide an implementation of
+ * an isolated WearableSensingService. If the data stream was provided successfully
+ * {@link WearableSensingManager#STATUS_SUCCESS} will be provided.
+ *
+ * @param parcelFileDescriptor The data stream to provide
+ * @param executor Executor on which to run the consumer callback
+ * @param statusConsumer A consumer that handles the status codes, which is returned
+ * right after the call.
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)
+ public void provideDataStream(
+ @NonNull ParcelFileDescriptor parcelFileDescriptor,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull @StatusCode Consumer<Integer> statusConsumer) {
+ try {
+ RemoteCallback callback = new RemoteCallback(result -> {
+ int status = result.getInt(STATUS_RESPONSE_BUNDLE_KEY);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> statusConsumer.accept(status));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ });
+ mService.provideDataStream(parcelFileDescriptor, callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets configuration and provides read-only data in a {@link PersistableBundle} that may be
+ * used by the WearableSensingService, and sends the result to the {@link Consumer}
+ * right after the call. It is dependent on the application to
+ * define the type of data to provide. This is used by applications that will also
+ * provide an implementation of an isolated WearableSensingService. If the data was
+ * provided successfully {@link WearableSensingManager#STATUS_SUCCESS} will be povided.
+ *
+ * @param data Application configuration data to provide to the {@link WearableSensingService}.
+ * PersistableBundle does not allow any remotable objects or other contents
+ * that can be used to communicate with other processes.
+ * @param sharedMemory The unrestricted data blob to
+ * provide to the {@link WearableSensingService}. Use this to provide the
+ * sensing models data or other such data to the trusted process.
+ * The sharedMemory must be read only and protected with
+ * {@link OsConstants.PROT_READ}.
+ * Other operations will be removed by the system.
+ * @param executor Executor on which to run the consumer callback
+ * @param statusConsumer A consumer that handles the status codes, which is returned
+ * right after the call
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)
+ public void provideData(
+ @NonNull PersistableBundle data, @Nullable SharedMemory sharedMemory,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull @StatusCode Consumer<Integer> statusConsumer) {
+ try {
+ RemoteCallback callback = new RemoteCallback(result -> {
+ int status = result.getInt(STATUS_RESPONSE_BUNDLE_KEY);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> statusConsumer.accept(status));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ });
+ mService.provideData(data, sharedMemory, callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 9dc3836..0b20078 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3850,6 +3850,7 @@
WIFI_RTT_RANGING_SERVICE,
NSD_SERVICE,
AUDIO_SERVICE,
+ AUDIO_DEVICE_VOLUME_SERVICE,
AUTH_SERVICE,
FINGERPRINT_SERVICE,
//@hide: FACE_SERVICE,
@@ -4693,6 +4694,18 @@
public static final String AUDIO_SERVICE = "audio";
/**
+ * @hide
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.media.AudioDeviceVolumeManager} for handling management of audio device
+ * (e.g. speaker, USB headset) volume.
+ *
+ * @see #getSystemService(String)
+ * @see android.media.AudioDeviceVolumeManager
+ */
+ @SystemApi
+ public static final String AUDIO_DEVICE_VOLUME_SERVICE = "audio_device_volume";
+
+ /**
* Use with {@link #getSystemService(String)} to retrieve a {@link
* android.media.MediaTranscodingManager} for transcoding media.
*
@@ -6065,6 +6078,17 @@
/**
* Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.app.wearable.WearableSensingManager}.
+ *
+ * @see #getSystemService(String)
+ * @see WearableSensingManager
+ * @hide
+ */
+ @SystemApi
+ public static final String WEARABLE_SENSING_SERVICE = "wearable_sensing";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
* {@link android.healthconnect.HealthConnectManager}.
*
* @see #getSystemService(String)
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 8a09cd7..19e7bd4 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -769,6 +769,15 @@
"ambient_context_manager_service";
/**
+ * Namespace for WearableSensingManagerService related features.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_WEARABLE_SENSING =
+ "wearable_sensing";
+
+ /**
* Namespace for Vendor System Native related features.
*
* @hide
diff --git a/core/java/android/service/wearable/IWearableSensingService.aidl b/core/java/android/service/wearable/IWearableSensingService.aidl
new file mode 100644
index 0000000..ba71174
--- /dev/null
+++ b/core/java/android/service/wearable/IWearableSensingService.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.wearable;
+
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.SharedMemory;
+
+/**
+ * Interface for a concrete implementation to provide AmbientContextEvents to the framework for
+ * wearables.
+ *
+ * @hide
+ */
+oneway interface IWearableSensingService {
+ void provideDataStream(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
+ void provideData(in PersistableBundle data, in SharedMemory sharedMemory, in RemoteCallback callback);
+}
\ No newline at end of file
diff --git a/core/java/android/service/wearable/OWNERS b/core/java/android/service/wearable/OWNERS
new file mode 100644
index 0000000..073e2d7
--- /dev/null
+++ b/core/java/android/service/wearable/OWNERS
@@ -0,0 +1,3 @@
+charliewang@google.com
+oni@google.com
+volnov@google.com
\ No newline at end of file
diff --git a/core/java/android/service/wearable/WearableSensingService.java b/core/java/android/service/wearable/WearableSensingService.java
new file mode 100644
index 0000000..a1c7658
--- /dev/null
+++ b/core/java/android/service/wearable/WearableSensingService.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.wearable;
+
+import android.annotation.BinderThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.app.ambientcontext.AmbientContextEvent;
+import android.app.wearable.WearableSensingManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.SharedMemory;
+import android.util.Slog;
+
+import java.util.Objects;
+import java.util.function.Consumer;
+
+/**
+ * Abstract base class for sensing with wearable devices. An example of this is {@link
+ *AmbientContextEvent} detection.
+ *
+ * <p> A service that provides requested sensing events to the system, such as a {@link
+ *AmbientContextEvent}. The system's default WearableSensingService implementation is configured in
+ * {@code config_defaultWearableSensingService}. If this config has no value, a stub is
+ * returned.
+ *
+ * <p> An implementation of a WearableSensingService should be an isolated service. Using the
+ * "isolatedProcess=true" attribute in the service's configurations. </p>
+ **
+ * <pre>
+ * {@literal
+ * <service android:name=".YourWearableSensingService"
+ * android:permission="android.permission.BIND_WEARABLE_SENSING_SERVICE"
+ * android:isolatedProcess="true">
+ * </service>}
+ * </pre>
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class WearableSensingService extends Service {
+ private static final String TAG = WearableSensingService.class.getSimpleName();
+
+ /**
+ * The bundle key for this class of object, used in {@code RemoteCallback#sendResult}.
+ *
+ * @hide
+ */
+ public static final String STATUS_RESPONSE_BUNDLE_KEY =
+ "android.app.wearable.WearableSensingStatusBundleKey";
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service. To be supported, the
+ * service must also require the
+ * {@link android.Manifest.permission#BIND_WEARABLE_SENSING_SERVICE}
+ * permission so that other applications can not abuse it.
+ */
+ public static final String SERVICE_INTERFACE =
+ "android.service.wearable.WearableSensingService";
+
+ @Nullable
+ @Override
+ public final IBinder onBind(@NonNull Intent intent) {
+ if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ return new IWearableSensingService.Stub() {
+ /** {@inheritDoc} */
+ @Override
+ public void provideDataStream(
+ ParcelFileDescriptor parcelFileDescriptor,
+ RemoteCallback callback) {
+ Objects.requireNonNull(parcelFileDescriptor);
+ Consumer<Integer> consumer = response -> {
+ Bundle bundle = new Bundle();
+ bundle.putInt(
+ STATUS_RESPONSE_BUNDLE_KEY,
+ response);
+ callback.sendResult(bundle);
+ };
+ WearableSensingService.this.onDataStreamProvided(
+ parcelFileDescriptor, consumer);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void provideData(
+ PersistableBundle data,
+ SharedMemory sharedMemory,
+ RemoteCallback callback) {
+ Objects.requireNonNull(data);
+ Consumer<Integer> consumer = response -> {
+ Bundle bundle = new Bundle();
+ bundle.putInt(
+ STATUS_RESPONSE_BUNDLE_KEY,
+ response);
+ callback.sendResult(bundle);
+ };
+ WearableSensingService.this.onDataProvided(data, sharedMemory, consumer);
+ }
+ };
+ }
+ Slog.w(TAG, "Incorrect service interface, returning null.");
+ return null;
+ }
+
+ /**
+ * Called when a data stream to the wearable is provided. This data stream can be used to obtain
+ * data from a wearable device. It is up to the implementation to maintain the data stream and
+ * close the data stream when it is finished.
+ *
+ * @param parcelFileDescriptor The data stream to the wearable
+ * @param statusConsumer the consumer for the service status.
+ */
+ @BinderThread
+ public abstract void onDataStreamProvided(@NonNull ParcelFileDescriptor parcelFileDescriptor,
+ @NonNull Consumer<Integer> statusConsumer);
+
+ /**
+ * Called when configurations and read-only data in a {@link PersistableBundle}
+ * can be used by the WearableSensingService and sends the result to the {@link Consumer}
+ * right after the call. It is dependent on the application to define the type of data to
+ * provide. This is used by applications that will also provide an implementation of an isolated
+ * WearableSensingService. If the data was provided successfully
+ * {@link WearableSensingManager#STATUS_SUCCESS} will be provided.
+ *
+ * @param data Application configuration data to provide to the {@link WearableSensingService}.
+ * PersistableBundle does not allow any remotable objects or other contents
+ * that can be used to communicate with other processes.
+ * @param sharedMemory The unrestricted data blob to
+ * provide to the {@link WearableSensingService}. Use this to provide the
+ * sensing models data or other such data to the trusted process.
+ * @param statusConsumer the consumer for the service status.
+ */
+ @BinderThread
+ public abstract void onDataProvided(
+ @NonNull PersistableBundle data,
+ @Nullable SharedMemory sharedMemory,
+ @NonNull Consumer<Integer> statusConsumer);
+}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 720813a..ef18458 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -947,112 +947,108 @@
+ " left=" + (mWindowSpaceLeft != mLocation[0])
+ " top=" + (mWindowSpaceTop != mLocation[1]));
+ mVisible = mRequestedVisible;
+ mWindowSpaceLeft = mLocation[0];
+ mWindowSpaceTop = mLocation[1];
+ mSurfaceWidth = myWidth;
+ mSurfaceHeight = myHeight;
+ mFormat = mRequestedFormat;
+ mAlpha = alpha;
+ mLastWindowVisibility = mWindowVisibility;
+ mTransformHint = viewRoot.getBufferTransformHint();
+ mSubLayer = mRequestedSubLayer;
+
+ mScreenRect.left = mWindowSpaceLeft;
+ mScreenRect.top = mWindowSpaceTop;
+ mScreenRect.right = mWindowSpaceLeft + getWidth();
+ mScreenRect.bottom = mWindowSpaceTop + getHeight();
+ if (translator != null) {
+ translator.translateRectInAppWindowToScreen(mScreenRect);
+ }
+
+ final Rect surfaceInsets = viewRoot.mWindowAttributes.surfaceInsets;
+ mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
+ // Collect all geometry changes and apply these changes on the RenderThread worker
+ // via the RenderNode.PositionUpdateListener.
+ final Transaction surfaceUpdateTransaction = new Transaction();
+ if (creating) {
+ updateOpaqueFlag();
+ final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
+ createBlastSurfaceControls(viewRoot, name, surfaceUpdateTransaction);
+ } else if (mSurfaceControl == null) {
+ return;
+ }
+
+ final boolean redrawNeeded = sizeChanged || creating || hintChanged
+ || (mVisible && !mDrawFinished) || alphaChanged || relativeZChanged;
+ boolean shouldSyncBuffer =
+ redrawNeeded && viewRoot.wasRelayoutRequested() && viewRoot.isInLocalSync();
+ SyncBufferTransactionCallback syncBufferTransactionCallback = null;
+ if (shouldSyncBuffer) {
+ syncBufferTransactionCallback = new SyncBufferTransactionCallback();
+ mBlastBufferQueue.syncNextTransaction(
+ false /* acquireSingleBuffer */,
+ syncBufferTransactionCallback::onTransactionReady);
+ }
+
+ final boolean realSizeChanged = performSurfaceTransaction(viewRoot, translator,
+ creating, sizeChanged, hintChanged, relativeZChanged,
+ surfaceUpdateTransaction);
+
try {
- mVisible = mRequestedVisible;
- mWindowSpaceLeft = mLocation[0];
- mWindowSpaceTop = mLocation[1];
- mSurfaceWidth = myWidth;
- mSurfaceHeight = myHeight;
- mFormat = mRequestedFormat;
- mAlpha = alpha;
- mLastWindowVisibility = mWindowVisibility;
- mTransformHint = viewRoot.getBufferTransformHint();
- mSubLayer = mRequestedSubLayer;
+ SurfaceHolder.Callback[] callbacks = null;
- mScreenRect.left = mWindowSpaceLeft;
- mScreenRect.top = mWindowSpaceTop;
- mScreenRect.right = mWindowSpaceLeft + getWidth();
- mScreenRect.bottom = mWindowSpaceTop + getHeight();
- if (translator != null) {
- translator.translateRectInAppWindowToScreen(mScreenRect);
+ final boolean surfaceChanged = creating;
+ if (mSurfaceCreated && (surfaceChanged || (!mVisible && visibleChanged))) {
+ mSurfaceCreated = false;
+ notifySurfaceDestroyed();
}
- final Rect surfaceInsets = viewRoot.mWindowAttributes.surfaceInsets;
- mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
- // Collect all geometry changes and apply these changes on the RenderThread worker
- // via the RenderNode.PositionUpdateListener.
- final Transaction surfaceUpdateTransaction = new Transaction();
- if (creating) {
- updateOpaqueFlag();
- final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
- createBlastSurfaceControls(viewRoot, name, surfaceUpdateTransaction);
- } else if (mSurfaceControl == null) {
- return;
- }
+ copySurface(creating /* surfaceControlCreated */, sizeChanged);
- final boolean redrawNeeded = sizeChanged || creating || hintChanged
- || (mVisible && !mDrawFinished) || alphaChanged || relativeZChanged;
- boolean shouldSyncBuffer =
- redrawNeeded && viewRoot.wasRelayoutRequested() && viewRoot.isInLocalSync();
- SyncBufferTransactionCallback syncBufferTransactionCallback = null;
- if (shouldSyncBuffer) {
- syncBufferTransactionCallback = new SyncBufferTransactionCallback();
- mBlastBufferQueue.syncNextTransaction(
- false /* acquireSingleBuffer */,
- syncBufferTransactionCallback::onTransactionReady);
- }
-
- final boolean realSizeChanged = performSurfaceTransaction(viewRoot, translator,
- creating, sizeChanged, hintChanged, relativeZChanged,
- surfaceUpdateTransaction);
-
- try {
- SurfaceHolder.Callback[] callbacks = null;
-
- final boolean surfaceChanged = creating;
- if (mSurfaceCreated && (surfaceChanged || (!mVisible && visibleChanged))) {
- mSurfaceCreated = false;
- notifySurfaceDestroyed();
+ if (mVisible && mSurface.isValid()) {
+ if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
+ mSurfaceCreated = true;
+ mIsCreating = true;
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "visibleChanged -- surfaceCreated");
+ callbacks = getSurfaceCallbacks();
+ for (SurfaceHolder.Callback c : callbacks) {
+ c.surfaceCreated(mSurfaceHolder);
+ }
}
-
- copySurface(creating /* surfaceControlCreated */, sizeChanged);
-
- if (mVisible && mSurface.isValid()) {
- if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
- mSurfaceCreated = true;
- mIsCreating = true;
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
- + "visibleChanged -- surfaceCreated");
+ if (creating || formatChanged || sizeChanged || hintChanged
+ || visibleChanged || realSizeChanged) {
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "surfaceChanged -- format=" + mFormat
+ + " w=" + myWidth + " h=" + myHeight);
+ if (callbacks == null) {
callbacks = getSurfaceCallbacks();
- for (SurfaceHolder.Callback c : callbacks) {
- c.surfaceCreated(mSurfaceHolder);
- }
}
- if (creating || formatChanged || sizeChanged || hintChanged
- || visibleChanged || realSizeChanged) {
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
- + "surfaceChanged -- format=" + mFormat
- + " w=" + myWidth + " h=" + myHeight);
- if (callbacks == null) {
- callbacks = getSurfaceCallbacks();
- }
- for (SurfaceHolder.Callback c : callbacks) {
- c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
- }
- }
- if (redrawNeeded) {
- if (DEBUG) {
- Log.i(TAG, System.identityHashCode(this) + " surfaceRedrawNeeded");
- }
- if (callbacks == null) {
- callbacks = getSurfaceCallbacks();
- }
-
- if (shouldSyncBuffer) {
- handleSyncBufferCallback(callbacks, syncBufferTransactionCallback);
- } else {
- handleSyncNoBuffer(callbacks);
- }
+ for (SurfaceHolder.Callback c : callbacks) {
+ c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
}
}
- } finally {
- mIsCreating = false;
- if (mSurfaceControl != null && !mSurfaceCreated) {
- releaseSurfaces(false /* releaseSurfacePackage*/);
+ if (redrawNeeded) {
+ if (DEBUG) {
+ Log.i(TAG, System.identityHashCode(this) + " surfaceRedrawNeeded");
+ }
+ if (callbacks == null) {
+ callbacks = getSurfaceCallbacks();
+ }
+
+ if (shouldSyncBuffer) {
+ handleSyncBufferCallback(callbacks, syncBufferTransactionCallback);
+ } else {
+ handleSyncNoBuffer(callbacks);
+ }
}
}
- } catch (Exception ex) {
- Log.e(TAG, "Exception configuring surface", ex);
+ } finally {
+ mIsCreating = false;
+ if (mSurfaceControl != null && !mSurfaceCreated) {
+ releaseSurfaces(false /* releaseSurfacePackage*/);
+ }
}
if (DEBUG) Log.v(
TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index fc64eb9..1063532 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -815,6 +815,18 @@
}
/**
+ * Clears container adjacent.
+ * @param root the root container to clear the adjacent roots for.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction clearAdjacentRoots(
+ @NonNull WindowContainerToken root) {
+ mHierarchyOps.add(HierarchyOp.createForClearAdjacentRoots(root.asBinder()));
+ return this;
+ }
+
+ /**
* Merges another WCT into this one.
* @param transfer When true, this will transfer everything from other potentially leaving
* other in an unusable state. When false, other is left alone, but
@@ -1229,6 +1241,7 @@
public static final int HIERARCHY_OP_TYPE_REMOVE_TASK = 20;
public static final int HIERARCHY_OP_TYPE_FINISH_ACTIVITY = 21;
public static final int HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT = 22;
+ public static final int HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS = 23;
// The following key(s) are for use with mLaunchOptions:
// When launching a task (eg. from recents), this is the taskId to be launched.
@@ -1365,6 +1378,13 @@
.build();
}
+ /** Create a hierarchy op for clearing adjacent root tasks. */
+ public static HierarchyOp createForClearAdjacentRoots(@NonNull IBinder root) {
+ return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS)
+ .setContainer(root)
+ .build();
+ }
+
/** Only creates through {@link Builder}. */
private HierarchyOp(int type) {
mType = type;
@@ -1560,6 +1580,8 @@
case HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT:
return "{setCompanionTaskFragment: container = " + mContainer + " companion = "
+ mReparent + "}";
+ case HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS:
+ return "{ClearAdjacentRoot: container=" + mContainer + "}";
default:
return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
+ " mToTop=" + mToTop
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f14ffc8..5a7abcc 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6763,6 +6763,21 @@
<permission android:name="android.permission.MANAGE_DEVICE_LOCK_STATE"
android:protectionLevel="internal|role" />
+ <!-- @SystemApi Required by a WearableSensingService to
+ ensure that only the caller with this permission can bind to it.
+ <p> Protection level: signature
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_WEARABLE_SENSING_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an app to manage the wearable sensing service.
+ <p>Protection level: signature|privileged
+ @hide
+ -->
+ <permission android:name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE"
+ android:protectionLevel="signature|privileged" />
+
<!-- Allows applications to use the long running jobs APIs.
<p>This is a special access permission that can be revoked by the system or the user.
<p>Apps need to target API {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or above
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 6c18259..b92d96f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4149,6 +4149,12 @@
-->
<string name="config_defaultAmbientContextDetectionService" translatable="false"></string>
+ <!-- The component name for the default system wearable sensing service.
+ This service must be trusted, as it can be activated without explicit consent of the user.
+ See android.service.wearable.WearableSensingService.
+ -->
+ <string name="config_defaultWearableSensingService" translatable="false"></string>
+
<!-- Component name that accepts ACTION_SEND intents for requesting ambient context consent. -->
<string translatable="false" name="config_defaultAmbientContextConsentComponent"></string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 691d48e..9d7e6f8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3768,6 +3768,7 @@
<java-symbol type="string" name="config_defaultAmbientContextConsentComponent" />
<java-symbol type="string" name="config_ambientContextPackageNameExtraKey" />
<java-symbol type="string" name="config_ambientContextEventArrayExtraKey" />
+ <java-symbol type="string" name="config_defaultWearableSensingService" />
<java-symbol type="string" name="config_retailDemoPackage" />
<java-symbol type="string" name="config_retailDemoPackageSignature" />
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/RadioServiceUserControllerTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/RadioServiceUserControllerTest.java
new file mode 100644
index 0000000..a2d8467
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/RadioServiceUserControllerTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.os.Binder;
+import android.os.UserHandle;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+/**
+ * Tests for {@link com.android.server.broadcastradio.RadioServiceUserController}
+ */
+public final class RadioServiceUserControllerTest extends ExtendedRadioMockitoTestCase {
+
+ private static final int USER_ID_1 = 11;
+ private static final int USER_ID_2 = 12;
+
+ @Mock
+ private UserHandle mUserHandleMock;
+
+ @Override
+ protected void initializeSession(StaticMockitoSessionBuilder builder) {
+ builder.spyStatic(ActivityManager.class)
+ .spyStatic(Binder.class);
+ }
+
+ @Before
+ public void setUp() {
+ doReturn(mUserHandleMock).when(() -> Binder.getCallingUserHandle());
+ doReturn(USER_ID_1).when(() -> ActivityManager.getCurrentUser());
+ }
+
+ @Test
+ public void isCurrentUser_forCurrentUser_returnsFalse() {
+ when(mUserHandleMock.getIdentifier()).thenReturn(USER_ID_1);
+
+ assertWithMessage("Current user")
+ .that(RadioServiceUserController.isCurrentOrSystemUser()).isTrue();
+ }
+
+ @Test
+ public void isCurrentUser_forNonCurrentUser_returnsFalse() {
+ when(mUserHandleMock.getIdentifier()).thenReturn(USER_ID_2);
+
+ assertWithMessage("Non-current user")
+ .that(RadioServiceUserController.isCurrentOrSystemUser()).isFalse();
+ }
+
+ @Test
+ public void isCurrentUser_forSystemUser_returnsTrue() {
+ when(mUserHandleMock.getIdentifier()).thenReturn(UserHandle.USER_SYSTEM);
+
+ assertWithMessage("System user")
+ .that(RadioServiceUserController.isCurrentOrSystemUser()).isTrue();
+ }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
index 93214e5..36aa915 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
@@ -20,6 +20,7 @@
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
@@ -28,6 +29,9 @@
import static org.mockito.Mockito.when;
import android.hardware.broadcastradio.IBroadcastRadio;
+import android.hardware.radio.Announcement;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
import android.hardware.radio.ITuner;
import android.hardware.radio.ITunerCallback;
import android.hardware.radio.RadioManager;
@@ -54,6 +58,7 @@
private static final int DAB_RADIO_MODULE_ID = 1;
private static final ArrayList<String> SERVICE_LIST =
new ArrayList<>(Arrays.asList("FmService", "DabService"));
+ private static final int[] TEST_ENABLED_TYPES = new int[]{Announcement.TYPE_TRAFFIC};
private BroadcastRadioServiceImpl mBroadcastRadioService;
private IBinder.DeathRecipient mFmDeathRecipient;
@@ -78,6 +83,14 @@
private TunerSession mFmTunerSessionMock;
@Mock
private ITunerCallback mTunerCallbackMock;
+ @Mock
+ private ICloseHandle mFmCloseHandleMock;
+ @Mock
+ private ICloseHandle mDabCloseHandleMock;
+ @Mock
+ private IAnnouncementListener mAnnouncementListenerMock;
+ @Mock
+ private IBinder mListenerBinderMock;
@Override
protected void initializeSession(StaticMockitoSessionBuilder builder) {
@@ -141,6 +154,19 @@
}
@Test
+ public void openSession_forNonCurrentUser_throwsException() throws Exception {
+ createBroadcastRadioService();
+ doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+ IllegalStateException thrown = assertThrows(IllegalStateException.class,
+ () -> mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID,
+ /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock));
+
+ assertWithMessage("Exception for opening session by non-current user")
+ .that(thrown).hasMessageThat().contains("Cannot open session for non-current user");
+ }
+
+ @Test
public void binderDied_forDeathRecipient() throws Exception {
createBroadcastRadioService();
@@ -151,6 +177,22 @@
.that(mBroadcastRadioService.hasModule(FM_RADIO_MODULE_ID)).isFalse();
}
+ @Test
+ public void addAnnouncementListener_addsOnAllRadioModules() throws Exception {
+ createBroadcastRadioService();
+ when(mAnnouncementListenerMock.asBinder()).thenReturn(mListenerBinderMock);
+ when(mFmRadioModuleMock.addAnnouncementListener(any(), any()))
+ .thenReturn(mFmCloseHandleMock);
+ when(mDabRadioModuleMock.addAnnouncementListener(any(), any()))
+ .thenReturn(mDabCloseHandleMock);
+
+ mBroadcastRadioService.addAnnouncementListener(TEST_ENABLED_TYPES,
+ mAnnouncementListenerMock);
+
+ verify(mFmRadioModuleMock).addAnnouncementListener(any(), any());
+ verify(mDabRadioModuleMock).addAnnouncementListener(any(), any());
+ }
+
private void createBroadcastRadioService() throws RemoteException {
doReturn(true).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
mockServiceManager();
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
index a29e9c5..87d0ea4 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -330,6 +330,20 @@
}
@Test
+ public void tune_forCurrentUser_doesNotTune() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+ ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
+ RadioManager.ProgramInfo tuneInfo =
+ AidlTestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
+
+ mTunerSessions[0].tune(initialSel);
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0))
+ .onCurrentProgramInfoChanged(tuneInfo);
+ }
+
+ @Test
public void step_withDirectionUp() throws Exception {
long initFreq = AM_FM_FREQUENCY_LIST[1];
ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/BroadcastRadioServiceHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/BroadcastRadioServiceHidlTest.java
index 99e7043..0b7bbea 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/BroadcastRadioServiceHidlTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/BroadcastRadioServiceHidlTest.java
@@ -156,6 +156,19 @@
}
@Test
+ public void openSession_forNonCurrentUser_throwsException() throws Exception {
+ createBroadcastRadioService();
+ doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+ IllegalStateException thrown = assertThrows(IllegalStateException.class,
+ () -> mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID,
+ /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock));
+
+ assertWithMessage("Exception for opening session by non-current user")
+ .that(thrown).hasMessageThat().contains("Cannot open session for non-current user");
+ }
+
+ @Test
public void addAnnouncementListener_addsOnAllRadioModules() throws Exception {
createBroadcastRadioService();
when(mAnnouncementListenerMock.asBinder()).thenReturn(mBinderMock);
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
index 8884053..b7da5d0 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
@@ -27,6 +27,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -337,6 +338,20 @@
}
@Test
+ public void tune_forCurrentUser_doesNotTune() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+ ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
+ RadioManager.ProgramInfo tuneInfo =
+ TestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
+
+ mTunerSessions[0].tune(initialSel);
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0))
+ .onCurrentProgramInfoChanged(tuneInfo);
+ }
+
+ @Test
public void step_withDirectionUp() throws Exception {
long initFreq = AM_FM_FREQUENCY_LIST[1];
ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq);
@@ -427,6 +442,18 @@
}
@Test
+ public void cancel_forNonCurrentUser() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
+ mTunerSessions[0].tune(initialSel);
+ doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+ mTunerSessions[0].cancel();
+
+ verify(mHalTunerSessionMock, never()).cancel();
+ }
+
+ @Test
public void getImage_withInvalidId_throwsIllegalArgumentException() throws Exception {
openAidlClients(/* numClients= */ 1);
int imageId = Constants.INVALID_IMAGE;
diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java
index 889edb3..82ced43 100644
--- a/graphics/java/android/view/PixelCopy.java
+++ b/graphics/java/android/view/PixelCopy.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.HardwareRenderer;
import android.graphics.Rect;
@@ -355,27 +356,23 @@
* PixelCopy.Request.ofSurface factories to create a {@link Request.Builder} for the
* given source content. After setting any optional parameters, such as
* {@link Builder#setSourceRect(Rect)}, build the request with {@link Builder#build()} and
- * then execute it with {@link PixelCopy#request(Request)}
+ * then execute it with {@link PixelCopy#request(Request, Executor, Consumer)}
*/
public static final class Request {
private final Surface mSource;
- private final Consumer<Result> mListener;
- private final Executor mListenerThread;
private final Rect mSourceInsets;
private Rect mSrcRect;
private Bitmap mDest;
- private Request(Surface source, Rect sourceInsets, Executor listenerThread,
- Consumer<Result> listener) {
+ private Request(Surface source, Rect sourceInsets) {
this.mSource = source;
this.mSourceInsets = sourceInsets;
- this.mListenerThread = listenerThread;
- this.mListener = listener;
}
/**
* A builder to create the complete PixelCopy request, which is then executed by calling
- * {@link #request(Request)} with the built request returned from {@link #build()}
+ * {@link #request(Request, Executor, Consumer)} with the built request returned from
+ * {@link #build()}
*/
public static final class Builder {
private Request mRequest;
@@ -384,6 +381,78 @@
mRequest = request;
}
+ /**
+ * Creates a PixelCopy request for the given {@link Window}
+ * @param source The Window to copy from
+ * @return A {@link Builder} builder to set the optional params & execute the request
+ */
+ @SuppressLint("BuilderSetStyle")
+ public static @NonNull Builder ofWindow(@NonNull Window source) {
+ final Rect insets = new Rect();
+ final Surface surface = sourceForWindow(source, insets);
+ return new Builder(new Request(surface, insets));
+ }
+
+ /**
+ * Creates a PixelCopy request for the {@link Window} that the given {@link View} is
+ * attached to.
+ *
+ * Note that this copy request is not cropped to the area the View occupies by default.
+ * If that behavior is desired, use {@link View#getLocationInWindow(int[])} combined
+ * with {@link Builder#setSourceRect(Rect)} to set a crop area to restrict the copy
+ * operation.
+ *
+ * @param source A View that {@link View#isAttachedToWindow() is attached} to a window
+ * that will be used to retrieve the window to copy from.
+ * @return A {@link Builder} builder to set the optional params & execute the request
+ */
+ @SuppressLint("BuilderSetStyle")
+ public static @NonNull Builder ofWindow(@NonNull View source) {
+ if (source == null || !source.isAttachedToWindow()) {
+ throw new IllegalArgumentException(
+ "View must not be null & must be attached to window");
+ }
+ final Rect insets = new Rect();
+ Surface surface = null;
+ final ViewRootImpl root = source.getViewRootImpl();
+ if (root != null) {
+ surface = root.mSurface;
+ insets.set(root.mWindowAttributes.surfaceInsets);
+ }
+ if (surface == null || !surface.isValid()) {
+ throw new IllegalArgumentException(
+ "Window doesn't have a backing surface!");
+ }
+ return new Builder(new Request(surface, insets));
+ }
+
+ /**
+ * Creates a PixelCopy request for the given {@link Surface}
+ *
+ * @param source The Surface to copy from. Must be {@link Surface#isValid() valid}.
+ * @return A {@link Builder} builder to set the optional params & execute the request
+ */
+ @SuppressLint("BuilderSetStyle")
+ public static @NonNull Builder ofSurface(@NonNull Surface source) {
+ if (source == null || !source.isValid()) {
+ throw new IllegalArgumentException("Source must not be null & must be valid");
+ }
+ return new Builder(new Request(source, null));
+ }
+
+ /**
+ * Creates a PixelCopy request for the {@link Surface} belonging to the
+ * given {@link SurfaceView}
+ *
+ * @param source The SurfaceView to copy from. The backing surface must be
+ * {@link Surface#isValid() valid}
+ * @return A {@link Builder} builder to set the optional params & execute the request
+ */
+ @SuppressLint("BuilderSetStyle")
+ public static @NonNull Builder ofSurface(@NonNull SurfaceView source) {
+ return ofSurface(source.getHolder().getSurface());
+ }
+
private void requireNotBuilt() {
if (mRequest == null) {
throw new IllegalStateException("build() already called on this builder");
@@ -439,89 +508,6 @@
}
/**
- * Creates a PixelCopy request for the given {@link Window}
- * @param source The Window to copy from
- * @param callbackExecutor The executor to run the callback on
- * @param listener The callback for when the copy request is completed
- * @return A {@link Builder} builder to set the optional params & execute the request
- */
- public static @NonNull Builder ofWindow(@NonNull Window source,
- @NonNull Executor callbackExecutor,
- @NonNull Consumer<Result> listener) {
- final Rect insets = new Rect();
- final Surface surface = sourceForWindow(source, insets);
- return new Builder(new Request(surface, insets, callbackExecutor, listener));
- }
-
- /**
- * Creates a PixelCopy request for the {@link Window} that the given {@link View} is
- * attached to.
- *
- * Note that this copy request is not cropped to the area the View occupies by default. If
- * that behavior is desired, use {@link View#getLocationInWindow(int[])} combined with
- * {@link Builder#setSourceRect(Rect)} to set a crop area to restrict the copy operation.
- *
- * @param source A View that {@link View#isAttachedToWindow() is attached} to a window that
- * will be used to retrieve the window to copy from.
- * @param callbackExecutor The executor to run the callback on
- * @param listener The callback for when the copy request is completed
- * @return A {@link Builder} builder to set the optional params & execute the request
- */
- public static @NonNull Builder ofWindow(@NonNull View source,
- @NonNull Executor callbackExecutor,
- @NonNull Consumer<Result> listener) {
- if (source == null || !source.isAttachedToWindow()) {
- throw new IllegalArgumentException(
- "View must not be null & must be attached to window");
- }
- final Rect insets = new Rect();
- Surface surface = null;
- final ViewRootImpl root = source.getViewRootImpl();
- if (root != null) {
- surface = root.mSurface;
- insets.set(root.mWindowAttributes.surfaceInsets);
- }
- if (surface == null || !surface.isValid()) {
- throw new IllegalArgumentException(
- "Window doesn't have a backing surface!");
- }
- return new Builder(new Request(surface, insets, callbackExecutor, listener));
- }
-
- /**
- * Creates a PixelCopy request for the given {@link Surface}
- *
- * @param source The Surface to copy from. Must be {@link Surface#isValid() valid}.
- * @param callbackExecutor The executor to run the callback on
- * @param listener The callback for when the copy request is completed
- * @return A {@link Builder} builder to set the optional params & execute the request
- */
- public static @NonNull Builder ofSurface(@NonNull Surface source,
- @NonNull Executor callbackExecutor,
- @NonNull Consumer<Result> listener) {
- if (source == null || !source.isValid()) {
- throw new IllegalArgumentException("Source must not be null & must be valid");
- }
- return new Builder(new Request(source, null, callbackExecutor, listener));
- }
-
- /**
- * Creates a PixelCopy request for the {@link Surface} belonging to the
- * given {@link SurfaceView}
- *
- * @param source The SurfaceView to copy from. The backing surface must be
- * {@link Surface#isValid() valid}
- * @param callbackExecutor The executor to run the callback on
- * @param listener The callback for when the copy request is completed
- * @return A {@link Builder} builder to set the optional params & execute the request
- */
- public static @NonNull Builder ofSurface(@NonNull SurfaceView source,
- @NonNull Executor callbackExecutor,
- @NonNull Consumer<Result> listener) {
- return ofSurface(source.getHolder().getSurface(), callbackExecutor, listener);
- }
-
- /**
* @return The destination bitmap as set by {@link Builder#setDestinationBitmap(Bitmap)}
*/
public @Nullable Bitmap getDestinationBitmap() {
@@ -538,9 +524,10 @@
/**
* @hide
*/
- public void request() {
+ public void request(@NonNull Executor callbackExecutor,
+ @NonNull Consumer<Result> listener) {
if (!mSource.isValid()) {
- mListenerThread.execute(() -> mListener.accept(
+ callbackExecutor.execute(() -> listener.accept(
new Result(ERROR_SOURCE_INVALID, null)));
return;
}
@@ -548,7 +535,7 @@
adjustSourceRectForInsets(mSourceInsets, mSrcRect), mDest) {
@Override
public void onCopyFinished(int result) {
- mListenerThread.execute(() -> mListener.accept(
+ callbackExecutor.execute(() -> listener.accept(
new Result(result, mDestinationBitmap)));
}
});
@@ -558,9 +545,12 @@
/**
* Executes the pixel copy request
* @param request The request to execute
+ * @param callbackExecutor The executor to run the callback on
+ * @param listener The callback for when the copy request is completed
*/
- public static void request(@NonNull Request request) {
- request.request();
+ public static void request(@NonNull Request request, @NonNull Executor callbackExecutor,
+ @NonNull Consumer<Result> listener) {
+ request.request(callbackExecutor, listener);
}
private PixelCopy() {}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
index a7d47ef..13afa49 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
@@ -86,13 +86,23 @@
/** Animation for target that is opening in a change transition. */
@NonNull
Animation createChangeBoundsOpenAnimation(@NonNull RemoteAnimationTarget target) {
- final Rect bounds = target.localBounds;
- // The target will be animated in from left or right depends on its position.
- final int startLeft = bounds.left == 0 ? -bounds.width() : bounds.width();
+ final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
+ final Rect bounds = target.screenSpaceBounds;
+ final int startLeft;
+ final int startTop;
+ if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
+ // The window will be animated in from left or right depending on its position.
+ startTop = 0;
+ startLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
+ } else {
+ // The window will be animated in from top or bottom depending on its position.
+ startTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
+ startLeft = 0;
+ }
// The position should be 0-based as we will post translate in
// TaskFragmentAnimationAdapter#onAnimationUpdate
- final Animation animation = new TranslateAnimation(startLeft, 0, 0, 0);
+ final Animation animation = new TranslateAnimation(startLeft, 0, startTop, 0);
animation.setInterpolator(mFastOutExtraSlowInInterpolator);
animation.setDuration(CHANGE_ANIMATION_DURATION);
animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
@@ -103,13 +113,24 @@
/** Animation for target that is closing in a change transition. */
@NonNull
Animation createChangeBoundsCloseAnimation(@NonNull RemoteAnimationTarget target) {
- final Rect bounds = target.localBounds;
- // The target will be animated out to left or right depends on its position.
- final int endLeft = bounds.left == 0 ? -bounds.width() : bounds.width();
+ final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
+ // TODO(b/258126915): we want to keep track of the closing start bounds
+ final Rect bounds = target.screenSpaceBounds;
+ final int endTop;
+ final int endLeft;
+ if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
+ // The window will be animated out to left or right depending on its position.
+ endTop = 0;
+ endLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
+ } else {
+ // The window will be animated out to top or bottom depending on its position.
+ endTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
+ endLeft = 0;
+ }
// The position should be 0-based as we will post translate in
// TaskFragmentAnimationAdapter#onAnimationUpdate
- final Animation animation = new TranslateAnimation(0, endLeft, 0, 0);
+ final Animation animation = new TranslateAnimation(0, endLeft, 0, endTop);
animation.setInterpolator(mFastOutExtraSlowInInterpolator);
animation.setDuration(CHANGE_ANIMATION_DURATION);
animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index 65a7d09..d10a674 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -86,11 +86,11 @@
final int startLeft;
final int startTop;
if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
- // The window will be animated in from left or right depends on its position.
+ // The window will be animated in from left or right depending on its position.
startTop = 0;
startLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
} else {
- // The window will be animated in from top or bottom depends on its position.
+ // The window will be animated in from top or bottom depending on its position.
startTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
startLeft = 0;
}
@@ -114,11 +114,11 @@
final int endTop;
final int endLeft;
if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
- // The window will be animated out to left or right depends on its position.
+ // The window will be animated out to left or right depending on its position.
endTop = 0;
endLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
} else {
- // The window will be animated out to top or bottom depends on its position.
+ // The window will be animated out to top or bottom depending on its position.
endTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
endLeft = 0;
}
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 06ffb72..3fa369d 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -571,7 +571,7 @@
// Retrieve the package group from the package id of the resource id.
if (UNLIKELY(!is_valid_resid(resid))) {
- LOG(ERROR) << base::StringPrintf("Invalid ID 0x%08x.", resid);
+ LOG(ERROR) << base::StringPrintf("Invalid resource ID 0x%08x.", resid);
return base::unexpected(std::nullopt);
}
@@ -580,7 +580,7 @@
const uint16_t entry_idx = get_entry_id(resid);
uint8_t package_idx = package_ids_[package_id];
if (UNLIKELY(package_idx == 0xff)) {
- ANDROID_LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.",
+ ANDROID_LOG(ERROR) << base::StringPrintf("No package ID %02x found for resource ID 0x%08x.",
package_id, resid);
return base::unexpected(std::nullopt);
}
diff --git a/media/java/android/media/AudioDeviceVolumeManager.java b/media/java/android/media/AudioDeviceVolumeManager.java
index 40f6dc5..4e2ce91 100644
--- a/media/java/android/media/AudioDeviceVolumeManager.java
+++ b/media/java/android/media/AudioDeviceVolumeManager.java
@@ -21,7 +21,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
-import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.content.Context;
import android.os.IBinder;
@@ -44,8 +43,7 @@
@SystemApi
public class AudioDeviceVolumeManager {
- // define when using Log.*
- //private static final String TAG = "AudioDeviceVolumeManager";
+ private static final String TAG = "AudioDeviceVolumeManager";
/** @hide
* Indicates no special treatment in the handling of the volume adjustment */
@@ -70,20 +68,15 @@
private static IAudioService sService;
private final @NonNull String mPackageName;
- private final @Nullable String mAttributionTag;
/**
+ * @hide
* Constructor
* @param context the Context for the device volume operations
*/
- @SuppressLint("ManagerConstructor")
- // reason for suppression: even though the functionality handled by this class is implemented in
- // AudioService, we want to avoid bloating android.media.AudioManager
- // with @SystemApi functionality
public AudioDeviceVolumeManager(@NonNull Context context) {
Objects.requireNonNull(context);
mPackageName = context.getApplicationContext().getOpPackageName();
- mAttributionTag = context.getApplicationContext().getAttributionTag();
}
/**
@@ -325,10 +318,11 @@
* @param ada the device for which volume is to be modified
*/
@SystemApi
+ // TODO alternatively require MODIFY_AUDIO_SYSTEM_SETTINGS when defined
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public void setDeviceVolume(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada) {
try {
- getService().setDeviceVolume(vi, ada, mPackageName, mAttributionTag);
+ getService().setDeviceVolume(vi, ada, mPackageName);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
@@ -336,6 +330,30 @@
/**
* @hide
+ * Returns the volume on the given audio device for the given volume information.
+ * For instance if using a {@link VolumeInfo} configured for {@link AudioManager#STREAM_ALARM},
+ * it will return the alarm volume. When no volume index has ever been set for the given
+ * device, the default volume will be returned (the volume setting that would have been
+ * applied if playback for that use case had started).
+ * @param vi the volume information, only stream-based volumes are supported. Information
+ * other than the stream type is ignored.
+ * @param ada the device for which volume is to be retrieved
+ */
+ @SystemApi
+ // TODO alternatively require MODIFY_AUDIO_SYSTEM_SETTINGS when defined
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public @NonNull VolumeInfo getDeviceVolume(@NonNull VolumeInfo vi,
+ @NonNull AudioDeviceAttributes ada) {
+ try {
+ return getService().getDeviceVolume(vi, ada, mPackageName);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return VolumeInfo.getDefaultVolumeInfo();
+ }
+
+ /**
+ * @hide
* Return human-readable name for volume behavior
* @param behavior one of the volume behaviors defined in AudioManager
* @return a string for the given behavior
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 17d7045..f3931df 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1212,7 +1212,13 @@
}
}
- private static boolean isPublicStreamType(int streamType) {
+ /**
+ * @hide
+ * Checks whether a stream type is part of the public SDK
+ * @param streamType
+ * @return true if the stream type is available in SDK
+ */
+ public static boolean isPublicStreamType(int streamType) {
switch (streamType) {
case STREAM_VOICE_CALL:
case STREAM_SYSTEM:
diff --git a/media/java/android/media/AudioTimestamp.aidl b/media/java/android/media/AudioTimestamp.aidl
new file mode 100644
index 0000000..2c161b3
--- /dev/null
+++ b/media/java/android/media/AudioTimestamp.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+parcelable AudioTimestamp;
diff --git a/media/java/android/media/AudioTimestamp.java b/media/java/android/media/AudioTimestamp.java
index be8ca15..a3277e5 100644
--- a/media/java/android/media/AudioTimestamp.java
+++ b/media/java/android/media/AudioTimestamp.java
@@ -16,11 +16,14 @@
package android.media;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import android.annotation.IntDef;
-
/**
* Structure that groups a position in frame units relative to an assumed audio stream,
* together with the estimated time when that frame enters or leaves the audio
@@ -33,8 +36,7 @@
* @see AudioTrack#getTimestamp AudioTrack.getTimestamp(AudioTimestamp)
* @see AudioRecord#getTimestamp AudioRecord.getTimestamp(AudioTimestamp, int)
*/
-public final class AudioTimestamp
-{
+public final class AudioTimestamp implements Parcelable {
/**
* Clock monotonic or its equivalent on the system,
* in the same units and timebase as {@link java.lang.System#nanoTime}.
@@ -86,4 +88,47 @@
* with a timebase of {@link #TIMEBASE_MONOTONIC}.
*/
public long nanoTime;
+
+ public AudioTimestamp() {
+ }
+
+ private AudioTimestamp(@NonNull Parcel in) {
+ framePosition = in.readLong();
+ nanoTime = in.readLong();
+ }
+
+ @Override
+ public String toString() {
+ return "AudioTimeStamp:"
+ + " framePos=" + framePosition
+ + " nanoTime=" + nanoTime;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeLong(framePosition);
+ dest.writeLong(nanoTime);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Creates an instance from a {@link Parcel}.
+ */
+ @NonNull
+ public static final Creator<AudioTimestamp> CREATOR = new Creator<>() {
+ @Override
+ public AudioTimestamp createFromParcel(@NonNull Parcel in) {
+ return new AudioTimestamp(in);
+ }
+
+ @Override
+ public AudioTimestamp[] newArray(int size) {
+ return new AudioTimestamp[size];
+ }
+ };
}
+
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 7c9e494..2e766d5 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -102,7 +102,10 @@
in String callingPackage, in String attributionTag);
void setDeviceVolume(in VolumeInfo vi, in AudioDeviceAttributes ada,
- in String callingPackage, in String attributionTag);
+ in String callingPackage);
+
+ VolumeInfo getDeviceVolume(in VolumeInfo vi, in AudioDeviceAttributes ada,
+ in String callingPackage);
oneway void handleVolumeKey(in KeyEvent event, boolean isOnTv,
String callingPackage, String caller);
diff --git a/media/java/android/media/VolumeInfo.java b/media/java/android/media/VolumeInfo.java
index 6b4f604..afb44bb 100644
--- a/media/java/android/media/VolumeInfo.java
+++ b/media/java/android/media/VolumeInfo.java
@@ -28,7 +28,6 @@
import android.os.ServiceManager;
import android.util.Log;
-import java.util.List;
import java.util.Objects;
/**
@@ -36,33 +35,36 @@
* A class to represent volume information.
* Can be used to represent volume associated with a stream type or {@link AudioVolumeGroup}.
* Volume index is optional when used to represent a category of volume.
- * Index ranges are supported too, making the representation of volume changes agnostic to the
- * range (e.g. can be used to map BT A2DP absolute volume range to internal range).
+ * Volume ranges are supported too, making the representation of volume changes agnostic regarding
+ * the range of values that are supported (e.g. can be used to map BT A2DP absolute volume range to
+ * internal range).
*/
@SystemApi
public final class VolumeInfo implements Parcelable {
private static final String TAG = "VolumeInfo";
private final boolean mUsesStreamType; // false implies AudioVolumeGroup is used
+ private final boolean mHasMuteCommand;
private final boolean mIsMuted;
private final int mVolIndex;
private final int mMinVolIndex;
private final int mMaxVolIndex;
- private final int mVolGroupId;
- private final int mStreamType;
+ private final @Nullable AudioVolumeGroup mVolGroup;
+ private final @AudioManager.PublicStreamTypes int mStreamType;
private static IAudioService sService;
private static VolumeInfo sDefaultVolumeInfo;
- private VolumeInfo(boolean usesStreamType, boolean isMuted, int volIndex,
- int minVolIndex, int maxVolIndex,
- int volGroupId, int streamType) {
+ private VolumeInfo(boolean usesStreamType, boolean hasMuteCommand, boolean isMuted,
+ int volIndex, int minVolIndex, int maxVolIndex,
+ AudioVolumeGroup volGroup, int streamType) {
mUsesStreamType = usesStreamType;
+ mHasMuteCommand = hasMuteCommand;
mIsMuted = isMuted;
mVolIndex = volIndex;
mMinVolIndex = minVolIndex;
mMaxVolIndex = maxVolIndex;
- mVolGroupId = volGroupId;
+ mVolGroup = volGroup;
mStreamType = streamType;
}
@@ -79,8 +81,10 @@
/**
* Returns the associated stream type, or will throw if {@link #hasStreamType()} returned false.
* @return a stream type value, see AudioManager.STREAM_*
+ * @throws IllegalStateException when called on a VolumeInfo not configured for
+ * stream types.
*/
- public int getStreamType() {
+ public @AudioManager.PublicStreamTypes int getStreamType() {
if (!mUsesStreamType) {
throw new IllegalStateException("VolumeInfo doesn't use stream types");
}
@@ -99,24 +103,28 @@
/**
* Returns the associated volume group, or will throw if {@link #hasVolumeGroup()} returned
* false.
- * @return the volume group corresponding to this VolumeInfo, or null if an error occurred
- * in the volume group management
+ * @return the volume group corresponding to this VolumeInfo
+ * @throws IllegalStateException when called on a VolumeInfo not configured for
+ * volume groups.
*/
- public @Nullable AudioVolumeGroup getVolumeGroup() {
+ public @NonNull AudioVolumeGroup getVolumeGroup() {
if (mUsesStreamType) {
throw new IllegalStateException("VolumeInfo doesn't use AudioVolumeGroup");
}
- List<AudioVolumeGroup> volGroups = AudioVolumeGroup.getAudioVolumeGroups();
- for (AudioVolumeGroup group : volGroups) {
- if (group.getId() == mVolGroupId) {
- return group;
- }
- }
- return null;
+ return mVolGroup;
}
/**
- * Returns whether this instance is conveying a mute state.
+ * Return whether this instance is conveying a mute state
+ * @return true if the muted state was explicitly set for this instance
+ */
+ public boolean hasMuteCommand() {
+ return mHasMuteCommand;
+ }
+
+ /**
+ * Returns whether this instance is conveying a mute state that was explicitly set
+ * by {@link Builder#setMuted(boolean)}, false otherwise
* @return true if the volume state is muted
*/
public boolean isMuted() {
@@ -183,18 +191,21 @@
*/
public static final class Builder {
private boolean mUsesStreamType = true; // false implies AudioVolumeGroup is used
- private int mStreamType = AudioManager.STREAM_MUSIC;
+ private @AudioManager.PublicStreamTypes int mStreamType = AudioManager.STREAM_MUSIC;
+ private boolean mHasMuteCommand = false;
private boolean mIsMuted = false;
private int mVolIndex = INDEX_NOT_SET;
private int mMinVolIndex = INDEX_NOT_SET;
private int mMaxVolIndex = INDEX_NOT_SET;
- private int mVolGroupId = -Integer.MIN_VALUE;
+ private @Nullable AudioVolumeGroup mVolGroup;
/**
* Builder constructor for stream type-based VolumeInfo
*/
- public Builder(int streamType) {
- // TODO validate stream type
+ public Builder(@AudioManager.PublicStreamTypes int streamType) {
+ if (!AudioManager.isPublicStreamType(streamType)) {
+ throw new IllegalArgumentException("Not a valid public stream type " + streamType);
+ }
mUsesStreamType = true;
mStreamType = streamType;
}
@@ -206,7 +217,7 @@
Objects.requireNonNull(volGroup);
mUsesStreamType = false;
mStreamType = -Integer.MIN_VALUE;
- mVolGroupId = volGroup.getId();
+ mVolGroup = volGroup;
}
/**
@@ -217,11 +228,12 @@
Objects.requireNonNull(info);
mUsesStreamType = info.mUsesStreamType;
mStreamType = info.mStreamType;
+ mHasMuteCommand = info.mHasMuteCommand;
mIsMuted = info.mIsMuted;
mVolIndex = info.mVolIndex;
mMinVolIndex = info.mMinVolIndex;
mMaxVolIndex = info.mMaxVolIndex;
- mVolGroupId = info.mVolGroupId;
+ mVolGroup = info.mVolGroup;
}
/**
@@ -230,6 +242,7 @@
* @return the same builder instance
*/
public @NonNull Builder setMuted(boolean isMuted) {
+ mHasMuteCommand = true;
mIsMuted = isMuted;
return this;
}
@@ -239,7 +252,6 @@
* @param volIndex a 0 or greater value, or {@link #INDEX_NOT_SET} if unknown
* @return the same builder instance
*/
- // TODO should we allow muted true + volume index set? (useful when toggling mute on/off?)
public @NonNull Builder setVolumeIndex(int volIndex) {
if (volIndex != INDEX_NOT_SET && volIndex < 0) {
throw new IllegalArgumentException("Volume index cannot be negative");
@@ -294,9 +306,9 @@
throw new IllegalArgumentException("Min volume index:" + mMinVolIndex
+ " greater than max index:" + mMaxVolIndex);
}
- return new VolumeInfo(mUsesStreamType, mIsMuted,
+ return new VolumeInfo(mUsesStreamType, mHasMuteCommand, mIsMuted,
mVolIndex, mMinVolIndex, mMaxVolIndex,
- mVolGroupId, mStreamType);
+ mVolGroup, mStreamType);
}
}
@@ -304,8 +316,8 @@
// Parcelable
@Override
public int hashCode() {
- return Objects.hash(mUsesStreamType, mStreamType, mIsMuted,
- mVolIndex, mMinVolIndex, mMaxVolIndex, mVolGroupId);
+ return Objects.hash(mUsesStreamType, mHasMuteCommand, mStreamType, mIsMuted,
+ mVolIndex, mMinVolIndex, mMaxVolIndex, mVolGroup);
}
@Override
@@ -316,19 +328,20 @@
VolumeInfo that = (VolumeInfo) o;
return ((mUsesStreamType == that.mUsesStreamType)
&& (mStreamType == that.mStreamType)
- && (mIsMuted == that.mIsMuted)
- && (mVolIndex == that.mVolIndex)
- && (mMinVolIndex == that.mMinVolIndex)
- && (mMaxVolIndex == that.mMaxVolIndex)
- && (mVolGroupId == that.mVolGroupId));
+ && (mHasMuteCommand == that.mHasMuteCommand)
+ && (mIsMuted == that.mIsMuted)
+ && (mVolIndex == that.mVolIndex)
+ && (mMinVolIndex == that.mMinVolIndex)
+ && (mMaxVolIndex == that.mMaxVolIndex)
+ && Objects.equals(mVolGroup, that.mVolGroup));
}
@Override
public String toString() {
return new String("VolumeInfo:"
+ (mUsesStreamType ? (" streamType:" + mStreamType)
- : (" volGroupId" + mVolGroupId))
- + " muted:" + mIsMuted
+ : (" volGroup:" + mVolGroup))
+ + (mHasMuteCommand ? (" muted:" + mIsMuted) : ("[no mute cmd]"))
+ ((mVolIndex != INDEX_NOT_SET) ? (" volIndex:" + mVolIndex) : "")
+ ((mMinVolIndex != INDEX_NOT_SET) ? (" min:" + mMinVolIndex) : "")
+ ((mMaxVolIndex != INDEX_NOT_SET) ? (" max:" + mMaxVolIndex) : ""));
@@ -343,21 +356,29 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeBoolean(mUsesStreamType);
dest.writeInt(mStreamType);
+ dest.writeBoolean(mHasMuteCommand);
dest.writeBoolean(mIsMuted);
dest.writeInt(mVolIndex);
dest.writeInt(mMinVolIndex);
dest.writeInt(mMaxVolIndex);
- dest.writeInt(mVolGroupId);
+ if (!mUsesStreamType) {
+ mVolGroup.writeToParcel(dest, 0 /*ignored*/);
+ }
}
private VolumeInfo(@NonNull Parcel in) {
mUsesStreamType = in.readBoolean();
mStreamType = in.readInt();
+ mHasMuteCommand = in.readBoolean();
mIsMuted = in.readBoolean();
mVolIndex = in.readInt();
mMinVolIndex = in.readInt();
mMaxVolIndex = in.readInt();
- mVolGroupId = in.readInt();
+ if (!mUsesStreamType) {
+ mVolGroup = AudioVolumeGroup.CREATOR.createFromParcel(in);
+ } else {
+ mVolGroup = null;
+ }
}
public static final @NonNull Parcelable.Creator<VolumeInfo> CREATOR =
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index dd3c967..1ee2a26 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -7,6 +7,7 @@
<string name="string_create_in_another_place">Create in another place</string>
<string name="string_save_to_another_place">Save to another place</string>
<string name="string_use_another_device">Use another device</string>
+ <string name="string_save_to_another_device">Save to another device</string>
<string name="string_no_thanks">No thanks</string>
<string name="passkey_creation_intro_title">A simple way to sign in safely</string>
<string name="passkey_creation_intro_body">Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more</string>
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 8bff8bc..2bede9a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -172,6 +172,7 @@
{CreateScreenState.PASSKEY_INTRO} else {CreateScreenState.PROVIDER_SELECTION}
} else {CreateScreenState.CREATION_OPTION_SELECTION},
requestDisplayInfo,
+ false,
if (hasDefault) {
ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
} else null
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 87d8a73b..9f73aef 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -80,14 +80,16 @@
enabledProviderList = uiState.enabledProviders,
disabledProviderList = uiState.disabledProviders,
onCancel = viewModel::onCancel,
- onOptionSelected = viewModel::onMoreOptionsRowSelectedForFirstUse,
+ onOptionSelected = viewModel::onEntrySelectedFromFirstUseScreen,
onDisabledPasswordManagerSelected = viewModel::onDisabledPasswordManagerSelected,
+ onRemoteEntrySelected = selectEntryCallback,
)
CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
requestDisplayInfo = uiState.requestDisplayInfo,
enabledProviderList = uiState.enabledProviders,
providerInfo = uiState.activeEntry?.activeProvider!!,
createOptionInfo = uiState.activeEntry.activeEntryInfo as CreateOptionInfo,
+ showActiveEntryOnly = uiState.showActiveEntryOnly,
onOptionSelected = selectEntryCallback,
onConfirm = confirmEntryCallback,
onCancel = viewModel::onCancel,
@@ -98,7 +100,7 @@
enabledProviderList = uiState.enabledProviders,
disabledProviderList = uiState.disabledProviders,
onBackButtonSelected = viewModel::onBackButtonSelected,
- onOptionSelected = viewModel::onMoreOptionsRowSelected,
+ onOptionSelected = viewModel::onEntrySelectedFromMoreOptionScreen,
onDisabledPasswordManagerSelected = viewModel::onDisabledPasswordManagerSelected,
onRemoteEntrySelected = selectEntryCallback,
)
@@ -184,7 +186,8 @@
disabledProviderList: List<DisabledProviderInfo>?,
onOptionSelected: (ActiveEntry) -> Unit,
onDisabledPasswordManagerSelected: () -> Unit,
- onCancel: () -> Unit
+ onCancel: () -> Unit,
+ onRemoteEntrySelected: (EntryInfo) -> Unit,
) {
Card() {
Column() {
@@ -254,10 +257,23 @@
}
}
}
- Divider(
- thickness = 24.dp,
- color = Color.Transparent
- )
+ // TODO: handle the error situation that if multiple remoteInfos exists
+ enabledProviderList.forEach { enabledProvider ->
+ if (enabledProvider.remoteEntry != null) {
+ TextButton(
+ onClick = {
+ onRemoteEntrySelected(enabledProvider.remoteEntry!!) },
+ modifier = Modifier
+ .padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally)
+ ) {
+ Text(
+ text = stringResource(R.string.string_save_to_another_device),
+ textAlign = TextAlign.Center,
+ )
+ }
+ }
+ }
Row(
horizontalArrangement = Arrangement.Start,
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
@@ -416,6 +432,7 @@
enabledProviderList: List<EnabledProviderInfo>,
providerInfo: EnabledProviderInfo,
createOptionInfo: CreateOptionInfo,
+ showActiveEntryOnly: Boolean,
onOptionSelected: (EntryInfo) -> Unit,
onConfirm: () -> Unit,
onCancel: () -> Unit,
@@ -473,41 +490,43 @@
onOptionSelected = onOptionSelected
)
}
- var createOptionsSize = 0
- enabledProviderList.forEach{
- enabledProvider -> createOptionsSize += enabledProvider.createOptions.size}
- if (createOptionsSize > 1) {
- TextButton(
- onClick = onMoreOptionsSelected,
- modifier = Modifier
- .padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally)){
- Text(
- text =
- when (requestDisplayInfo.type) {
- TYPE_PUBLIC_KEY_CREDENTIAL ->
- stringResource(R.string.string_create_in_another_place)
- else -> stringResource(R.string.string_save_to_another_place)},
- textAlign = TextAlign.Center,
- )
- }
- } else if (
- requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL
- ) {
- // TODO: handle the error situation that if multiple remoteInfos exists
- enabledProviderList.forEach { enabledProvider ->
- if (enabledProvider.remoteEntry != null) {
- TextButton(
- onClick = {
- onOptionSelected(enabledProvider.remoteEntry!!) },
- modifier = Modifier
- .padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally)
- ) {
- Text(
- text = stringResource(R.string.string_use_another_device),
- textAlign = TextAlign.Center,
- )
+ if (!showActiveEntryOnly) {
+ var createOptionsSize = 0
+ enabledProviderList.forEach{
+ enabledProvider -> createOptionsSize += enabledProvider.createOptions.size}
+ if (createOptionsSize > 1) {
+ TextButton(
+ onClick = onMoreOptionsSelected,
+ modifier = Modifier
+ .padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally)){
+ Text(
+ text =
+ when (requestDisplayInfo.type) {
+ TYPE_PUBLIC_KEY_CREDENTIAL ->
+ stringResource(R.string.string_create_in_another_place)
+ else -> stringResource(R.string.string_save_to_another_place)},
+ textAlign = TextAlign.Center,
+ )
+ }
+ } else if (
+ requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL
+ ) {
+ // TODO: handle the error situation that if multiple remoteInfos exists
+ enabledProviderList.forEach { enabledProvider ->
+ if (enabledProvider.remoteEntry != null) {
+ TextButton(
+ onClick = {
+ onOptionSelected(enabledProvider.remoteEntry!!) },
+ modifier = Modifier
+ .padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally)
+ ) {
+ Text(
+ text = stringResource(R.string.string_use_another_device),
+ textAlign = TextAlign.Center,
+ )
+ }
}
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
index f7e9fd0..0f685a1 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
@@ -36,6 +36,7 @@
val disabledProviders: List<DisabledProviderInfo>? = null,
val currentScreenState: CreateScreenState,
val requestDisplayInfo: RequestDisplayInfo,
+ val showActiveEntryOnly: Boolean,
val activeEntry: ActiveEntry? = null,
val selectedEntry: EntryInfo? = null,
)
@@ -56,13 +57,18 @@
}
fun onConfirmIntro() {
- if (uiState.enabledProviders.size > 1) {
- uiState = uiState.copy(
- currentScreenState = CreateScreenState.PROVIDER_SELECTION
+ var createOptionSize = 0
+ uiState.enabledProviders.forEach {
+ enabledProvider -> createOptionSize += enabledProvider.createOptions.size}
+ uiState = if (createOptionSize > 1) {
+ uiState.copy(
+ currentScreenState = CreateScreenState.PROVIDER_SELECTION,
+ showActiveEntryOnly = true
)
- } else if (uiState.enabledProviders.size == 1){
- uiState = uiState.copy(
+ } else if (createOptionSize == 1){
+ uiState.copy(
currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
+ showActiveEntryOnly = false,
activeEntry = ActiveEntry(uiState.enabledProviders.first(),
uiState.enabledProviders.first().createOptions.first()
)
@@ -90,16 +96,18 @@
)
}
- fun onMoreOptionsRowSelected(activeEntry: ActiveEntry) {
+ fun onEntrySelectedFromMoreOptionScreen(activeEntry: ActiveEntry) {
uiState = uiState.copy(
currentScreenState = CreateScreenState.MORE_OPTIONS_ROW_INTRO,
+ showActiveEntryOnly = false,
activeEntry = activeEntry
)
}
- fun onMoreOptionsRowSelectedForFirstUse(activeEntry: ActiveEntry) {
+ fun onEntrySelectedFromFirstUseScreen(activeEntry: ActiveEntry) {
uiState = uiState.copy(
currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
+ showActiveEntryOnly = true,
activeEntry = activeEntry
)
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 941e770..6d20c91 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -23,6 +23,7 @@
import com.android.settingslib.spa.framework.common.SpaEnvironment
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
+import com.android.settingslib.spa.gallery.dialog.AlterDialogPageProvider
import com.android.settingslib.spa.gallery.home.HomePageProvider
import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
import com.android.settingslib.spa.gallery.page.ChartPageProvider
@@ -72,6 +73,7 @@
ActionButtonPageProvider,
ProgressBarPageProvider,
ChartPageProvider,
+ AlterDialogPageProvider,
),
rootPages = listOf(
HomePageProvider.createSettingsPage(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/AlterDialogPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/AlterDialogPage.kt
new file mode 100644
index 0000000..a57df0f
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/AlterDialogPage.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.dialog
+
+import android.os.Bundle
+import androidx.compose.material3.Text
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.widget.dialog.AlertDialogButton
+import com.android.settingslib.spa.widget.dialog.rememberAlertDialogPresenter
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+
+private const val TITLE = "AlterDialogPage"
+
+object AlterDialogPageProvider : SettingsPageProvider {
+ override val name = "AlterDialogPage"
+ private val owner = createSettingsPage()
+
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> = listOf(
+ SettingsEntryBuilder.create("AlterDialog", owner).setUiLayoutFn {
+ val alertDialogPresenter = rememberAlertDialogPresenter(
+ confirmButton = AlertDialogButton("Ok"),
+ dismissButton = AlertDialogButton("Cancel"),
+ title = "Title",
+ text = { Text("Text") },
+ )
+ Preference(object : PreferenceModel {
+ override val title = "Show AlterDialog"
+ override val onClick = alertDialogPresenter::open
+ })
+ }.build(),
+ )
+
+ fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner)
+ .setIsAllowSearch(true)
+ .setUiLayoutFn {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
+
+ override fun getTitle(arguments: Bundle?) = TITLE
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
index 83c72c7..6d53dae 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
@@ -27,6 +27,7 @@
import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
+import com.android.settingslib.spa.gallery.dialog.AlterDialogPageProvider
import com.android.settingslib.spa.gallery.page.ArgumentPageModel
import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
import com.android.settingslib.spa.gallery.page.ChartPageProvider
@@ -58,6 +59,7 @@
ActionButtonPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
ProgressBarPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
ChartPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ AlterDialogPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
)
}
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index 8543596..b1d8d0d 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -55,6 +55,8 @@
}
dependencies {
+ String jetpack_lifecycle_version = "2.6.0-alpha03"
+
api "androidx.appcompat:appcompat:1.7.0-alpha01"
api "androidx.slice:slice-builders:1.1.0-alpha02"
api "androidx.slice:slice-core:1.1.0-alpha02"
@@ -63,8 +65,9 @@
api "androidx.compose.material:material-icons-extended:$jetpack_compose_version"
api "androidx.compose.runtime:runtime-livedata:$jetpack_compose_version"
api "androidx.compose.ui:ui-tooling-preview:$jetpack_compose_version"
- api "androidx.lifecycle:lifecycle-livedata-ktx:2.6.0-alpha03"
- api "androidx.navigation:navigation-compose:2.6.0-alpha03"
+ api "androidx.lifecycle:lifecycle-livedata-ktx:$jetpack_lifecycle_version"
+ api "androidx.lifecycle:lifecycle-runtime-compose:$jetpack_lifecycle_version"
+ api "androidx.navigation:navigation-compose:2.6.0-alpha04"
api "com.github.PhilJay:MPAndroidChart:v3.1.0-alpha"
api "com.google.android.material:material:1.7.0-alpha03"
debugApi "androidx.compose.ui:ui-tooling:$jetpack_compose_version"
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
new file mode 100644
index 0000000..d6f5328
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.dialog
+
+import android.content.res.Configuration
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.DialogProperties
+
+data class AlertDialogButton(
+ val text: String,
+ val onClick: () -> Unit = {},
+)
+
+interface AlertDialogPresenter {
+ /** Opens the dialog. */
+ fun open()
+
+ /** Closes the dialog. */
+ fun close()
+}
+
+@Composable
+fun rememberAlertDialogPresenter(
+ confirmButton: AlertDialogButton? = null,
+ dismissButton: AlertDialogButton? = null,
+ title: String? = null,
+ text: @Composable (() -> Unit)? = null,
+): AlertDialogPresenter {
+ var openDialog by rememberSaveable { mutableStateOf(false) }
+ val alertDialogPresenter = remember {
+ object : AlertDialogPresenter {
+ override fun open() {
+ openDialog = true
+ }
+
+ override fun close() {
+ openDialog = false
+ }
+ }
+ }
+ if (openDialog) {
+ alertDialogPresenter.SettingsAlertDialog(confirmButton, dismissButton, title, text)
+ }
+ return alertDialogPresenter
+}
+
+@OptIn(ExperimentalComposeUiApi::class)
+@Composable
+private fun AlertDialogPresenter.SettingsAlertDialog(
+ confirmButton: AlertDialogButton?,
+ dismissButton: AlertDialogButton?,
+ title: String?,
+ text: @Composable (() -> Unit)?,
+) {
+ val configuration = LocalConfiguration.current
+ AlertDialog(
+ onDismissRequest = ::close,
+ modifier = when (configuration.orientation) {
+ Configuration.ORIENTATION_LANDSCAPE -> {
+ Modifier.width(configuration.screenWidthDp.dp * 0.6f)
+ }
+ else -> Modifier
+ },
+ confirmButton = { confirmButton?.let { Button(it) } },
+ dismissButton = dismissButton?.let { { Button(it) } },
+ title = title?.let { { Text(it) } },
+ text = text?.let {
+ {
+ Column(Modifier.verticalScroll(rememberScrollState())) {
+ text()
+ }
+ }
+ },
+ properties = DialogProperties(usePlatformDefaultWidth = false),
+ )
+}
+
+@Composable
+private fun AlertDialogPresenter.Button(button: AlertDialogButton) {
+ TextButton(
+ onClick = {
+ close()
+ button.onClick()
+ },
+ ) {
+ Text(button.text)
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt
index 5e201df..84fea15 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt
@@ -17,7 +17,6 @@
package com.android.settingslib.spa.widget.scaffold
import androidx.appcompat.R
-import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.MoreVert
import androidx.compose.material3.DropdownMenu
@@ -36,7 +35,7 @@
/**
* Scope for the children of [MoreOptionsAction].
*/
-interface MoreOptionsScope : ColumnScope {
+interface MoreOptionsScope {
fun dismiss()
@Composable
@@ -61,7 +60,7 @@
val onDismiss = { expanded = false }
DropdownMenu(expanded = expanded, onDismissRequest = onDismiss) {
val moreOptionsScope = remember(this) {
- object : MoreOptionsScope, ColumnScope by this {
+ object : MoreOptionsScope {
override fun dismiss() {
onDismiss()
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogTest.kt
new file mode 100644
index 0000000..9468f95
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogTest.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.dialog
+
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.onDialogText
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsAlertDialogTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun title_whenDialogNotOpen_notDisplayed() {
+ composeTestRule.setContent {
+ rememberAlertDialogPresenter(title = TITLE)
+ }
+
+ composeTestRule.onDialogText(TITLE).assertDoesNotExist()
+ }
+
+ @Test
+ fun title_displayed() {
+ setAndOpenDialog {
+ rememberAlertDialogPresenter(title = TITLE)
+ }
+
+ composeTestRule.onDialogText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun text_displayed() {
+ setAndOpenDialog {
+ rememberAlertDialogPresenter(text = { Text(TEXT) })
+ }
+
+ composeTestRule.onDialogText(TEXT).assertIsDisplayed()
+ }
+
+ @Test
+ fun confirmButton_displayed() {
+ setAndOpenDialog {
+ rememberAlertDialogPresenter(confirmButton = AlertDialogButton(CONFIRM_TEXT))
+ }
+
+ composeTestRule.onDialogText(CONFIRM_TEXT).assertIsDisplayed()
+ }
+
+ @Test
+ fun confirmButton_clickable() {
+ var confirmButtonClicked = false
+ setAndOpenDialog {
+ rememberAlertDialogPresenter(confirmButton = AlertDialogButton(CONFIRM_TEXT) {
+ confirmButtonClicked = true
+ })
+ }
+
+ composeTestRule.onDialogText(CONFIRM_TEXT).performClick()
+
+ assertThat(confirmButtonClicked).isTrue()
+ }
+
+ @Test
+ fun dismissButton_displayed() {
+ setAndOpenDialog {
+ rememberAlertDialogPresenter(dismissButton = AlertDialogButton(DISMISS_TEXT))
+ }
+
+ composeTestRule.onDialogText(DISMISS_TEXT).assertIsDisplayed()
+ }
+
+ @Test
+ fun dismissButton_clickable() {
+ var dismissButtonClicked = false
+ setAndOpenDialog {
+ rememberAlertDialogPresenter(dismissButton = AlertDialogButton(DISMISS_TEXT) {
+ dismissButtonClicked = true
+ })
+ }
+
+ composeTestRule.onDialogText(DISMISS_TEXT).performClick()
+
+ assertThat(dismissButtonClicked).isTrue()
+ }
+
+ private fun setAndOpenDialog(dialog: @Composable () -> AlertDialogPresenter) {
+ composeTestRule.setContent {
+ val dialogPresenter = dialog()
+ LaunchedEffect(Unit) {
+ dialogPresenter.open()
+ }
+ }
+ }
+
+ private companion object {
+ const val CONFIRM_TEXT = "Confirm"
+ const val DISMISS_TEXT = "Dismiss"
+ const val TITLE = "Title"
+ const val TEXT = "Text"
+ }
+}
diff --git a/packages/SystemUI/res/values-h700dp/dimens.xml b/packages/SystemUI/customization/res/values-h700dp/dimens.xml
similarity index 92%
rename from packages/SystemUI/res/values-h700dp/dimens.xml
rename to packages/SystemUI/customization/res/values-h700dp/dimens.xml
index fbd985e..2a15981a 100644
--- a/packages/SystemUI/res/values-h700dp/dimens.xml
+++ b/packages/SystemUI/customization/res/values-h700dp/dimens.xml
@@ -1,5 +1,5 @@
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ Copyright (C) 2022 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -17,4 +17,4 @@
<resources>
<!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) -->
<dimen name="large_clock_text_size">170dp</dimen>
-</resources>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-h700dp/dimens.xml b/packages/SystemUI/customization/res/values-h800dp/dimens.xml
similarity index 86%
copy from packages/SystemUI/res/values-h700dp/dimens.xml
copy to packages/SystemUI/customization/res/values-h800dp/dimens.xml
index fbd985e..60afc8a 100644
--- a/packages/SystemUI/res/values-h700dp/dimens.xml
+++ b/packages/SystemUI/customization/res/values-h800dp/dimens.xml
@@ -1,5 +1,5 @@
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ Copyright (C) 2022 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -16,5 +16,5 @@
<resources>
<!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) -->
- <dimen name="large_clock_text_size">170dp</dimen>
+ <dimen name="large_clock_text_size">200dp</dimen>
</resources>
diff --git a/packages/SystemUI/customization/res/values/dimens.xml b/packages/SystemUI/customization/res/values/dimens.xml
index 8f90f0f..ba8f284 100644
--- a/packages/SystemUI/customization/res/values/dimens.xml
+++ b/packages/SystemUI/customization/res/values/dimens.xml
@@ -17,8 +17,8 @@
-->
<resources>
<!-- Clock maximum font size (dp is intentional, to prevent any further scaling) -->
- <dimen name="large_clock_text_size">150sp</dimen>
- <dimen name="small_clock_text_size">86sp</dimen>
+ <dimen name="large_clock_text_size">150dp</dimen>
+ <dimen name="small_clock_text_size">86dp</dimen>
<!-- Default line spacing multiplier between hours and minutes of the keyguard clock -->
<item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item>
diff --git a/packages/SystemUI/res/drawable/ic_ring_volume.xml b/packages/SystemUI/res/drawable/ic_ring_volume.xml
new file mode 100644
index 0000000..343fe5d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_ring_volume.xml
@@ -0,0 +1,26 @@
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:pathData="M11,7V2H13V7ZM17.6,9.85 L16.2,8.4 19.75,4.85 21.15,6.3ZM6.4,9.85 L2.85,6.3 4.25,4.85 7.8,8.4ZM12,12Q14.95,12 17.812,13.188Q20.675,14.375 22.9,16.75Q23.2,17.05 23.2,17.45Q23.2,17.85 22.9,18.15L20.6,20.4Q20.325,20.675 19.963,20.7Q19.6,20.725 19.3,20.5L16.4,18.3Q16.2,18.15 16.1,17.95Q16,17.75 16,17.5V14.65Q15.05,14.35 14.05,14.175Q13.05,14 12,14Q10.95,14 9.95,14.175Q8.95,14.35 8,14.65V17.5Q8,17.75 7.9,17.95Q7.8,18.15 7.6,18.3L4.7,20.5Q4.4,20.725 4.038,20.7Q3.675,20.675 3.4,20.4L1.1,18.15Q0.8,17.85 0.8,17.45Q0.8,17.05 1.1,16.75Q3.3,14.375 6.175,13.188Q9.05,12 12,12ZM6,15.35Q5.275,15.725 4.6,16.212Q3.925,16.7 3.2,17.3L4.2,18.3L6,16.9ZM18,15.4V16.9L19.8,18.3L20.8,17.35Q20.075,16.7 19.4,16.225Q18.725,15.75 18,15.4ZM6,15.35Q6,15.35 6,15.35Q6,15.35 6,15.35ZM18,15.4Q18,15.4 18,15.4Q18,15.4 18,15.4Z"
+ android:fillColor="?android:attr/colorPrimary"/>
+
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_ring_volume_off.xml b/packages/SystemUI/res/drawable/ic_ring_volume_off.xml
new file mode 100644
index 0000000..74f30d1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_ring_volume_off.xml
@@ -0,0 +1,34 @@
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/colorControlNormal">
+<path
+ android:pathData="M0.8,4.2l8.1,8.1c-2.2,0.5 -5.2,1.6 -7.8,4.4c-0.4,0.4 -0.4,1 0,1.4l2.3,2.3c0.3,0.3 0.9,0.4 1.3,0.1l2.9,-2.2C7.8,18.1 8,17.8 8,17.5v-2.9c0.9,-0.3 1.7,-0.5 2.7,-0.6l8.5,8.5l1.4,-1.4L2.2,2.8L0.8,4.2z"
+ android:fillColor="?android:attr/colorPrimary"/>
+ <path
+ android:pathData="M11,2h2v5h-2z"
+ android:fillColor="?android:attr/colorPrimary"/>
+ <path
+ android:pathData="M21.2,6.3l-1.4,-1.4l-3.6,3.6l1.4,1.4C17.6,9.8 21,6.3 21.2,6.3z"
+ android:fillColor="?android:attr/colorPrimary"/>
+ <path
+ android:pathData="M22.9,16.7c-2.8,-3 -6.2,-4.1 -8.4,-4.5l7.2,7.2l1.3,-1.3C23.3,17.7 23.3,17.1 22.9,16.7z"
+ android:fillColor="?android:attr/colorPrimary"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_speaker_mute.xml b/packages/SystemUI/res/drawable/ic_speaker_mute.xml
new file mode 100644
index 0000000..4e402cf
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_speaker_mute.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/textColorPrimary"
+ android:autoMirrored="true">
+ <path android:fillColor="#FFFFFFFF"
+ android:pathData="M19.8,22.6 L16.775,19.575Q16.15,19.975 15.45,20.263Q14.75,20.55 14,20.725V18.675Q14.35,18.55 14.688,18.425Q15.025,18.3 15.325,18.125L12,14.8V20L7,15H3V9H6.2L1.4,4.2L2.8,2.8L21.2,21.2ZM19.6,16.8 L18.15,15.35Q18.575,14.575 18.788,13.725Q19,12.875 19,11.975Q19,9.625 17.625,7.775Q16.25,5.925 14,5.275V3.225Q17.1,3.925 19.05,6.362Q21,8.8 21,11.975Q21,13.3 20.638,14.525Q20.275,15.75 19.6,16.8ZM16.25,13.45 L14,11.2V7.95Q15.175,8.5 15.838,9.6Q16.5,10.7 16.5,12Q16.5,12.375 16.438,12.738Q16.375,13.1 16.25,13.45ZM12,9.2 L9.4,6.6 12,4ZM10,15.15V12.8L8.2,11H5V13H7.85ZM9.1,11.9Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_speaker_on.xml b/packages/SystemUI/res/drawable/ic_speaker_on.xml
new file mode 100644
index 0000000..2a90e05
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_speaker_on.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/textColorPrimary"
+ android:autoMirrored="true">
+ <path android:fillColor="#FFFFFFFF"
+ android:pathData="M14,20.725V18.675Q16.25,18.025 17.625,16.175Q19,14.325 19,11.975Q19,9.625 17.625,7.775Q16.25,5.925 14,5.275V3.225Q17.1,3.925 19.05,6.362Q21,8.8 21,11.975Q21,15.15 19.05,17.587Q17.1,20.025 14,20.725ZM3,15V9H7L12,4V20L7,15ZM14,16V7.95Q15.175,8.5 15.838,9.6Q16.5,10.7 16.5,12Q16.5,13.275 15.838,14.362Q15.175,15.45 14,16ZM10,8.85 L7.85,11H5V13H7.85L10,15.15ZM7.5,12Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-h800dp/dimens.xml b/packages/SystemUI/res/values-h800dp/dimens.xml
index 94fe209..8efd6f0 100644
--- a/packages/SystemUI/res/values-h800dp/dimens.xml
+++ b/packages/SystemUI/res/values-h800dp/dimens.xml
@@ -15,9 +15,6 @@
-->
<resources>
- <!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) -->
- <dimen name="large_clock_text_size">200dp</dimen>
-
<!-- With the large clock, move up slightly from the center -->
<dimen name="keyguard_large_clock_top_margin">-112dp</dimen>
</resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
index cbd0875..9999f08 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
@@ -117,7 +117,7 @@
@Override
public void onSampleCollected(float medianLuma) {
if (mSamplingEnabled) {
- updateMediaLuma(medianLuma);
+ updateMedianLuma(medianLuma);
}
}
};
@@ -261,7 +261,7 @@
}
}
- private void updateMediaLuma(float medianLuma) {
+ private void updateMedianLuma(float medianLuma) {
mCurrentMedianLuma = medianLuma;
// If the difference between the new luma and the current luma is larger than threshold
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 268b55e..e2f86bd 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -205,10 +205,6 @@
@JvmField val QS_SECONDARY_DATA_SUB_INFO = releasedFlag(508, "qs_secondary_data_sub_info")
// 600- status bar
- // TODO(b/254513246): Tracking Bug
- val STATUS_BAR_USER_SWITCHER =
- resourceBooleanFlag(602, R.bool.flag_user_switcher_chip, "status_bar_user_switcher")
-
// TODO(b/254512623): Tracking Bug
@Deprecated("Replaced by mobile and wifi specific flags.")
val NEW_STATUS_BAR_PIPELINE_BACKEND =
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 81b839e..833a6a4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -72,6 +72,7 @@
import android.os.SystemClock;
import android.os.Trace;
import android.os.VibrationEffect;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.text.InputFilter;
@@ -108,6 +109,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.graphics.drawable.BackgroundBlurDrawable;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.view.RotationPolicy;
@@ -125,11 +128,15 @@
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.AlphaTintDrawableWrapper;
+import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.RoundedCornerProgressDrawable;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -186,6 +193,9 @@
private ViewGroup mDialogRowsView;
private ViewGroup mRinger;
+ private DeviceConfigProxy mDeviceConfigProxy;
+ private Executor mExecutor;
+
/**
* Container for the top part of the dialog, which contains the ringer, the ringer drawer, the
* volume rows, and the ellipsis button. This does not include the live caption button.
@@ -274,6 +284,13 @@
private BackgroundBlurDrawable mDialogRowsViewBackground;
private final InteractionJankMonitor mInteractionJankMonitor;
+ private boolean mSeparateNotification;
+
+ @VisibleForTesting
+ int mVolumeRingerIconDrawableId;
+ @VisibleForTesting
+ int mVolumeRingerMuteIconDrawableId;
+
public VolumeDialogImpl(
Context context,
VolumeDialogController volumeDialogController,
@@ -283,7 +300,9 @@
MediaOutputDialogFactory mediaOutputDialogFactory,
VolumePanelFactory volumePanelFactory,
ActivityStarter activityStarter,
- InteractionJankMonitor interactionJankMonitor) {
+ InteractionJankMonitor interactionJankMonitor,
+ DeviceConfigProxy deviceConfigProxy,
+ Executor executor) {
mContext =
new ContextThemeWrapper(context, R.style.volume_dialog_theme);
mController = volumeDialogController;
@@ -323,6 +342,50 @@
}
initDimens();
+
+ mDeviceConfigProxy = deviceConfigProxy;
+ mExecutor = executor;
+ mSeparateNotification = mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false);
+ updateRingerModeIconSet();
+ }
+
+ /**
+ * If ringer and notification are the same stream (T and earlier), use notification-like bell
+ * icon set.
+ * If ringer and notification are separated, then use generic speaker icons.
+ */
+ private void updateRingerModeIconSet() {
+ if (mSeparateNotification) {
+ mVolumeRingerIconDrawableId = R.drawable.ic_speaker_on;
+ mVolumeRingerMuteIconDrawableId = R.drawable.ic_speaker_mute;
+ } else {
+ mVolumeRingerIconDrawableId = R.drawable.ic_volume_ringer;
+ mVolumeRingerMuteIconDrawableId = R.drawable.ic_volume_ringer_mute;
+ }
+
+ if (mRingerDrawerMuteIcon != null) {
+ mRingerDrawerMuteIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
+ }
+ if (mRingerDrawerNormalIcon != null) {
+ mRingerDrawerNormalIcon.setImageResource(mVolumeRingerIconDrawableId);
+ }
+ }
+
+ /**
+ * Change icon for ring stream (not ringer mode icon)
+ */
+ private void updateRingRowIcon() {
+ Optional<VolumeRow> volumeRow = mRows.stream().filter(row -> row.stream == STREAM_RING)
+ .findFirst();
+ if (volumeRow.isPresent()) {
+ VolumeRow volRow = volumeRow.get();
+ volRow.iconRes = mSeparateNotification ? R.drawable.ic_ring_volume
+ : R.drawable.ic_volume_ringer;
+ volRow.iconMuteRes = mSeparateNotification ? R.drawable.ic_ring_volume_off
+ : R.drawable.ic_volume_ringer_mute;
+ volRow.setIcon(volRow.iconRes, mContext.getTheme());
+ }
}
@Override
@@ -339,6 +402,9 @@
mController.getState();
mConfigurationController.addCallback(this);
+
+ mDeviceConfigProxy.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+ mExecutor, this::onDeviceConfigChange);
}
@Override
@@ -346,6 +412,24 @@
mController.removeCallback(mControllerCallbackH);
mHandler.removeCallbacksAndMessages(null);
mConfigurationController.removeCallback(this);
+ mDeviceConfigProxy.removeOnPropertiesChangedListener(this::onDeviceConfigChange);
+ }
+
+ /**
+ * Update ringer mode icon based on the config
+ */
+ private void onDeviceConfigChange(DeviceConfig.Properties properties) {
+ Set<String> changeSet = properties.getKeyset();
+ if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) {
+ boolean newVal = properties.getBoolean(
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false);
+ if (newVal != mSeparateNotification) {
+ mSeparateNotification = newVal;
+ updateRingerModeIconSet();
+ updateRingRowIcon();
+
+ }
+ }
}
@Override
@@ -554,6 +638,8 @@
mRingerDrawerNormalIcon = mDialog.findViewById(R.id.volume_drawer_normal_icon);
mRingerDrawerNewSelectionBg = mDialog.findViewById(R.id.volume_drawer_selection_background);
+ updateRingerModeIconSet();
+
setupRingerDrawer();
mODICaptionsView = mDialog.findViewById(R.id.odi_captions);
@@ -577,8 +663,14 @@
addRow(AudioManager.STREAM_MUSIC,
R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true, true);
if (!AudioSystem.isSingleVolume(mContext)) {
- addRow(AudioManager.STREAM_RING,
- R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true, false);
+ if (mSeparateNotification) {
+ addRow(AudioManager.STREAM_RING, R.drawable.ic_ring_volume,
+ R.drawable.ic_ring_volume_off, true, false);
+ } else {
+ addRow(AudioManager.STREAM_RING, R.drawable.ic_volume_ringer,
+ R.drawable.ic_volume_ringer, true, false);
+ }
+
addRow(STREAM_ALARM,
R.drawable.ic_alarm, R.drawable.ic_volume_alarm_mute, true, false);
addRow(AudioManager.STREAM_VOICE_CALL,
@@ -1534,8 +1626,8 @@
mRingerIcon.setTag(Events.ICON_STATE_VIBRATE);
break;
case AudioManager.RINGER_MODE_SILENT:
- mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
- mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
+ mRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
+ mSelectedRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
mRingerIcon.setTag(Events.ICON_STATE_MUTE);
addAccessibilityDescription(mRingerIcon, RINGER_MODE_SILENT,
mContext.getString(R.string.volume_ringer_hint_unmute));
@@ -1544,14 +1636,14 @@
default:
boolean muted = (mAutomute && ss.level == 0) || ss.muted;
if (!isZenMuted && muted) {
- mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
- mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
+ mRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
+ mSelectedRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL,
mContext.getString(R.string.volume_ringer_hint_unmute));
mRingerIcon.setTag(Events.ICON_STATE_MUTE);
} else {
- mRingerIcon.setImageResource(R.drawable.ic_volume_ringer);
- mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer);
+ mRingerIcon.setImageResource(mVolumeRingerIconDrawableId);
+ mSelectedRingerIcon.setImageResource(mVolumeRingerIconDrawableId);
if (mController.hasVibrator()) {
addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL,
mContext.getString(R.string.volume_ringer_hint_vibrate));
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index c5792b9..8f10fa6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -20,6 +20,7 @@
import android.media.AudioManager;
import com.android.internal.jank.InteractionJankMonitor;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialog;
@@ -27,11 +28,14 @@
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.volume.VolumeComponent;
import com.android.systemui.volume.VolumeDialogComponent;
import com.android.systemui.volume.VolumeDialogImpl;
import com.android.systemui.volume.VolumePanelFactory;
+import java.util.concurrent.Executor;
+
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
@@ -55,7 +59,9 @@
MediaOutputDialogFactory mediaOutputDialogFactory,
VolumePanelFactory volumePanelFactory,
ActivityStarter activityStarter,
- InteractionJankMonitor interactionJankMonitor) {
+ InteractionJankMonitor interactionJankMonitor,
+ DeviceConfigProxy deviceConfigProxy,
+ @Main Executor executor) {
VolumeDialogImpl impl = new VolumeDialogImpl(
context,
volumeDialogController,
@@ -65,7 +71,9 @@
mediaOutputDialogFactory,
volumePanelFactory,
activityStarter,
- interactionJankMonitor);
+ interactionJankMonitor,
+ deviceConfigProxy,
+ executor);
impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
impl.setAutomute(true);
impl.setSilentMode(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 2e74bf5..a0b4eab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -18,6 +18,7 @@
import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
+import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -28,6 +29,7 @@
import android.app.KeyguardManager;
import android.media.AudioManager;
import android.os.SystemClock;
+import android.provider.DeviceConfig;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.InputDevice;
@@ -38,6 +40,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.systemui.Prefs;
import com.android.systemui.R;
@@ -49,6 +52,9 @@
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.util.DeviceConfigProxyFake;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -71,6 +77,8 @@
View mDrawerVibrate;
View mDrawerMute;
View mDrawerNormal;
+ private DeviceConfigProxyFake mDeviceConfigProxy;
+ private FakeExecutor mExecutor;
@Mock
VolumeDialogController mVolumeDialogController;
@@ -97,6 +105,9 @@
getContext().addMockSystemService(KeyguardManager.class, mKeyguard);
+ mDeviceConfigProxy = new DeviceConfigProxyFake();
+ mExecutor = new FakeExecutor(new FakeSystemClock());
+
mDialog = new VolumeDialogImpl(
getContext(),
mVolumeDialogController,
@@ -106,7 +117,9 @@
mMediaOutputDialogFactory,
mVolumePanelFactory,
mActivityStarter,
- mInteractionJankMonitor);
+ mInteractionJankMonitor,
+ mDeviceConfigProxy,
+ mExecutor);
mDialog.init(0, null);
State state = createShellState();
mDialog.onStateChangedH(state);
@@ -123,6 +136,9 @@
VolumePrefs.SHOW_RINGER_TOAST_COUNT + 1);
Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false);
+
+ mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
}
private State createShellState() {
@@ -292,6 +308,35 @@
AudioManager.RINGER_MODE_NORMAL, false);
}
+ /**
+ * Ideally we would look at the ringer ImageView and check its assigned drawable id, but that
+ * API does not exist. So we do the next best thing; we check the cached icon id.
+ */
+ @Test
+ public void notificationVolumeSeparated_theRingerIconChanges() {
+ mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "true", false);
+
+ mExecutor.runAllReady(); // for the config change to take effect
+
+ // assert icon is new based on res id
+ assertEquals(mDialog.mVolumeRingerIconDrawableId,
+ R.drawable.ic_speaker_on);
+ assertEquals(mDialog.mVolumeRingerMuteIconDrawableId,
+ R.drawable.ic_speaker_mute);
+ }
+
+ @Test
+ public void notificationVolumeNotSeparated_theRingerIconRemainsTheSame() {
+ mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
+
+ mExecutor.runAllReady();
+
+ assertEquals(mDialog.mVolumeRingerIconDrawableId, R.drawable.ic_volume_ringer);
+ assertEquals(mDialog.mVolumeRingerMuteIconDrawableId, R.drawable.ic_volume_ringer_mute);
+ }
+
/*
@Test
public void testContentDescriptions() {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 088eddb..a61a61b 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -162,6 +162,7 @@
"android.hardware.biometrics.face-V1.0-java",
"android.hardware.biometrics.fingerprint-V2.3-java",
"android.hardware.oemlock-V1.0-java",
+ "android.hardware.oemlock-V1-java",
"android.hardware.configstore-V1.1-java",
"android.hardware.ir-V1-java",
"android.hardware.rebootescrow-V1-java",
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 3cf53ef..3231240 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3622,6 +3622,18 @@
}
}
+ // TODO enforce MODIFY_AUDIO_SYSTEM_SETTINGS when defined
+ private void enforceModifyAudioRoutingOrSystemSettingsPermission() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ != PackageManager.PERMISSION_GRANTED
+ /*&& mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+ != PackageManager.PERMISSION_DENIED*/) {
+ throw new SecurityException(
+ "Missing MODIFY_AUDIO_ROUTING or MODIFY_AUDIO_SYSTEM_SETTINGS permission");
+ }
+ }
+
private void enforceAccessUltrasoundPermission() {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.ACCESS_ULTRASOUND)
!= PackageManager.PERMISSION_GRANTED) {
@@ -3734,20 +3746,35 @@
}
/** @see AudioDeviceVolumeManager#setDeviceVolume(VolumeInfo, AudioDeviceAttributes)
- * Part of service interface, check permissions and parameters here */
+ * Part of service interface, check permissions and parameters here
+ * Note calling package is for logging purposes only, not to be trusted
+ */
public void setDeviceVolume(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada,
- @NonNull String callingPackage, @Nullable String attributionTag) {
- enforceModifyAudioRoutingPermission();
+ @NonNull String callingPackage) {
+ enforceModifyAudioRoutingOrSystemSettingsPermission();
Objects.requireNonNull(vi);
Objects.requireNonNull(ada);
Objects.requireNonNull(callingPackage);
+
if (!vi.hasStreamType()) {
Log.e(TAG, "Unsupported non-stream type based VolumeInfo", new Exception());
return;
}
int index = vi.getVolumeIndex();
- if (index == VolumeInfo.INDEX_NOT_SET) {
- throw new IllegalArgumentException("changing device volume requires a volume index");
+ if (index == VolumeInfo.INDEX_NOT_SET && !vi.hasMuteCommand()) {
+ throw new IllegalArgumentException(
+ "changing device volume requires a volume index or mute command");
+ }
+
+ // TODO handle unmuting if current audio device
+ // if a stream is not muted but the VolumeInfo is for muting, set the volume index
+ // for the device to min volume
+ if (vi.hasMuteCommand() && vi.isMuted() && !isStreamMute(vi.getStreamType())) {
+ setStreamVolumeWithAttributionInt(vi.getStreamType(),
+ mStreamStates[vi.getStreamType()].getMinIndex(),
+ /*flags*/ 0,
+ ada, callingPackage, null);
+ return;
}
AudioService.sVolumeLogger.enqueueAndLog("setDeviceVolume" + " from:" + callingPackage
@@ -3772,7 +3799,7 @@
}
}
setStreamVolumeWithAttributionInt(vi.getStreamType(), index, /*flags*/ 0,
- ada, callingPackage, attributionTag);
+ ada, callingPackage, null);
}
/** Retain API for unsupported app usage */
@@ -4682,6 +4709,36 @@
}
}
+ /**
+ * @see AudioDeviceVolumeManager#getDeviceVolume(VolumeInfo, AudioDeviceAttributes)
+ */
+ public @NonNull VolumeInfo getDeviceVolume(@NonNull VolumeInfo vi,
+ @NonNull AudioDeviceAttributes ada, @NonNull String callingPackage) {
+ enforceModifyAudioRoutingOrSystemSettingsPermission();
+ Objects.requireNonNull(vi);
+ Objects.requireNonNull(ada);
+ Objects.requireNonNull(callingPackage);
+ if (!vi.hasStreamType()) {
+ Log.e(TAG, "Unsupported non-stream type based VolumeInfo", new Exception());
+ return getDefaultVolumeInfo();
+ }
+
+ int streamType = vi.getStreamType();
+ final VolumeInfo.Builder vib = new VolumeInfo.Builder(vi);
+ vib.setMinVolumeIndex(mStreamStates[streamType].mIndexMin);
+ vib.setMaxVolumeIndex(mStreamStates[streamType].mIndexMax);
+ synchronized (VolumeStreamState.class) {
+ final int index;
+ if (isFixedVolumeDevice(ada.getInternalType())) {
+ index = (mStreamStates[streamType].mIndexMax + 5) / 10;
+ } else {
+ index = (mStreamStates[streamType].getIndex(ada.getInternalType()) + 5) / 10;
+ }
+ vib.setVolumeIndex(index);
+ return vib.setMuted(mStreamStates[streamType].mIsMuted).build();
+ }
+ }
+
/** @see AudioManager#getStreamMaxVolume(int) */
public int getStreamMaxVolume(int streamType) {
ensureValidStreamType(streamType);
@@ -4722,7 +4779,6 @@
sDefaultVolumeInfo = new VolumeInfo.Builder(AudioSystem.STREAM_MUSIC)
.setMinVolumeIndex(getStreamMinVolume(AudioSystem.STREAM_MUSIC))
.setMaxVolumeIndex(getStreamMaxVolume(AudioSystem.STREAM_MUSIC))
- .setMuted(false)
.build();
}
return sDefaultVolumeInfo;
@@ -7965,6 +8021,23 @@
}
}
+ public @NonNull VolumeInfo getVolumeInfo(int device) {
+ synchronized (VolumeStreamState.class) {
+ int index = mIndexMap.get(device, -1);
+ if (index == -1) {
+ // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT
+ index = mIndexMap.get(AudioSystem.DEVICE_OUT_DEFAULT);
+ }
+ final VolumeInfo vi = new VolumeInfo.Builder(mStreamType)
+ .setMinVolumeIndex(mIndexMin)
+ .setMaxVolumeIndex(mIndexMax)
+ .setVolumeIndex(index)
+ .setMuted(isFullyMuted())
+ .build();
+ return vi;
+ }
+ }
+
public boolean hasIndexForDevice(int device) {
synchronized (VolumeStreamState.class) {
return (mIndexMap.get(device, -1) != -1);
diff --git a/services/core/java/com/android/server/oemlock/OemLockService.java b/services/core/java/com/android/server/oemlock/OemLockService.java
index bac8916..4c6110b 100644
--- a/services/core/java/com/android/server/oemlock/OemLockService.java
+++ b/services/core/java/com/android/server/oemlock/OemLockService.java
@@ -25,7 +25,6 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
-import android.hardware.oemlock.V1_0.IOemLock;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -58,15 +57,18 @@
private OemLock mOemLock;
public static boolean isHalPresent() {
- return VendorLock.getOemLockHalService() != null;
+ return (VendorLockHidl.getOemLockHalService() != null)
+ || (VendorLockAidl.getOemLockHalService() != null);
}
/** Select the OEM lock implementation */
private static OemLock getOemLock(Context context) {
- final IOemLock oemLockHal = VendorLock.getOemLockHalService();
- if (oemLockHal != null) {
- Slog.i(TAG, "Using vendor lock via the HAL");
- return new VendorLock(context, oemLockHal);
+ if (VendorLockAidl.getOemLockHalService() != null) {
+ Slog.i(TAG, "Using vendor lock via the HAL(aidl)");
+ return new VendorLockAidl(context);
+ } else if (VendorLockHidl.getOemLockHalService() != null) {
+ Slog.i(TAG, "Using vendor lock via the HAL(hidl)");
+ return new VendorLockHidl(context);
} else {
Slog.i(TAG, "Using persistent data block based lock");
return new PersistentDataBlockLock(context);
diff --git a/services/core/java/com/android/server/oemlock/VendorLockAidl.java b/services/core/java/com/android/server/oemlock/VendorLockAidl.java
new file mode 100644
index 0000000..82d45ab
--- /dev/null
+++ b/services/core/java/com/android/server/oemlock/VendorLockAidl.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.oemlock;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.oemlock.IOemLock;
+import android.hardware.oemlock.OemLockSecureStatus;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Slog;
+
+/** Uses the OEM lock HAL. */
+class VendorLockAidl extends OemLock {
+ private static final String TAG = "OemLock";
+ private IOemLock mOemLock;
+
+ static IOemLock getOemLockHalService() {
+ return IOemLock.Stub.asInterface(
+ ServiceManager.waitForDeclaredService(IOemLock.DESCRIPTOR + "/default"));
+ }
+
+ VendorLockAidl(Context context) {
+ mOemLock = getOemLockHalService();
+ }
+
+ @Override
+ @Nullable
+ String getLockName() {
+ try {
+ return mOemLock.getName();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get name from HAL", e);
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ void setOemUnlockAllowedByCarrier(boolean allowed, @Nullable byte[] signature) {
+ try {
+ final int status;
+ if (signature == null) {
+ status = mOemLock.setOemUnlockAllowedByCarrier(allowed, new byte[0]);
+ } else {
+ status = mOemLock.setOemUnlockAllowedByCarrier(allowed, signature);
+ }
+ switch (status) {
+ case OemLockSecureStatus.OK:
+ Slog.i(TAG, "Updated carrier allows OEM lock state to: " + allowed);
+ return;
+
+ case OemLockSecureStatus.INVALID_SIGNATURE:
+ if (signature == null) {
+ throw new IllegalArgumentException("Signature required for carrier unlock");
+ }
+ throw new SecurityException(
+ "Invalid signature used in attempt to carrier unlock");
+
+ default:
+ Slog.e(TAG, "Unknown return value indicates code is out of sync with HAL");
+ // Fallthrough
+ case OemLockSecureStatus.FAILED:
+ throw new RuntimeException("Failed to set carrier OEM unlock state");
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set carrier state with HAL", e);
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ boolean isOemUnlockAllowedByCarrier() {
+ try {
+ return mOemLock.isOemUnlockAllowedByCarrier();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get carrier state from HAL");
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ void setOemUnlockAllowedByDevice(boolean allowedByDevice) {
+ try {
+ mOemLock.setOemUnlockAllowedByDevice(allowedByDevice);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set device state with HAL", e);
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ boolean isOemUnlockAllowedByDevice() {
+
+ try {
+ return mOemLock.isOemUnlockAllowedByDevice();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get devie state from HAL");
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/oemlock/VendorLock.java b/services/core/java/com/android/server/oemlock/VendorLockHidl.java
similarity index 87%
rename from services/core/java/com/android/server/oemlock/VendorLock.java
rename to services/core/java/com/android/server/oemlock/VendorLockHidl.java
index 9c876da..fe76787 100644
--- a/services/core/java/com/android/server/oemlock/VendorLock.java
+++ b/services/core/java/com/android/server/oemlock/VendorLockHidl.java
@@ -27,10 +27,8 @@
import java.util.ArrayList;
import java.util.NoSuchElementException;
-/**
- * Uses the OEM lock HAL.
- */
-class VendorLock extends OemLock {
+/** Uses the OEM lock HAL. */
+class VendorLockHidl extends OemLock {
private static final String TAG = "OemLock";
private Context mContext;
@@ -40,29 +38,30 @@
try {
return IOemLock.getService(/* retry */ true);
} catch (NoSuchElementException e) {
- Slog.i(TAG, "OemLock HAL not present on device");
+ Slog.i(TAG, "OemLock Hidl HAL not present on device");
return null;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
- VendorLock(Context context, IOemLock oemLock) {
+ VendorLockHidl(Context context) {
mContext = context;
- mOemLock = oemLock;
+ mOemLock = getOemLockHalService();
}
@Override
@Nullable
String getLockName() {
- final Integer[] requestStatus = new Integer[1];
final String[] lockName = new String[1];
+ final Integer[] requestStatus = new Integer[1];
try {
- mOemLock.getName((status, name) -> {
- requestStatus[0] = status;
- lockName[0] = name;
- });
+ mOemLock.getName(
+ (status, name) -> {
+ requestStatus[0] = status;
+ lockName[0] = name;
+ });
} catch (RemoteException e) {
Slog.e(TAG, "Failed to get name from HAL", e);
throw e.rethrowFromSystemServer();
@@ -113,14 +112,14 @@
@Override
boolean isOemUnlockAllowedByCarrier() {
- final Integer[] requestStatus = new Integer[1];
final Boolean[] allowedByCarrier = new Boolean[1];
-
+ final Integer[] requestStatus = new Integer[1];
try {
- mOemLock.isOemUnlockAllowedByCarrier((status, allowed) -> {
- requestStatus[0] = status;
- allowedByCarrier[0] = allowed;
- });
+ mOemLock.isOemUnlockAllowedByCarrier(
+ (status, allowed) -> {
+ requestStatus[0] = status;
+ allowedByCarrier[0] = allowed;
+ });
} catch (RemoteException e) {
Slog.e(TAG, "Failed to get carrier state from HAL");
throw e.rethrowFromSystemServer();
@@ -161,14 +160,15 @@
@Override
boolean isOemUnlockAllowedByDevice() {
- final Integer[] requestStatus = new Integer[1];
final Boolean[] allowedByDevice = new Boolean[1];
+ final Integer[] requestStatus = new Integer[1];
try {
- mOemLock.isOemUnlockAllowedByDevice((status, allowed) -> {
- requestStatus[0] = status;
- allowedByDevice[0] = allowed;
- });
+ mOemLock.isOemUnlockAllowedByDevice(
+ (status, allowed) -> {
+ requestStatus[0] = status;
+ allowedByDevice[0] = allowed;
+ });
} catch (RemoteException e) {
Slog.e(TAG, "Failed to get devie state from HAL");
throw e.rethrowFromSystemServer();
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 5dd5d81..b02d1a8 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1816,8 +1816,6 @@
// Collect files we care for fs-verity setup.
ArrayMap<String, String> fsverityCandidates = new ArrayMap<>();
- // NB: These files will become only accessible if the signing key is loaded in kernel's
- // .fs-verity keyring.
fsverityCandidates.put(pkg.getBaseApkPath(),
VerityUtils.getFsveritySignatureFilePath(pkg.getBaseApkPath()));
@@ -1855,20 +1853,6 @@
throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
"fs-verity signature does not verify against a known key");
}
- } else {
- // Without signature, we don't need to access the digest right away and can
- // enable fs-verity in background (since this is a blocking call).
- new Thread("fsverity-setup") {
- @Override public void run() {
- try {
- VerityUtils.setUpFsverity(filePath, (byte[]) null);
- } catch (IOException e) {
- // There's nothing we can do if the setup failed. Since fs-verity is
- // optional, just ignore the error for now.
- Slog.e(TAG, "Failed to enable fs-verity to " + filePath);
- }
- }
- }.start();
}
} catch (IOException e) {
throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
@@ -2243,6 +2227,22 @@
}
incrementalStorages.add(storage);
}
+
+ try {
+ if (!VerityUtils.hasFsverity(pkg.getBaseApkPath())) {
+ VerityUtils.setUpFsverity(pkg.getBaseApkPath(), (byte[]) null);
+ }
+ for (String path : pkg.getSplitCodePaths()) {
+ if (!VerityUtils.hasFsverity(path)) {
+ VerityUtils.setUpFsverity(path, (byte[]) null);
+ }
+ }
+ } catch (IOException e) {
+ // There's nothing we can do if the setup failed. Since fs-verity is
+ // optional, just ignore the error for now.
+ Slog.e(TAG, "Failed to fully enable fs-verity to " + packageName);
+ }
+
// Hardcode previousAppId to 0 to disable any data migration (http://b/221088088)
mAppDataHelper.prepareAppDataPostCommitLIF(pkg, 0);
if (installRequest.isClearCodeCache()) {
diff --git a/services/core/java/com/android/server/pm/KnownPackages.java b/services/core/java/com/android/server/pm/KnownPackages.java
index dcf7152..154709a 100644
--- a/services/core/java/com/android/server/pm/KnownPackages.java
+++ b/services/core/java/com/android/server/pm/KnownPackages.java
@@ -47,6 +47,7 @@
PACKAGE_RETAIL_DEMO,
PACKAGE_RECENTS,
PACKAGE_AMBIENT_CONTEXT_DETECTION,
+ PACKAGE_WEARABLE_SENSING,
})
@Retention(RetentionPolicy.SOURCE)
public @interface KnownPackage {
@@ -71,9 +72,10 @@
public static final int PACKAGE_RETAIL_DEMO = 16;
public static final int PACKAGE_RECENTS = 17;
public static final int PACKAGE_AMBIENT_CONTEXT_DETECTION = 18;
+ public static final int PACKAGE_WEARABLE_SENSING = 19;
// Integer value of the last known package ID. Increases as new ID is added to KnownPackage.
// Please note the numbers should be continuous.
- public static final int LAST_KNOWN_PACKAGE = PACKAGE_AMBIENT_CONTEXT_DETECTION;
+ public static final int LAST_KNOWN_PACKAGE = PACKAGE_WEARABLE_SENSING;
private final DefaultAppProvider mDefaultAppProvider;
private final String mRequiredInstallerPackage;
@@ -86,6 +88,7 @@
private final String mConfiguratorPackage;
private final String mIncidentReportApproverPackage;
private final String mAmbientContextDetectionPackage;
+ private final String mWearableSensingPackage;
private final String mAppPredictionServicePackage;
private final String mCompanionPackage;
private final String mRetailDemoPackage;
@@ -97,9 +100,9 @@
String[] requiredVerifierPackages, String defaultTextClassifierPackage,
String systemTextClassifierPackageName, String requiredPermissionControllerPackage,
String configuratorPackage, String incidentReportApproverPackage,
- String ambientContextDetectionPackage, String appPredictionServicePackage,
- String companionPackageName, String retailDemoPackage,
- String overlayConfigSignaturePackage, String recentsPackage) {
+ String ambientContextDetectionPackage, String wearableSensingPackage,
+ String appPredictionServicePackage, String companionPackageName,
+ String retailDemoPackage, String overlayConfigSignaturePackage, String recentsPackage) {
mDefaultAppProvider = defaultAppProvider;
mRequiredInstallerPackage = requiredInstallerPackage;
mRequiredUninstallerPackage = requiredUninstallerPackage;
@@ -111,6 +114,7 @@
mConfiguratorPackage = configuratorPackage;
mIncidentReportApproverPackage = incidentReportApproverPackage;
mAmbientContextDetectionPackage = ambientContextDetectionPackage;
+ mWearableSensingPackage = wearableSensingPackage;
mAppPredictionServicePackage = appPredictionServicePackage;
mCompanionPackage = companionPackageName;
mRetailDemoPackage = retailDemoPackage;
@@ -165,6 +169,8 @@
return "Recents";
case PACKAGE_AMBIENT_CONTEXT_DETECTION:
return "Ambient Context Detection";
+ case PACKAGE_WEARABLE_SENSING:
+ return "Wearable sensing";
}
return "Unknown";
}
@@ -194,6 +200,8 @@
return snapshot.filterOnlySystemPackages(mIncidentReportApproverPackage);
case PACKAGE_AMBIENT_CONTEXT_DETECTION:
return snapshot.filterOnlySystemPackages(mAmbientContextDetectionPackage);
+ case PACKAGE_WEARABLE_SENSING:
+ return snapshot.filterOnlySystemPackages(mWearableSensingPackage);
case PACKAGE_APP_PREDICTOR:
return snapshot.filterOnlySystemPackages(mAppPredictionServicePackage);
case PACKAGE_COMPANION:
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 50eb926..8f8cc8a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -940,6 +940,7 @@
final @Nullable String mOverlayConfigSignaturePackage;
final @Nullable String mRecentsPackage;
final @Nullable String mAmbientContextDetectionPackage;
+ final @Nullable String mWearableSensingPackage;
private final @NonNull String mRequiredSdkSandboxPackage;
@GuardedBy("mLock")
@@ -1713,6 +1714,7 @@
mRetailDemoPackage = testParams.retailDemoPackage;
mRecentsPackage = testParams.recentsPackage;
mAmbientContextDetectionPackage = testParams.ambientContextDetectionPackage;
+ mWearableSensingPackage = testParams.wearableSensingPackage;
mConfiguratorPackage = testParams.configuratorPackage;
mAppPredictionServicePackage = testParams.appPredictionServicePackage;
mIncidentReportApproverPackage = testParams.incidentReportApproverPackage;
@@ -2072,6 +2074,9 @@
mAmbientContextDetectionPackage = ensureSystemPackageName(computer,
getPackageFromComponentString(
R.string.config_defaultAmbientContextDetectionService));
+ mWearableSensingPackage = ensureSystemPackageName(computer,
+ getPackageFromComponentString(
+ R.string.config_defaultWearableSensingService));
// Now that we know all of the shared libraries, update all clients to have
// the correct library paths.
@@ -6067,6 +6072,7 @@
mConfiguratorPackage,
mIncidentReportApproverPackage,
mAmbientContextDetectionPackage,
+ mWearableSensingPackage,
mAppPredictionServicePackage,
COMPANION_PACKAGE_NAME,
mRetailDemoPackage,
@@ -7105,6 +7111,7 @@
mConfiguratorPackage,
mIncidentReportApproverPackage,
mAmbientContextDetectionPackage,
+ mWearableSensingPackage,
mAppPredictionServicePackage,
COMPANION_PACKAGE_NAME,
mRetailDemoPackage,
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 4391fdd..bffbb84 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -94,6 +94,7 @@
public @Nullable String retailDemoPackage;
public @Nullable String recentsPackage;
public @Nullable String ambientContextDetectionPackage;
+ public @Nullable String wearableSensingPackage;
public ComponentName resolveComponentName;
public ArrayMap<String, AndroidPackage> packages;
public boolean enableFreeCacheV2;
diff --git a/services/core/java/com/android/server/wearable/OWNERS b/services/core/java/com/android/server/wearable/OWNERS
new file mode 100644
index 0000000..073e2d7
--- /dev/null
+++ b/services/core/java/com/android/server/wearable/OWNERS
@@ -0,0 +1,3 @@
+charliewang@google.com
+oni@google.com
+volnov@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
new file mode 100644
index 0000000..b2bbcda
--- /dev/null
+++ b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wearable;
+
+import static android.content.Context.BIND_FOREGROUND_SERVICE;
+import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.SharedMemory;
+import android.service.wearable.IWearableSensingService;
+import android.service.wearable.WearableSensingService;
+import android.util.Slog;
+
+import com.android.internal.infra.ServiceConnector;
+
+/** Manages the connection to the remote wearable sensing service. */
+final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearableSensingService> {
+ private static final String TAG =
+ com.android.server.wearable.RemoteWearableSensingService.class.getSimpleName();
+ private final static boolean DEBUG = false;
+
+ RemoteWearableSensingService(Context context, ComponentName serviceName,
+ int userId) {
+ super(context, new Intent(
+ WearableSensingService.SERVICE_INTERFACE).setComponent(serviceName),
+ BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES, userId,
+ IWearableSensingService.Stub::asInterface);
+
+ // Bind right away
+ connect();
+ }
+
+ @Override
+ protected long getAutoDisconnectTimeoutMs() {
+ // Disable automatic unbinding.
+ return -1;
+ }
+
+ /**
+ * Provides the implementation a data stream to the wearable.
+ *
+ * @param parcelFileDescriptor The data stream to the wearable
+ * @param callback The callback for service status
+ */
+ public void provideDataStream(ParcelFileDescriptor parcelFileDescriptor,
+ RemoteCallback callback) {
+ if (DEBUG) {
+ Slog.i(TAG, "Providing data stream.");
+ }
+ post(service -> service.provideDataStream(parcelFileDescriptor, callback));
+ }
+
+ /**
+ * Provides the implementation data.
+ *
+ * @param data Application configuration data to provide to the implementation.
+ * @param sharedMemory The unrestricted data blob to provide to the implementation.
+ * @param callback The callback for service status
+ */
+ public void provideData(PersistableBundle data,
+ SharedMemory sharedMemory,
+ RemoteCallback callback) {
+ if (DEBUG) {
+ Slog.i(TAG, "Providing data.");
+ }
+ post(service -> service.provideData(data, sharedMemory, callback));
+ }
+}
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
new file mode 100644
index 0000000..e73fd0f
--- /dev/null
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wearable;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AppGlobals;
+import android.app.ambientcontext.AmbientContextEvent;
+import android.app.wearable.WearableSensingManager;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.Bundle;
+import android.system.OsConstants;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.SharedMemory;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.infra.AbstractPerUserSystemService;
+
+import java.io.PrintWriter;
+
+/**
+ * Per-user manager service for managing sensing {@link AmbientContextEvent}s on Wearables.
+ */
+final class WearableSensingManagerPerUserService extends
+ AbstractPerUserSystemService<WearableSensingManagerPerUserService,
+ WearableSensingManagerService> {
+ private static final String TAG = WearableSensingManagerPerUserService.class.getSimpleName();
+
+ @Nullable
+ @VisibleForTesting
+ RemoteWearableSensingService mRemoteService;
+
+ private ComponentName mComponentName;
+
+ WearableSensingManagerPerUserService(
+ @NonNull WearableSensingManagerService master, Object lock, @UserIdInt int userId) {
+ super(master, lock, userId);
+ }
+
+ static void notifyStatusCallback(RemoteCallback statusCallback, int statusCode) {
+ Bundle bundle = new Bundle();
+ bundle.putInt(
+ WearableSensingManager.STATUS_RESPONSE_BUNDLE_KEY, statusCode);
+ statusCallback.sendResult(bundle);
+ }
+
+ void destroyLocked() {
+ Slog.d(TAG, "Trying to cancel the remote request. Reason: Service destroyed.");
+ if (mRemoteService != null) {
+ synchronized (mLock) {
+ mRemoteService.unbind();
+ mRemoteService = null;
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void ensureRemoteServiceInitiated() {
+ if (mRemoteService == null) {
+ mRemoteService = new RemoteWearableSensingService(
+ getContext(), mComponentName, getUserId());
+ }
+ }
+
+ /**
+ * get the currently bound component name.
+ */
+ @VisibleForTesting
+ ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+
+ /**
+ * Resolves and sets up the service if it had not been done yet. Returns true if the service
+ * is available.
+ */
+ @GuardedBy("mLock")
+ @VisibleForTesting
+ boolean setUpServiceIfNeeded() {
+ if (mComponentName == null) {
+ mComponentName = updateServiceInfoLocked();
+ }
+ if (mComponentName == null) {
+ return false;
+ }
+
+ ServiceInfo serviceInfo;
+ try {
+ serviceInfo = AppGlobals.getPackageManager().getServiceInfo(
+ mComponentName, 0, mUserId);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException while setting up service");
+ return false;
+ }
+ return serviceInfo != null;
+ }
+
+ @Override
+ protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
+ throws PackageManager.NameNotFoundException {
+ ServiceInfo serviceInfo;
+ try {
+ serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+ 0, mUserId);
+ if (serviceInfo != null) {
+ final String permission = serviceInfo.permission;
+ if (!Manifest.permission.BIND_WEARABLE_SENSING_SERVICE.equals(
+ permission)) {
+ throw new SecurityException(String.format(
+ "Service %s requires %s permission. Found %s permission",
+ serviceInfo.getComponentName(),
+ Manifest.permission.BIND_WEARABLE_SENSING_SERVICE,
+ serviceInfo.permission));
+ }
+ }
+ } catch (RemoteException e) {
+ throw new PackageManager.NameNotFoundException(
+ "Could not get service for " + serviceComponent);
+ }
+ return serviceInfo;
+ }
+
+ @Override
+ protected void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) {
+ synchronized (super.mLock) {
+ super.dumpLocked(prefix, pw);
+ }
+ if (mRemoteService != null) {
+ mRemoteService.dump("", new IndentingPrintWriter(pw, " "));
+ }
+ }
+
+ /**
+ * Handles sending the provided data stream for the wearable to the wearable sensing service.
+ */
+ public void onProvideDataStream(
+ ParcelFileDescriptor parcelFileDescriptor,
+ RemoteCallback callback) {
+ Slog.i(TAG, "onProvideDataStream in per user service.");
+ synchronized (mLock) {
+ if (!setUpServiceIfNeeded()) {
+ Slog.w(TAG, "Detection service is not available at this moment.");
+ notifyStatusCallback(callback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+ return;
+ }
+ Slog.i(TAG, "calling over to remote servvice.");
+ ensureRemoteServiceInitiated();
+ mRemoteService.provideDataStream(parcelFileDescriptor, callback);
+ }
+ }
+
+ /**
+ * Handles sending the provided data to the wearable sensing service.
+ */
+ public void onProvidedData(PersistableBundle data,
+ SharedMemory sharedMemory,
+ RemoteCallback callback) {
+ synchronized (mLock) {
+ if (!setUpServiceIfNeeded()) {
+ Slog.w(TAG, "Detection service is not available at this moment.");
+ notifyStatusCallback(callback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+ return;
+ }
+ ensureRemoteServiceInitiated();
+ if (sharedMemory != null) {
+ sharedMemory.setProtect(OsConstants.PROT_READ);
+ }
+ mRemoteService.provideData(data, sharedMemory, callback);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
new file mode 100644
index 0000000..e155a06
--- /dev/null
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wearable;
+
+import static android.provider.DeviceConfig.NAMESPACE_WEARABLE_SENSING;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.ambientcontext.AmbientContextEvent;
+import android.app.wearable.IWearableSensingManager;
+import android.app.wearable.WearableSensingManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.ResultReceiver;
+import android.os.SharedMemory;
+import android.os.ShellCallback;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.infra.AbstractMasterSystemService;
+import com.android.server.infra.FrameworkResourcesServiceNameResolver;
+import com.android.server.pm.KnownPackages;
+
+import java.io.FileDescriptor;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * System service for managing sensing {@link AmbientContextEvent}s on Wearables.
+ */
+public class WearableSensingManagerService extends
+ AbstractMasterSystemService<WearableSensingManagerService,
+ WearableSensingManagerPerUserService> {
+ private static final String TAG = WearableSensingManagerService.class.getSimpleName();
+ private static final String KEY_SERVICE_ENABLED = "service_enabled";
+
+ /** Default value in absence of {@link DeviceConfig} override. */
+ private static final boolean DEFAULT_SERVICE_ENABLED = true;
+ public static final int MAX_TEMPORARY_SERVICE_DURATION_MS = 30000;
+
+ private final Context mContext;
+ volatile boolean mIsServiceEnabled;
+
+ public WearableSensingManagerService(Context context) {
+ super(context,
+ new FrameworkResourcesServiceNameResolver(
+ context,
+ R.string.config_defaultWearableSensingService),
+ /*disallowProperty=*/null,
+ PACKAGE_UPDATE_POLICY_REFRESH_EAGER
+ | /*To avoid high latency*/ PACKAGE_RESTART_POLICY_REFRESH_EAGER);
+ mContext = context;
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(
+ Context.WEARABLE_SENSING_SERVICE, new WearableSensingManagerInternal());
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+ DeviceConfig.addOnPropertiesChangedListener(
+ NAMESPACE_WEARABLE_SENSING,
+ getContext().getMainExecutor(),
+ (properties) -> onDeviceConfigChange(properties.getKeyset()));
+
+ mIsServiceEnabled = DeviceConfig.getBoolean(
+ NAMESPACE_WEARABLE_SENSING,
+ KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
+ }
+ }
+
+
+ private void onDeviceConfigChange(@NonNull Set<String> keys) {
+ if (keys.contains(KEY_SERVICE_ENABLED)) {
+ mIsServiceEnabled = DeviceConfig.getBoolean(
+ NAMESPACE_WEARABLE_SENSING,
+ KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
+ }
+ }
+
+ @Override
+ protected WearableSensingManagerPerUserService newServiceLocked(int resolvedUserId,
+ boolean disabled) {
+ return new WearableSensingManagerPerUserService(this, mLock, resolvedUserId);
+ }
+
+ @Override
+ protected void onServiceRemoved(
+ WearableSensingManagerPerUserService service, @UserIdInt int userId) {
+ Slog.d(TAG, "onServiceRemoved");
+ service.destroyLocked();
+ }
+
+ @Override
+ protected void onServicePackageRestartedLocked(@UserIdInt int userId) {
+ Slog.d(TAG, "onServicePackageRestartedLocked.");
+ }
+
+ @Override
+ protected void onServicePackageUpdatedLocked(@UserIdInt int userId) {
+ Slog.d(TAG, "onServicePackageUpdatedLocked.");
+ }
+
+ @Override
+ protected void enforceCallingPermissionForManagement() {
+ getContext().enforceCallingPermission(
+ Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
+ }
+
+ @Override
+ protected int getMaximumTemporaryServiceDurationMs() {
+ return MAX_TEMPORARY_SERVICE_DURATION_MS;
+ }
+
+ /** Returns {@code true} if the detection service is configured on this device. */
+ public static boolean isDetectionServiceConfigured() {
+ final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+ final String[] packageNames = pmi.getKnownPackageNames(
+ KnownPackages.PACKAGE_WEARABLE_SENSING, UserHandle.USER_SYSTEM);
+ boolean isServiceConfigured = (packageNames.length != 0);
+ Slog.i(TAG, "Wearable sensing service configured: " + isServiceConfigured);
+ return isServiceConfigured;
+ }
+
+ /**
+ * Returns the AmbientContextManagerPerUserService component for this user.
+ */
+ public ComponentName getComponentName(@UserIdInt int userId) {
+ synchronized (mLock) {
+ final WearableSensingManagerPerUserService service = getServiceForUserLocked(userId);
+ if (service != null) {
+ return service.getComponentName();
+ }
+ }
+ return null;
+ }
+
+ private final class WearableSensingManagerInternal extends IWearableSensingManager.Stub {
+ final WearableSensingManagerPerUserService mService = getServiceForUserLocked(
+ UserHandle.getCallingUserId());
+
+ @Override
+ public void provideDataStream(
+ ParcelFileDescriptor parcelFileDescriptor,
+ RemoteCallback callback) {
+ Slog.i(TAG, "WearableSensingManagerInternal provideDataStream.");
+ Objects.requireNonNull(parcelFileDescriptor);
+ Objects.requireNonNull(callback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available.");
+ WearableSensingManagerPerUserService.notifyStatusCallback(callback,
+ WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+ return;
+ }
+ mService.onProvideDataStream(parcelFileDescriptor, callback);
+ }
+
+ @Override
+ public void provideData(
+ PersistableBundle data,
+ SharedMemory sharedMemory,
+ RemoteCallback callback) {
+ Slog.i(TAG, "WearableSensingManagerInternal provideData.");
+ Objects.requireNonNull(data);
+ Objects.requireNonNull(callback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available.");
+ WearableSensingManagerPerUserService.notifyStatusCallback(callback,
+ WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+ return;
+ }
+ mService.onProvidedData(data, sharedMemory, callback);
+ }
+
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 8dc1f0b..4c35178 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -23,6 +23,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_FINISH_ACTIVITY;
@@ -850,6 +851,10 @@
effects |= setAdjacentRootsHierarchyOp(hop);
break;
}
+ case HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS: {
+ effects |= clearAdjacentRootsHierarchyOp(hop);
+ break;
+ }
case HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT: {
final TaskFragmentCreationParams taskFragmentCreationOptions =
hop.getTaskFragmentCreationOptions();
@@ -1486,6 +1491,17 @@
return TRANSACT_EFFECTS_LIFECYCLE;
}
+ private int clearAdjacentRootsHierarchyOp(WindowContainerTransaction.HierarchyOp hop) {
+ final TaskFragment root = WindowContainer.fromBinder(hop.getContainer()).asTaskFragment();
+ if (!root.mCreatedByOrganizer) {
+ throw new IllegalArgumentException("clearAdjacentRootsHierarchyOp: Not created by"
+ + " organizer root=" + root);
+ }
+
+ root.resetAdjacentTaskFragment();
+ return TRANSACT_EFFECTS_LIFECYCLE;
+ }
+
private void sanitizeWindowContainer(WindowContainer wc) {
if (!(wc instanceof TaskFragment) && !(wc instanceof DisplayArea)) {
throw new RuntimeException("Invalid token in task fragment or displayArea transaction");
@@ -1650,6 +1666,10 @@
WindowContainer.fromBinder(hop.getAdjacentRoot()),
organizer);
break;
+ case HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS:
+ enforceTaskFragmentOrganized(func,
+ WindowContainer.fromBinder(hop.getContainer()), organizer);
+ break;
case HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT:
// We are allowing organizer to create TaskFragment. We will check the
// ownerToken in #createTaskFragment, and trigger error callback if that is not
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/CrossProfileAppsServiceImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/CrossProfileAppsServiceImplTest.java
index d6d1574..129efc6 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/CrossProfileAppsServiceImplTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/CrossProfileAppsServiceImplTest.java
@@ -38,6 +38,7 @@
import android.content.pm.PackageManagerInternal;
import android.content.pm.PermissionInfo;
import android.content.pm.ResolveInfo;
+import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.UserHandle;
@@ -47,7 +48,6 @@
import android.platform.test.annotations.Presubmit;
import android.util.SparseArray;
-import com.android.activitycontext.ActivityContext;
import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
import com.android.internal.util.FunctionalUtils.ThrowingSupplier;
import com.android.server.LocalServices;
@@ -611,34 +611,23 @@
mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER);
Bundle options = ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle();
- IBinder result = ActivityContext.getWithContext(activity -> {
- try {
- IBinder targetTask = activity.getActivityToken();
- mCrossProfileAppsServiceImpl.startActivityAsUser(
- mIApplicationThread,
- PACKAGE_ONE,
- FEATURE_ID,
- ACTIVITY_COMPONENT,
- UserHandle.of(PRIMARY_USER).getIdentifier(),
- true,
- targetTask,
- options);
- return targetTask;
- } catch (Exception re) {
- return null;
- }
- });
- if (result == null) {
- throw new Exception();
- }
-
+ Binder targetTask = new Binder();
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_ONE,
+ FEATURE_ID,
+ ACTIVITY_COMPONENT,
+ UserHandle.of(PRIMARY_USER).getIdentifier(),
+ true,
+ targetTask,
+ options);
verify(mActivityTaskManagerInternal)
.startActivityAsUser(
nullable(IApplicationThread.class),
eq(PACKAGE_ONE),
eq(FEATURE_ID),
any(Intent.class),
- eq(result),
+ eq(targetTask),
anyInt(),
eq(options),
eq(PRIMARY_USER));
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
index 7acb6d6..7f54b63 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
@@ -84,12 +84,12 @@
final AudioDeviceAttributes usbDevice = new AudioDeviceAttributes(
/*native type*/ AudioSystem.DEVICE_OUT_USB_DEVICE, /*address*/ "bla");
- mAudioService.setDeviceVolume(volMin, usbDevice, mPackageName, TAG);
+ mAudioService.setDeviceVolume(volMin, usbDevice, mPackageName);
mTestLooper.dispatchAll();
verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
AudioManager.STREAM_MUSIC, minIndex, AudioSystem.DEVICE_OUT_USB_DEVICE);
- mAudioService.setDeviceVolume(volMid, usbDevice, mPackageName, TAG);
+ mAudioService.setDeviceVolume(volMid, usbDevice, mPackageName);
mTestLooper.dispatchAll();
verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
AudioManager.STREAM_MUSIC, midIndex, AudioSystem.DEVICE_OUT_USB_DEVICE);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 690c2aa..c73e237 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -736,11 +736,12 @@
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
assertEquals(dc.getDefaultTaskDisplayArea().mLaunchAdjacentFlagRootTask, task1);
- task1.setAdjacentTaskFragment(null);
- task2.setAdjacentTaskFragment(null);
wct = new WindowContainerTransaction();
+ wct.clearAdjacentRoots(info1.token);
wct.clearLaunchAdjacentFlagRoot(info1.token);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
+ assertEquals(task1.getAdjacentTaskFragment(), null);
+ assertEquals(task2.getAdjacentTaskFragment(), null);
assertEquals(dc.getDefaultTaskDisplayArea().mLaunchAdjacentFlagRootTask, null);
}