Merge "Modifies the Trust Agent API to support Active Unlock."
diff --git a/Android.bp b/Android.bp
index d64951c..070bb73 100644
--- a/Android.bp
+++ b/Android.bp
@@ -155,38 +155,6 @@
}
java_library_with_nonpublic_deps {
- name: "framework-updatable-stubs-module_libs_api",
- static_libs: [
- "android.net.ipsec.ike.stubs.module_lib",
- "framework-appsearch.stubs.module_lib",
- "framework-connectivity.stubs.module_lib",
- "framework-connectivity-tiramisu.stubs.module_lib",
- "framework-graphics.stubs.module_lib",
- "framework-media.stubs.module_lib",
- "framework-mediaprovider.stubs.module_lib",
- "framework-permission.stubs.module_lib",
- "framework-permission-s.stubs.module_lib",
- "framework-scheduling.stubs.module_lib",
- "framework-sdkextensions.stubs.module_lib",
- "framework-statsd.stubs.module_lib",
- "framework-supplementalprocess.stubs.module_lib",
- "framework-tethering.stubs.module_lib",
- "framework-uwb.stubs.module_lib",
- "framework-nearby.stubs.module_lib",
- "framework-wifi.stubs.module_lib",
- ],
- soong_config_variables: {
- include_nonpublic_framework_api: {
- static_libs: [
- "framework-supplementalapi.stubs.module_lib",
- ],
- },
- },
- sdk_version: "module_current",
- visibility: ["//visibility:private"],
-}
-
-java_library_with_nonpublic_deps {
name: "framework-all",
installable: false,
static_libs: [
@@ -212,7 +180,7 @@
soong_config_variables: {
include_nonpublic_framework_api: {
static_libs: [
- "framework-supplementalapi.stubs.module_lib",
+ "framework-supplementalapi.impl",
],
},
},
diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
index a1a46af..161a317 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
@@ -235,7 +235,6 @@
public static final int REASON_LOCKED_BOOT_COMPLETED = 202;
/**
* All Bluetooth broadcasts.
- * @hide
*/
public static final int REASON_BLUETOOTH_BROADCAST = 203;
/**
diff --git a/api/Android.bp b/api/Android.bp
index 7aa68f8..362f39f 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -32,6 +32,7 @@
"soong",
"soong-android",
"soong-genrule",
+ "soong-java",
],
srcs: ["api.go"],
pluginFor: ["soong_build"],
diff --git a/api/api.go b/api/api.go
index 6cc129a..3b0e300 100644
--- a/api/api.go
+++ b/api/api.go
@@ -21,8 +21,13 @@
"android/soong/android"
"android/soong/genrule"
+ "android/soong/java"
)
+const art = "art.module.public.api"
+const conscrypt = "conscrypt.module.public.api"
+const i18n = "i18n.module.public.api"
+
// The intention behind this soong plugin is to generate a number of "merged"
// API-related modules that would otherwise require a large amount of very
// similar Android.bp boilerplate to define. For example, the merged current.txt
@@ -69,6 +74,13 @@
Visibility []string
}
+type libraryProps struct {
+ Name *string
+ Sdk_version *string
+ Static_libs []string
+ Visibility []string
+}
+
// Struct to pass parameters for the various merged [current|removed].txt file modules we create.
type MergedTxtDefinition struct {
// "current.txt" or "removed.txt"
@@ -130,6 +142,8 @@
// This produces the same annotations.zip as framework-doc-stubs, but by using
// outputs from individual modules instead of all the source code.
func createMergedAnnotations(ctx android.LoadHookContext, modules []string) {
+ // Conscrypt and i18n currently do not enable annotations
+ modules = removeAll(modules, []string{conscrypt, i18n})
props := genruleProps{}
props.Name = proptools.StringPtr("sdk-annotations.zip")
props.Tools = []string{"merge_annotation_zips", "soong_zip"}
@@ -141,6 +155,15 @@
}
func createFilteredApiVersions(ctx android.LoadHookContext, modules []string) {
+ // For the filtered api versions, we prune all APIs except art module's APIs. because
+ // 1) ART apis are available by default to all modules, while other module-to-module deps are
+ // explicit and probably receive more scrutiny anyway
+ // 2) The number of ART/libcore APIs is large, so not linting them would create a large gap
+ // 3) It's a compromise. Ideally we wouldn't be filtering out any module APIs, and have
+ // per-module lint databases that excludes just that module's APIs. Alas, that's more
+ // difficult to achieve.
+ modules = remove(modules, art)
+
props := genruleProps{}
props.Name = proptools.StringPtr("api-versions-xml-public-filtered")
props.Tools = []string{"api_versions_trimmer"}
@@ -154,6 +177,25 @@
ctx.CreateModule(genrule.GenRuleFactory, &props)
}
+func createMergedModuleLibStubs(ctx android.LoadHookContext, modules []string) {
+ // The user of this module compiles against the "core" SDK, so remove core libraries to avoid dupes.
+ modules = removeAll(modules, []string{art, conscrypt, i18n})
+ props := libraryProps{}
+ props.Name = proptools.StringPtr("framework-updatable-stubs-module_libs_api")
+ props.Static_libs = appendStr(modules, ".stubs.module_lib")
+ props.Sdk_version = proptools.StringPtr("module_current")
+ props.Visibility = []string{"//frameworks/base"}
+ ctx.CreateModule(java.LibraryFactory, &props)
+}
+
+func appendStr(modules []string, s string) []string {
+ a := make([]string, 0, len(modules))
+ for _, module := range modules {
+ a = append(a, module+s)
+ }
+ return a
+}
+
func createSrcs(base string, modules []string, tag string) []string {
a := make([]string, 0, len(modules)+1)
a = append(a, base)
@@ -163,6 +205,13 @@
return a
}
+func removeAll(s []string, vs []string) []string {
+ for _, v := range vs {
+ s = remove(s, v)
+ }
+ return s
+}
+
func remove(s []string, v string) []string {
s2 := make([]string, 0, len(s))
for _, sv := range s {
@@ -176,9 +225,7 @@
func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string) {
var textFiles []MergedTxtDefinition
// Two module libraries currently do not support @SystemApi so only have the public scope.
- bcpWithSystemApi := bootclasspath
- bcpWithSystemApi = remove(bcpWithSystemApi, "conscrypt.module.public.api")
- bcpWithSystemApi = remove(bcpWithSystemApi, "i18n.module.public.api")
+ bcpWithSystemApi := removeAll(bootclasspath, []string{conscrypt, i18n})
tagSuffix := []string{".api.txt}", ".removed-api.txt}"}
for i, f := range []string{"current.txt", "removed.txt"} {
@@ -226,22 +273,11 @@
createMergedStubsSrcjar(ctx, bootclasspath)
- // Conscrypt and i18n currently do not enable annotations
- annotationModules := bootclasspath
- annotationModules = remove(annotationModules, "conscrypt.module.public.api")
- annotationModules = remove(annotationModules, "i18n.module.public.api")
- createMergedAnnotations(ctx, annotationModules)
+ createMergedModuleLibStubs(ctx, bootclasspath)
- // For the filtered api versions, we prune all APIs except art module's APIs. because
- // 1) ART apis are available by default to all modules, while other module-to-module deps are
- // explicit and probably receive more scrutiny anyway
- // 2) The number of ART/libcore APIs is large, so not linting them would create a large gap
- // 3) It's a compromise. Ideally we wouldn't be filtering out any module APIs, and have
- // per-module lint databases that excludes just that module's APIs. Alas, that's more
- // difficult to achieve.
- filteredModules := bootclasspath
- filteredModules = remove(filteredModules, "art.module.public.api")
- createFilteredApiVersions(ctx, filteredModules)
+ createMergedAnnotations(ctx, bootclasspath)
+
+ createFilteredApiVersions(ctx, bootclasspath)
}
func combinedApisModuleFactory() android.Module {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 2f9d7d0..b869116 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1038,12 +1038,12 @@
field public static final String ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE";
field public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
field @RequiresPermission(android.Manifest.permission.MANAGE_FACTORY_RESET_PROTECTION) public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED = "android.app.action.RESET_PROTECTION_POLICY_CHANGED";
- field public static final String ACTION_ROLE_HOLDER_PROVISION_FINALIZATION = "android.app.action.ROLE_HOLDER_PROVISION_FINALIZATION";
- field public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
- field public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_PROFILE";
+ field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_ROLE_HOLDER_PROVISION_FINALIZATION = "android.app.action.ROLE_HOLDER_PROVISION_FINALIZATION";
+ field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
+ field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_PROFILE";
field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
field @Deprecated public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
- field public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER";
+ field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER";
field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6
field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb
field public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd
@@ -6384,6 +6384,7 @@
public static final class TvInputManager.Hardware {
method public void overrideAudioSink(int, String, int, int, int);
+ method public void overrideAudioSink(@NonNull android.media.AudioDeviceInfo, @IntRange(from=0) int, int, int);
method public void setStreamVolume(float);
method public boolean setSurface(android.view.Surface, android.media.tv.TvStreamConfig);
}
@@ -9154,6 +9155,7 @@
field public static final int EVENT_UNSPECIFIED = 0; // 0x0
field public static final int REASON_ACCOUNT_TRANSFER = 104; // 0x68
field public static final int REASON_ACTIVITY_RECOGNITION = 103; // 0x67
+ field public static final int REASON_BLUETOOTH_BROADCAST = 203; // 0xcb
field public static final int REASON_GEOFENCING = 100; // 0x64
field public static final int REASON_LOCATION_PROVIDER = 312; // 0x138
field public static final int REASON_OTHER = 1; // 0x1
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 66250fce..d3c99b8 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1147,15 +1147,15 @@
public final class DisplayManager {
method public boolean areUserDisabledHdrTypesAllowed();
- method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void clearUserPreferredDisplayMode();
+ method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void clearGlobalUserPreferredDisplayMode();
+ method @Nullable public android.view.Display.Mode getGlobalUserPreferredDisplayMode();
method @NonNull public int[] getUserDisabledHdrTypes();
- method @Nullable public android.view.Display.Mode getUserPreferredDisplayMode();
method public boolean isMinimalPostProcessingRequested(int);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setAreUserDisabledHdrTypesAllowed(boolean);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void setGlobalUserPreferredDisplayMode(@NonNull android.view.Display.Mode);
method @RequiresPermission(android.Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE) public void setRefreshRateSwitchingType(int);
method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public void setShouldAlwaysRespectAppRequestedMode(boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setUserDisabledHdrTypes(@NonNull int[]);
- method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void setUserPreferredDisplayMode(@NonNull android.view.Display.Mode);
method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public boolean shouldAlwaysRespectAppRequestedMode();
field public static final int SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS = 2; // 0x2
field public static final int SWITCHING_TYPE_NONE = 0; // 0x0
@@ -2714,11 +2714,14 @@
}
public final class Display {
+ method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void clearUserPreferredDisplayMode();
method @NonNull public android.view.Display.Mode getDefaultMode();
method @NonNull public int[] getReportedHdrTypes();
method @NonNull public android.graphics.ColorSpace[] getSupportedWideColorGamut();
method public int getType();
+ method @Nullable public android.view.Display.Mode getUserPreferredDisplayMode();
method public boolean hasAccess(int);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void setUserPreferredDisplayMode(@NonNull android.view.Display.Mode);
field public static final int FLAG_TRUSTED = 128; // 0x80
field public static final int TYPE_EXTERNAL = 2; // 0x2
field public static final int TYPE_INTERNAL = 1; // 0x1
@@ -2733,6 +2736,13 @@
method public boolean matches(int, int, float);
}
+ public static final class Display.Mode.Builder {
+ ctor public Display.Mode.Builder();
+ method @NonNull public android.view.Display.Mode build();
+ method @NonNull public android.view.Display.Mode.Builder setRefreshRate(float);
+ method @NonNull public android.view.Display.Mode.Builder setResolution(int, int);
+ }
+
public class FocusFinder {
method public static void sort(android.view.View[], int, int, android.view.ViewGroup, boolean);
}
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index e3690e5..01604e6 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -737,6 +737,8 @@
MissingGetterMatchingBuilder: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String):
+MissingGetterMatchingBuilder: android.view.Display.Mode.Builder#setResolution(int, int):
+ android.view.Display.Mode does not declare a `getResolution()` method matching method android.view.Display.Mode.Builder.setResolution(int,int)
MissingNullability: android.app.Activity#onMovedToDisplay(int, android.content.res.Configuration) parameter #1:
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 73ebdf6..a60de08 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -488,8 +488,7 @@
*
* @hide
*/
- // TODO(b/208628038): Uncomment when the permission is in place
- // @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+ @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP)
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@SystemApi
public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE =
@@ -535,8 +534,7 @@
*
* @hide
*/
- // TODO(b/208628038): Uncomment when the permission is in place
- // @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+ @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP)
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@SystemApi
public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE =
@@ -561,8 +559,7 @@
*
* @hide
*/
- // TODO(b/208628038): Uncomment when the permission is in place
- // @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+ @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP)
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@SystemApi
public static final String ACTION_ROLE_HOLDER_PROVISION_FINALIZATION =
@@ -2859,8 +2856,7 @@
*
* @hide
*/
- // TODO(b/208628038): Uncomment when the permission is in place
- // @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+ @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP)
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@SystemApi
public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER =
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index a695f6d..661291c 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -28,8 +28,7 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SuppressLint;
-import android.annotation.SystemApi;
-import android.app.PropertyInvalidatedCache;
+import android.annotation.SystemApi; //import android.app.PropertyInvalidatedCache;
import android.bluetooth.BluetoothDevice.Transport;
import android.bluetooth.BluetoothProfile.ConnectionPolicy;
import android.bluetooth.annotations.RequiresBluetoothAdvertisePermission;
@@ -687,14 +686,15 @@
"android.bluetooth.adapter.action.BLE_ACL_DISCONNECTED";
/** The profile is in disconnected state */
- public static final int STATE_DISCONNECTED = BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTED;
+ public static final int STATE_DISCONNECTED =
+ 0; //BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTED;
/** The profile is in connecting state */
- public static final int STATE_CONNECTING = BluetoothProtoEnums.CONNECTION_STATE_CONNECTING;
+ public static final int STATE_CONNECTING = 1; //BluetoothProtoEnums.CONNECTION_STATE_CONNECTING;
/** The profile is in connected state */
- public static final int STATE_CONNECTED = BluetoothProtoEnums.CONNECTION_STATE_CONNECTED;
+ public static final int STATE_CONNECTED = 2; //BluetoothProtoEnums.CONNECTION_STATE_CONNECTED;
/** The profile is in disconnecting state */
public static final int STATE_DISCONNECTING =
- BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTING;
+ 3; //BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTING;
/** @hide */
public static final String BLUETOOTH_MANAGER_SERVICE = "bluetooth_manager";
@@ -1055,6 +1055,7 @@
return false;
}
+ /*
private static final String BLUETOOTH_GET_STATE_CACHE_PROPERTY = "cache_key.bluetooth.get_state";
private final PropertyInvalidatedCache<Void, Integer> mBluetoothGetStateCache =
@@ -1070,17 +1071,22 @@
}
}
};
+ */
/** @hide */
+ /*
@RequiresNoPermission
public void disableBluetoothGetStateCache() {
mBluetoothGetStateCache.disableLocal();
}
+ */
/** @hide */
+ /*
public static void invalidateBluetoothGetStateCache() {
PropertyInvalidatedCache.invalidateCache(BLUETOOTH_GET_STATE_CACHE_PROPERTY);
}
+ */
/**
* Fetch the current bluetooth state. If the service is down, return
@@ -1092,14 +1098,12 @@
try {
mServiceLock.readLock().lock();
if (mService != null) {
- state = mBluetoothGetStateCache.query(null);
+ //state = mBluetoothGetStateCache.query(null);
+ state = mService.getState();
}
- } catch (RuntimeException e) {
- if (e.getCause() instanceof RemoteException) {
- Log.e(TAG, "", e.getCause());
- } else {
- throw e;
- }
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ e.rethrowFromSystemServer();
} finally {
mServiceLock.readLock().unlock();
}
@@ -2078,6 +2082,7 @@
}
}
+ /*
private static final String BLUETOOTH_FILTERING_CACHE_PROPERTY =
"cache_key.bluetooth.is_offloaded_filtering_supported";
private final PropertyInvalidatedCache<Void, Boolean> mBluetoothFilteringCache =
@@ -2100,17 +2105,22 @@
}
};
+ */
/** @hide */
+ /*
@RequiresNoPermission
public void disableIsOffloadedFilteringSupportedCache() {
mBluetoothFilteringCache.disableLocal();
}
+ */
/** @hide */
+ /*
public static void invalidateIsOffloadedFilteringSupportedCache() {
PropertyInvalidatedCache.invalidateCache(BLUETOOTH_FILTERING_CACHE_PROPERTY);
}
+ */
/**
* Return true if offloaded filters are supported
@@ -2123,7 +2133,18 @@
if (!getLeAccess()) {
return false;
}
- return mBluetoothFilteringCache.query(null);
+ //return mBluetoothFilteringCache.query(null);
+ try {
+ mServiceLock.readLock().lock();
+ if (mService != null) {
+ return mService.isOffloadedFilteringSupported();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to get isOffloadedFilteringSupported, error: ", e);
+ } finally {
+ mServiceLock.readLock().unlock();
+ }
+ return false;
}
/**
@@ -2530,15 +2551,13 @@
return supportedProfiles;
}
+ /*
private static final String BLUETOOTH_GET_ADAPTER_CONNECTION_STATE_CACHE_PROPERTY =
"cache_key.bluetooth.get_adapter_connection_state";
private final PropertyInvalidatedCache<Void, Integer>
mBluetoothGetAdapterConnectionStateCache =
new PropertyInvalidatedCache<Void, Integer> (
8, BLUETOOTH_GET_ADAPTER_CONNECTION_STATE_CACHE_PROPERTY) {
- /**
- * This method must not be called when mService is null.
- */
@Override
@SuppressLint("AndroidFrameworkRequiresPermission")
public Integer recompute(Void query) {
@@ -2549,18 +2568,23 @@
}
}
};
+ */
/** @hide */
+ /*
@RequiresNoPermission
public void disableGetAdapterConnectionStateCache() {
mBluetoothGetAdapterConnectionStateCache.disableLocal();
}
+ */
/** @hide */
+ /*
public static void invalidateGetAdapterConnectionStateCache() {
PropertyInvalidatedCache.invalidateCache(
BLUETOOTH_GET_ADAPTER_CONNECTION_STATE_CACHE_PROPERTY);
}
+ */
/**
* Get the current connection state of the local Bluetooth adapter.
@@ -2584,20 +2608,18 @@
try {
mServiceLock.readLock().lock();
if (mService != null) {
- return mBluetoothGetAdapterConnectionStateCache.query(null);
+ return mService.getAdapterConnectionState();
}
- } catch (RuntimeException e) {
- if (e.getCause() instanceof RemoteException) {
- Log.e(TAG, "getConnectionState:", e.getCause());
- } else {
- throw e;
- }
+ //return mBluetoothGetAdapterConnectionStateCache.query(null);
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to getConnectionState, error: ", e);
} finally {
mServiceLock.readLock().unlock();
}
return BluetoothAdapter.STATE_DISCONNECTED;
}
+ /*
private static final String BLUETOOTH_PROFILE_CACHE_PROPERTY =
"cache_key.bluetooth.get_profile_connection_state";
private final PropertyInvalidatedCache<Integer, Integer>
@@ -2625,17 +2647,22 @@
query);
}
};
+ */
/** @hide */
+ /*
@RequiresNoPermission
public void disableGetProfileConnectionStateCache() {
mGetProfileConnectionStateCache.disableLocal();
}
+ */
/** @hide */
+ /*
public static void invalidateGetProfileConnectionStateCache() {
PropertyInvalidatedCache.invalidateCache(BLUETOOTH_PROFILE_CACHE_PROPERTY);
}
+ */
/**
* Get the current connection state of a profile.
@@ -2657,7 +2684,18 @@
if (getState() != STATE_ON) {
return BluetoothProfile.STATE_DISCONNECTED;
}
- return mGetProfileConnectionStateCache.query(new Integer(profile));
+ try {
+ mServiceLock.readLock().lock();
+ if (mService != null) {
+ mService.getProfileConnectionState(profile);
+ }
+ //return mGetProfileConnectionStateCache.query(new Integer(profile));
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to getProfileConnectionState, error: ", e);
+ } finally {
+ mServiceLock.readLock().unlock();
+ }
+ return BluetoothProfile.STATE_DISCONNECTED;
}
/**
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index fc99942..984166d 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -23,8 +23,7 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SuppressLint;
-import android.annotation.SystemApi;
-import android.app.PropertyInvalidatedCache;
+import android.annotation.SystemApi; //import android.app.PropertyInvalidatedCache;
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
import android.bluetooth.annotations.RequiresBluetoothLocationPermission;
import android.bluetooth.annotations.RequiresBluetoothScanPermission;
@@ -1597,6 +1596,7 @@
return false;
}
+ /*
private static final String BLUETOOTH_BONDING_CACHE_PROPERTY =
"cache_key.bluetooth.get_bond_state";
private final PropertyInvalidatedCache<BluetoothDevice, Integer> mBluetoothBondCache =
@@ -1612,16 +1612,19 @@
}
}
};
+ */
/** @hide */
- public void disableBluetoothGetBondStateCache() {
+ /* public void disableBluetoothGetBondStateCache() {
mBluetoothBondCache.disableLocal();
- }
+ } */
/** @hide */
+ /*
public static void invalidateBluetoothGetBondStateCache() {
PropertyInvalidatedCache.invalidateCache(BLUETOOTH_BONDING_CACHE_PROPERTY);
}
+ */
/**
* Get the bond state of the remote device.
@@ -1643,13 +1646,11 @@
return BOND_NONE;
}
try {
- return mBluetoothBondCache.query(this);
- } catch (RuntimeException e) {
- if (e.getCause() instanceof RemoteException) {
- Log.e(TAG, "", e);
- } else {
- throw e;
- }
+ //return mBluetoothBondCache.query(this);
+ return sService.getBondState(this, mAttributionSource);
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to ", e);
+ e.rethrowFromSystemServer();
}
return BOND_NONE;
}
diff --git a/core/java/android/companion/IOnAssociationsChangedListener.aidl b/core/java/android/companion/IOnAssociationsChangedListener.aidl
index e6794b7..d369456 100644
--- a/core/java/android/companion/IOnAssociationsChangedListener.aidl
+++ b/core/java/android/companion/IOnAssociationsChangedListener.aidl
@@ -20,5 +20,22 @@
/** @hide */
interface IOnAssociationsChangedListener {
- oneway void onAssociationsChanged(in List<AssociationInfo> associations);
+
+ /*
+ * IMPORTANT: This method is intentionally NOT "oneway".
+ *
+ * The method is intentionally "blocking" to make sure that the clients of the
+ * addOnAssociationsChangedListener() API (@SystemAPI guarded by a "signature" permission) are
+ * able to prevent race conditions that may arise if their own clients (applications)
+ * effectively get notified about the changes before system services do.
+ *
+ * This is safe for 2 reasons:
+ * 1. The addOnAssociationsChangedListener() is only available to the system components
+ * (guarded by a "signature" permission).
+ * See android.permission.MANAGE_COMPANION_DEVICES.
+ * 2. On the Java side addOnAssociationsChangedListener() in CDM takes an Executor, and the
+ * proxy implementation of onAssociationsChanged() simply "post" a Runnable to it.
+ * See CompanionDeviceManager.OnAssociationsChangedListenerProxy class.
+ */
+ void onAssociationsChanged(in List<AssociationInfo> associations);
}
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index 2dfa202..d80bee6 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -17,6 +17,7 @@
package android.companion.virtual;
import android.companion.virtual.IVirtualDevice;
+import android.companion.virtual.VirtualDeviceParams;
/**
* Interface for communication between VirtualDeviceManager and VirtualDeviceManagerService.
@@ -33,6 +34,10 @@
* that this belongs to the calling UID.
* @param associationId The association ID as returned by {@link AssociationInfo#getId()} from
* CDM. Virtual devices must have a corresponding association with CDM in order to be created.
+ * @param params The parameters for creating this virtual device. See {@link
+ * VirtualDeviceManager.VirtualDeviceParams}.
*/
- IVirtualDevice createVirtualDevice(in IBinder token, String packageName, int associationId);
+ IVirtualDevice createVirtualDevice(
+ in IBinder token, String packageName, int associationId,
+ in VirtualDeviceParams params);
}
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index bace45b..858e4daa1 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -100,11 +100,11 @@
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@Nullable
- public VirtualDevice createVirtualDevice(int associationId) {
+ public VirtualDevice createVirtualDevice(int associationId, VirtualDeviceParams params) {
// TODO(b/194949534): Unhide this API
try {
IVirtualDevice virtualDevice = mService.createVirtualDevice(
- new Binder(), mContext.getPackageName(), associationId);
+ new Binder(), mContext.getPackageName(), associationId, params);
return new VirtualDevice(mContext, virtualDevice);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -273,6 +273,12 @@
}
}
+ /**
+ * Returns the display flags that should be added to a particular virtual display.
+ * Additional device-level flags from {@link
+ * com.android.server.companion.virtual.VirtualDeviceImpl#getBaseVirtualDisplayFlags()} will
+ * be added by DisplayManagerService.
+ */
private int getVirtualDisplayFlags(@DisplayFlags int flags) {
int virtualDisplayFlags = DEFAULT_VIRTUAL_DISPLAY_FLAGS;
if ((flags & DISPLAY_FLAG_TRUSTED) != 0) {
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.aidl b/core/java/android/companion/virtual/VirtualDeviceParams.aidl
new file mode 100644
index 0000000..9b3974a
--- /dev/null
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual;
+
+parcelable VirtualDeviceParams;
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
new file mode 100644
index 0000000..d61d474
--- /dev/null
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual;
+
+import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.util.ArraySet;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Params that can be configured when creating virtual devices.
+ *
+ * @hide
+ */
+// TODO(b/194949534): Unhide this API
+public final class VirtualDeviceParams implements Parcelable {
+
+ /** @hide */
+ @IntDef(prefix = "LOCK_STATE_",
+ value = {LOCK_STATE_ALWAYS_LOCKED, LOCK_STATE_ALWAYS_UNLOCKED})
+ @Retention(RetentionPolicy.SOURCE)
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ public @interface LockState {}
+
+ /**
+ * Indicates that the lock state of the virtual device should be always locked.
+ *
+ * @hide // TODO(b/194949534): Unhide this API
+ */
+ public static final int LOCK_STATE_ALWAYS_LOCKED = 0;
+
+ /**
+ * Indicates that the lock state of the virtual device should be always unlocked.
+ *
+ * @hide // TODO(b/194949534): Unhide this API
+ */
+ public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1;
+
+ private final int mLockState;
+ private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
+
+ private VirtualDeviceParams(
+ @LockState int lockState,
+ @NonNull Set<UserHandle> usersWithMatchingAccounts) {
+ mLockState = lockState;
+ mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts);
+ }
+
+ @SuppressWarnings("unchecked")
+ private VirtualDeviceParams(Parcel parcel) {
+ mLockState = parcel.readInt();
+ mUsersWithMatchingAccounts = (ArraySet<UserHandle>) parcel.readArraySet(null);
+ }
+
+ /**
+ * Returns the lock state of the virtual device.
+ */
+ @LockState
+ public int getLockState() {
+ return mLockState;
+ }
+
+ /**
+ * Returns the user handles with matching managed accounts on the remote device to which
+ * this virtual device is streaming.
+ *
+ * @see android.app.admin.DevicePolicyManager#NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY
+ */
+ @NonNull
+ public Set<UserHandle> getUsersWithMatchingAccounts() {
+ return Collections.unmodifiableSet(mUsersWithMatchingAccounts);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mLockState);
+ dest.writeArraySet(mUsersWithMatchingAccounts);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof VirtualDeviceParams)) {
+ return false;
+ }
+ VirtualDeviceParams that = (VirtualDeviceParams) o;
+ return mLockState == that.mLockState && mUsersWithMatchingAccounts.equals(
+ that.mUsersWithMatchingAccounts);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mLockState, mUsersWithMatchingAccounts);
+ }
+
+ @Override
+ public String toString() {
+ return "VirtualDeviceParams("
+ + " mLockState=" + mLockState
+ + " mUsersWithMatchingAccounts=" + mUsersWithMatchingAccounts
+ + ")";
+ }
+
+ public static final Parcelable.Creator<VirtualDeviceParams> CREATOR =
+ new Parcelable.Creator<VirtualDeviceParams>() {
+ public VirtualDeviceParams createFromParcel(Parcel in) {
+ return new VirtualDeviceParams(in);
+ }
+
+ public VirtualDeviceParams[] newArray(int size) {
+ return new VirtualDeviceParams[size];
+ }
+ };
+
+ /**
+ * Builder for {@link VirtualDeviceParams}.
+ */
+ public static final class Builder {
+
+ private @LockState int mLockState = LOCK_STATE_ALWAYS_LOCKED;
+ private Set<UserHandle> mUsersWithMatchingAccounts;
+
+ /**
+ * Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY}
+ * is required if this is set to {@link #LOCK_STATE_ALWAYS_UNLOCKED}.
+ * The default is {@link #LOCK_STATE_ALWAYS_LOCKED}.
+ *
+ * @param lockState The lock state, either {@link #LOCK_STATE_ALWAYS_LOCKED} or
+ * {@link #LOCK_STATE_ALWAYS_UNLOCKED}.
+ */
+ @RequiresPermission(value = ADD_ALWAYS_UNLOCKED_DISPLAY, conditional = true)
+ @NonNull
+ public Builder setLockState(@LockState int lockState) {
+ mLockState = lockState;
+ return this;
+ }
+
+ /**
+ * Sets the user handles with matching managed accounts on the remote device to which
+ * this virtual device is streaming.
+ *
+ * @param usersWithMatchingAccounts A set of user handles with matching managed
+ * accounts on the remote device this is streaming to.
+ * @see android.app.admin.DevicePolicyManager#NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY
+ */
+ public Builder setUsersWithMatchingAccounts(
+ @NonNull Set<UserHandle> usersWithMatchingAccounts) {
+ mUsersWithMatchingAccounts = usersWithMatchingAccounts;
+ return this;
+ }
+
+ /**
+ * Builds the {@link VirtualDeviceParams} instance.
+ */
+ @NonNull
+ public VirtualDeviceParams build() {
+ if (mUsersWithMatchingAccounts == null) {
+ mUsersWithMatchingAccounts = Collections.emptySet();
+ }
+ return new VirtualDeviceParams(mLockState, mUsersWithMatchingAccounts);
+ }
+ }
+}
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index d04c97c..a503d14 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -25,6 +25,9 @@
"path": "cts/hostsidetests/packagemanager"
},
{
+ "path": "cts/hostsidetests/os/test_mappings/packagemanager"
+ },
+ {
"path": "system/apex/tests"
}
],
@@ -128,6 +131,23 @@
"exclude-annotation": "org.junit.Ignore"
}
]
+ },
+ {
+ "name": "CtsAppSecurityHostTestCases",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.Postsubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
}
],
"postsubmit": [
diff --git a/core/java/android/content/pm/parsing/component/ParsedApexSystemService.java b/core/java/android/content/pm/parsing/component/ParsedApexSystemService.java
index fe821e0..c89d3b2 100644
--- a/core/java/android/content/pm/parsing/component/ParsedApexSystemService.java
+++ b/core/java/android/content/pm/parsing/component/ParsedApexSystemService.java
@@ -35,4 +35,6 @@
@Nullable
String getMaxSdkVersion();
+ int getInitOrder();
+
}
diff --git a/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java b/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java
index 54196fd..65d26b9 100644
--- a/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java
+++ b/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java
@@ -45,10 +45,11 @@
@Nullable
private String maxSdkVersion;
+ private int initOrder;
+
public ParsedApexSystemServiceImpl() {
}
-
// Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
@@ -67,13 +68,15 @@
@NonNull String name,
@Nullable String jarPath,
@Nullable String minSdkVersion,
- @Nullable String maxSdkVersion) {
+ @Nullable String maxSdkVersion,
+ int initOrder) {
this.name = name;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, name);
this.jarPath = jarPath;
this.minSdkVersion = minSdkVersion;
this.maxSdkVersion = maxSdkVersion;
+ this.initOrder = initOrder;
// onConstructed(); // You can define this method to get a callback
}
@@ -99,6 +102,11 @@
}
@DataClass.Generated.Member
+ public int getInitOrder() {
+ return initOrder;
+ }
+
+ @DataClass.Generated.Member
public @NonNull ParsedApexSystemServiceImpl setName(@NonNull String value) {
name = value;
com.android.internal.util.AnnotationValidations.validate(
@@ -125,6 +133,12 @@
}
@DataClass.Generated.Member
+ public @NonNull ParsedApexSystemServiceImpl setInitOrder( int value) {
+ initOrder = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
static Parcelling<String> sParcellingForName =
Parcelling.Cache.get(
Parcelling.BuiltIn.ForInternedString.class);
@@ -183,6 +197,7 @@
sParcellingForJarPath.parcel(jarPath, dest, flags);
sParcellingForMinSdkVersion.parcel(minSdkVersion, dest, flags);
sParcellingForMaxSdkVersion.parcel(maxSdkVersion, dest, flags);
+ dest.writeInt(initOrder);
}
@Override
@@ -201,6 +216,7 @@
String _jarPath = sParcellingForJarPath.unparcel(in);
String _minSdkVersion = sParcellingForMinSdkVersion.unparcel(in);
String _maxSdkVersion = sParcellingForMaxSdkVersion.unparcel(in);
+ int _initOrder = in.readInt();
this.name = _name;
com.android.internal.util.AnnotationValidations.validate(
@@ -208,6 +224,7 @@
this.jarPath = _jarPath;
this.minSdkVersion = _minSdkVersion;
this.maxSdkVersion = _maxSdkVersion;
+ this.initOrder = _initOrder;
// onConstructed(); // You can define this method to get a callback
}
@@ -227,10 +244,10 @@
};
@DataClass.Generated(
- time = 1638903241144L,
+ time = 1641307133386L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java",
- inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedApexSystemService]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)")
+ inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nprivate int initOrder\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedApexSystemService]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceUtils.java b/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceUtils.java
index 26abf48..eca8976 100644
--- a/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceUtils.java
@@ -53,10 +53,13 @@
R.styleable.AndroidManifestApexSystemService_minSdkVersion);
String maxSdkVersion = sa.getString(
R.styleable.AndroidManifestApexSystemService_maxSdkVersion);
+ int initOrder = sa.getInt(R.styleable.AndroidManifestApexSystemService_initOrder, 0);
systemService.setName(className)
.setMinSdkVersion(minSdkVersion)
- .setMaxSdkVersion(maxSdkVersion);
+ .setMaxSdkVersion(maxSdkVersion)
+ .setInitOrder(initOrder);
+
if (!TextUtils.isEmpty(jarPath)) {
systemService.setJarPath(jarPath);
}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index de5c9ad..89ac8bf 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -1132,41 +1132,48 @@
}
/**
- * Sets the default display mode, according to the refresh rate and the resolution chosen by the
- * user.
+ * Sets the global default {@link Display.Mode}. The display mode includes preference for
+ * resolution and refresh rate. The mode change is applied globally, i.e. to all the connected
+ * displays. If the mode specified is not supported by a connected display, then no mode change
+ * occurs for that display.
*
+ * @param mode The {@link Display.Mode} to set, which can include resolution and/or
+ * refresh-rate. It is created using {@link Display.Mode.Builder}.
+ *`
* @hide
*/
@TestApi
@RequiresPermission(Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE)
- public void setUserPreferredDisplayMode(@NonNull Display.Mode mode) {
+ public void setGlobalUserPreferredDisplayMode(@NonNull Display.Mode mode) {
// Create a new object containing default values for the unused fields like mode ID and
// alternative refresh rates.
Display.Mode preferredMode = new Display.Mode(mode.getPhysicalWidth(),
mode.getPhysicalHeight(), mode.getRefreshRate());
- mGlobal.setUserPreferredDisplayMode(preferredMode);
+ mGlobal.setUserPreferredDisplayMode(Display.INVALID_DISPLAY, preferredMode);
}
/**
- * Removes the user preferred display mode.
+ * Removes the global user preferred display mode.
+ * User preferred display mode is cleared for all the connected displays.
*
* @hide
*/
@TestApi
@RequiresPermission(Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE)
- public void clearUserPreferredDisplayMode() {
- mGlobal.setUserPreferredDisplayMode(null);
+ public void clearGlobalUserPreferredDisplayMode() {
+ mGlobal.setUserPreferredDisplayMode(Display.INVALID_DISPLAY, null);
}
/**
- * Returns the user preferred display mode.
+ * Returns the global user preferred display mode.
+ * If no user preferred mode has been set, or it has been cleared, this method returns null.
*
* @hide
*/
@TestApi
@Nullable
- public Display.Mode getUserPreferredDisplayMode() {
- return mGlobal.getUserPreferredDisplayMode();
+ public Display.Mode getGlobalUserPreferredDisplayMode() {
+ return mGlobal.getUserPreferredDisplayMode(Display.INVALID_DISPLAY);
}
/**
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index bf6e665..1a7a63ae 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -896,9 +896,9 @@
* Sets the default display mode, according to the refresh rate and the resolution chosen by the
* user.
*/
- public void setUserPreferredDisplayMode(Display.Mode mode) {
+ public void setUserPreferredDisplayMode(int displayId, Display.Mode mode) {
try {
- mDm.setUserPreferredDisplayMode(mode);
+ mDm.setUserPreferredDisplayMode(displayId, mode);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
@@ -907,9 +907,9 @@
/**
* Returns the user preferred display mode.
*/
- public Display.Mode getUserPreferredDisplayMode() {
+ public Display.Mode getUserPreferredDisplayMode(int displayId) {
try {
- return mDm.getUserPreferredDisplayMode();
+ return mDm.getUserPreferredDisplayMode(displayId);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index d38d388..35663af 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -166,8 +166,8 @@
// Sets the user preferred display mode.
// Requires MODIFY_USER_PREFERRED_DISPLAY_MODE permission.
- void setUserPreferredDisplayMode(in Mode mode);
- Mode getUserPreferredDisplayMode();
+ void setUserPreferredDisplayMode(int displayId, in Mode mode);
+ Mode getUserPreferredDisplayMode(int displayId);
// When enabled the app requested display resolution and refresh rate is always selected
// in DisplayModeDirector regardless of user settings and policies for low brightness, low
diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java
index 5c28553..3193826 100644
--- a/core/java/android/net/VpnManager.java
+++ b/core/java/android/net/VpnManager.java
@@ -96,6 +96,141 @@
*/
public static final String NOTIFICATION_CHANNEL_VPN = "VPN";
+ /**
+ * Action sent in the intent when an error occurred.
+ *
+ * @hide
+ */
+ public static final String ACTION_VPN_MANAGER_ERROR = "android.net.action.VPN_MANAGER_ERROR";
+
+ /**
+ * An IKE protocol error. Codes are the codes from IkeProtocolException, RFC 7296.
+ *
+ * @hide
+ */
+ public static final String CATEGORY_ERROR_IKE = "android.net.category.ERROR_IKE";
+
+ /**
+ * User deactivated the VPN, either by turning it off or selecting a different VPN provider.
+ * The error code is always 0.
+ *
+ * @hide
+ */
+ public static final String CATEGORY_ERROR_USER_DEACTIVATED =
+ "android.net.category.ERROR_USER_DEACTIVATED";
+
+ /**
+ * Network error. Error codes are ERROR_CODE_NETWORK_*.
+ *
+ * @hide
+ */
+ public static final String CATEGORY_ERROR_NETWORK = "android.net.category.ERROR_NETWORK";
+
+ /**
+ * The key of the session that experienced this error, as returned by
+ * startProvisionedVpnProfileSession.
+ *
+ * @hide
+ */
+ public static final String EXTRA_SESSION_KEY = "android.net.extra.SESSION_KEY";
+
+ /**
+ * Extra for the Network object that was the underlying network at the time of the failure, or
+ * null if none.
+ *
+ * @hide
+ */
+ public static final String EXTRA_UNDERLYING_NETWORK = "android.net.extra.UNDERLYING_NETWORK";
+
+ /**
+ * The NetworkCapabilities of the underlying network.
+ *
+ * @hide
+ */
+ public static final String EXTRA_UNDERLYING_NETWORK_CAPABILITIES =
+ "android.net.extra.UNDERLYING_NETWORK_CAPABILITIES";
+
+ /**
+ * The LinkProperties of the underlying network.
+ *
+ * @hide
+ */
+ public static final String EXTRA_UNDERLYING_LINK_PROPERTIES =
+ "android.net.extra.UNDERLYING_LINK_PROPERTIES";
+
+ /**
+ * A long timestamp with SystemClock.elapsedRealtime base for when the event happened.
+ *
+ * @hide
+ */
+ public static final String EXTRA_TIMESTAMP = "android.net.extra.TIMESTAMP";
+
+ /**
+ * Extra for the error type. This is ERROR_NOT_RECOVERABLE or ERROR_RECOVERABLE.
+ *
+ * @hide
+ */
+ public static final String EXTRA_ERROR_TYPE = "android.net.extra.ERROR_TYPE";
+
+ /**
+ * Extra for the error code. The value will be 0 for CATEGORY_ERROR_USER_DEACTIVATED, one of
+ * ERROR_CODE_NETWORK_* for ERROR_CATEGORY_NETWORK or one of values defined in
+ * IkeProtocolException#ErrorType for CATEGORY_ERROR_IKE.
+ *
+ * @hide
+ */
+ public static final String EXTRA_ERROR_CODE = "android.net.extra.ERROR_CODE";
+
+ /**
+ * This error is fatal, e.g. the VPN was disabled or configuration error. The stack will not
+ * retry connection.
+ *
+ * @hide
+ */
+ public static final int ERROR_NOT_RECOVERABLE = 1;
+
+ /**
+ * The stack experienced an error but will retry with exponential backoff, e.g. network timeout.
+ *
+ * @hide
+ */
+ public static final int ERROR_RECOVERABLE = 2;
+
+ /**
+ * An error code to indicate that there was an UnknownHostException.
+ *
+ * @hide
+ */
+ public static final int ERROR_CODE_NETWORK_UNKNOWN_HOST = 0;
+
+ /**
+ * An error code to indicate that there is a SocketTimeoutException.
+ *
+ * @hide
+ */
+ public static final int ERROR_CODE_NETWORK_TIMEOUT = 1;
+
+ /**
+ * An error code to indicate that the connection is refused.
+ *
+ * @hide
+ */
+ public static final int ERROR_CODE_NETWORK_CONNECT = 2;
+
+ /**
+ * An error code to indicate the connection was reset. (e.g. SocketException)
+ *
+ * @hide
+ */
+ public static final int ERROR_CODE_NETWORK_CONNECTION_RESET = 3;
+
+ /**
+ * An error code to indicate that there is an IOException.
+ *
+ * @hide
+ */
+ public static final int ERROR_CODE_NETWORK_IO = 4;
+
/** @hide */
@IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM, TYPE_VPN_LEGACY,
TYPE_VPN_OEM})
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 3cc51c7..70266c1 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE;
import static android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS;
+import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -138,6 +139,24 @@
public static final int INVALID_DISPLAY = -1;
/**
+ * Invalid resolution width.
+ * @hide
+ */
+ public static final int INVALID_DISPLAY_WIDTH = -1;
+
+ /**
+ * Invalid resolution height.
+ * @hide
+ */
+ public static final int INVALID_DISPLAY_HEIGHT = -1;
+
+ /**
+ * Invalid refresh rate.
+ * @hide
+ */
+ public static final float INVALID_DISPLAY_REFRESH_RATE = 0.0f;
+
+ /**
* The default display group id, which is the display group id of the primary display assuming
* there is one.
* @hide
@@ -1170,6 +1189,49 @@
}
/**
+ * Sets the default {@link Display.Mode} to use for the display. The display mode includes
+ * preference for resolution and refresh rate.
+ * If the mode specified is not supported by the display, then no mode change occurs.
+ *
+ * @param mode The {@link Display.Mode} to set, which can include resolution and/or
+ * refresh-rate. It is created using {@link Display.Mode.Builder}.
+ *`
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE)
+ public void setUserPreferredDisplayMode(@NonNull Display.Mode mode) {
+ // Create a new object containing default values for the unused fields like mode ID and
+ // alternative refresh rates.
+ Display.Mode preferredMode = new Display.Mode(mode.getPhysicalWidth(),
+ mode.getPhysicalHeight(), mode.getRefreshRate());
+ mGlobal.setUserPreferredDisplayMode(mDisplayId, preferredMode);
+ }
+
+ /**
+ * Removes the display's user preferred display mode.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE)
+ public void clearUserPreferredDisplayMode() {
+ mGlobal.setUserPreferredDisplayMode(mDisplayId, null);
+ }
+
+ /**
+ * Returns the display's user preferred display mode.
+ *
+ * @hide
+ */
+ @TestApi
+ @Nullable
+ public Display.Mode getUserPreferredDisplayMode() {
+ return mGlobal.getUserPreferredDisplayMode(mDisplayId);
+ }
+
+
+ /**
* Returns whether this display can be used to display wide color gamut content.
* This does not necessarily mean the device itself can render wide color gamut
* content. To ensure wide color gamut content can be produced, refer to
@@ -1710,6 +1772,30 @@
}
/**
+ * Returns true if the specified width is valid.
+ * @hide
+ */
+ public static boolean isWidthValid(int width) {
+ return width > 0;
+ }
+
+ /**
+ * Returns true if the specified height is valid.
+ * @hide
+ */
+ public static boolean isHeightValid(int height) {
+ return height > 0;
+ }
+
+ /**
+ * Returns true if the specified refresh-rate is valid.
+ * @hide
+ */
+ public static boolean isRefreshRateValid(float refreshRate) {
+ return refreshRate > 0.0f;
+ }
+
+ /**
* A mode supported by a given display.
*
* @see Display#getSupportedModes()
@@ -1846,6 +1932,30 @@
}
/**
+ * Returns {@code true} if this mode matches the given parameters, if those parameters are
+ * valid.<p>
+ * If resolution (width and height) is valid and refresh-rate is not, the method matches
+ * only resolution.
+ * If refresh-rate is valid and resolution (width and height) is not, the method matches
+ * only refresh-rate.</p>
+ *
+ * @hide
+ */
+ public boolean matchesIfValid(int width, int height, float refreshRate) {
+ if (!isWidthValid(width) && !isHeightValid(height)
+ && !isRefreshRateValid(refreshRate)) {
+ return false;
+ }
+ if (isWidthValid(width) != isHeightValid(height)) {
+ return false;
+ }
+ return (!isWidthValid(width) || mWidth == width)
+ && (!isHeightValid(height) || mHeight == height)
+ && (!isRefreshRateValid(refreshRate)
+ || Float.floatToIntBits(mRefreshRate) == Float.floatToIntBits(refreshRate));
+ }
+
+ /**
* Returns {@code true} if this mode equals to the other mode in all parameters except
* the refresh rate.
*
@@ -1855,6 +1965,24 @@
return mWidth == other.mWidth && mHeight == other.mHeight;
}
+ /**
+ * Returns {@code true} if refresh-rate is set for a display mode
+ *
+ * @hide
+ */
+ public boolean isRefreshRateSet() {
+ return mRefreshRate != INVALID_DISPLAY_REFRESH_RATE;
+ }
+
+ /**
+ * Returns {@code true} if refresh-rate is set for a display mode
+ *
+ * @hide
+ */
+ public boolean isResolutionSet() {
+ return mWidth != INVALID_DISPLAY_WIDTH && mHeight != INVALID_DISPLAY_HEIGHT;
+ }
+
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
@@ -1923,6 +2051,80 @@
return new Mode[size];
}
};
+
+ /**
+ * Builder is used to create {@link Display.Mode} objects
+ *
+ * @hide
+ */
+ @TestApi
+ public static final class Builder {
+ private int mWidth;
+ private int mHeight;
+ private float mRefreshRate;
+
+ public Builder() {
+ mWidth = Display.INVALID_DISPLAY_WIDTH;
+ mHeight = Display.INVALID_DISPLAY_HEIGHT;
+ mRefreshRate = Display.INVALID_DISPLAY_REFRESH_RATE;
+ }
+
+ /**
+ * Sets the resolution (width and height) of a {@link Display.Mode}
+ *
+ * @return Instance of {@link Builder}
+ */
+ @NonNull
+ public Builder setResolution(int width, int height) {
+ if (width > 0 && height > 0) {
+ mWidth = width;
+ mHeight = height;
+ }
+ return this;
+ }
+
+ /**
+ * Sets the refresh rate of a {@link Display.Mode}
+ *
+ * @return Instance of {@link Builder}
+ */
+ @NonNull
+ public Builder setRefreshRate(float refreshRate) {
+ if (refreshRate > 0.0f) {
+ mRefreshRate = refreshRate;
+ }
+ return this;
+ }
+
+ /**
+ * Creates the {@link Display.Mode} object.
+ *
+ * <p>
+ * If resolution needs to be set, but refresh-rate doesn't matter, create a mode with
+ * Builder and call setResolution.
+ * {@code
+ * Display.Mode mode =
+ * new Display.Mode.Builder()
+ * .setResolution(width, height)
+ * .build();
+ * }
+ * </p><p>
+ * If refresh-rate needs to be set, but resolution doesn't matter, create a mode with
+ * Builder and call setRefreshRate.
+ * {@code
+ * Display.Mode mode =
+ * new Display.Mode.Builder()
+ * .setRefreshRate(refreshRate)
+ * .build();
+ * }
+ * </p>
+ */
+ @NonNull
+ public Mode build() {
+ Display.Mode mode = new Mode(mWidth, mHeight, mRefreshRate);
+ return mode;
+ }
+ }
}
/**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index cd9f3eb6..5be3a57 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -3581,7 +3581,8 @@
/**
* If specified, the insets provided by this window will be our window frame minus the
- * insets specified by providedInternalInsets.
+ * insets specified by providedInternalInsets. This should not be used together with
+ * {@link WindowState#mGivenContentInsets}. If both of them are set, both will be applied.
*
* @hide
*/
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index cd5d0a1..5a66e9a 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -62,6 +62,7 @@
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION;
@@ -174,6 +175,7 @@
public static final int CUJ_SCREEN_OFF_SHOW_AOD = 41;
public static final int CUJ_ONE_HANDED_ENTER_TRANSITION = 42;
public static final int CUJ_ONE_HANDED_EXIT_TRANSITION = 43;
+ public static final int CUJ_UNFOLD_ANIM = 44;
private static final int NO_STATSD_LOGGING = -1;
@@ -226,6 +228,7 @@
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM,
};
private static volatile InteractionJankMonitor sInstance;
@@ -290,6 +293,7 @@
CUJ_SCREEN_OFF_SHOW_AOD,
CUJ_ONE_HANDED_ENTER_TRANSITION,
CUJ_ONE_HANDED_EXIT_TRANSITION,
+ CUJ_UNFOLD_ANIM,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -695,6 +699,8 @@
return "ONE_HANDED_ENTER_TRANSITION";
case CUJ_ONE_HANDED_EXIT_TRANSITION:
return "ONE_HANDED_EXIT_TRANSITION";
+ case CUJ_UNFOLD_ANIM:
+ return "UNFOLD_ANIM";
}
return "UNKNOWN";
}
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index db24475..f7e0fcf 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2825,6 +2825,14 @@
<attr name="path" />
<attr name="minSdkVersion" />
<attr name="maxSdkVersion" />
+ <!-- The order in which the apex system services are initiated. When there are dependencies
+ among apex system services, setting this attribute for each of them ensures that they are
+ created in the order required by those dependencies. The apex-system-services that are
+ started manually within SystemServer ignore the initOrder and are not considered for
+ automatic starting of the other services.
+ The value is a simple integer, with higher number being initialized first. If not specified,
+ the default order is 0. -->
+ <attr name="initOrder" format="integer" />
</declare-styleable>
<!-- The <code>receiver</code> tag declares an
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 6755b7c..d862377 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -119,7 +119,7 @@
AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex(cbData);
int64_t vsyncId = AChoreographerFrameCallbackData_getFrameTimelineVsyncId(
cbData, preferredFrameTimelineIndex);
- int64_t frameDeadline = AChoreographerFrameCallbackData_getFrameTimelineDeadline(
+ int64_t frameDeadline = AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos(
cbData, preferredFrameTimelineIndex);
int64_t frameTimeNanos = AChoreographerFrameCallbackData_getFrameTimeNanos(cbData);
// TODO(b/193273294): Remove when shared memory in use w/ expected present time always current.
diff --git a/location/java/android/location/Geocoder.java b/location/java/android/location/Geocoder.java
index e4a0d0c..a158344 100644
--- a/location/java/android/location/Geocoder.java
+++ b/location/java/android/location/Geocoder.java
@@ -298,6 +298,7 @@
* @param lowerLeftLongitude the longitude of the lower left corner of the bounding box
* @param upperRightLatitude the latitude of the upper right corner of the bounding box
* @param upperRightLongitude the longitude of the upper right corner of the bounding box
+ * @param listener a listener for receiving results
*
* @throws IllegalArgumentException if locationName is null
* @throws IllegalArgumentException if any latitude or longitude is invalid
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 21fc6ec..ffd5eaa 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2995,19 +2995,17 @@
void onModeChanged(@AudioMode int mode);
}
- private final Object mModeListenerLock = new Object();
/**
- * List of listeners for audio mode and their associated Executor.
- * List is lazy-initialized on first registration
+ * manages the OnModeChangedListener listeners and the ModeDispatcherStub
*/
- @GuardedBy("mModeListenerLock")
- private @Nullable ArrayList<ListenerInfo<OnModeChangedListener>> mModeListeners;
+ private final CallbackUtil.LazyListenerManager<OnModeChangedListener> mModeChangedListenerMgr =
+ new CallbackUtil.LazyListenerManager();
- @GuardedBy("mModeListenerLock")
- private ModeDispatcherStub mModeDispatcherStub;
- private final class ModeDispatcherStub extends IAudioModeDispatcher.Stub {
+ final class ModeDispatcherStub extends IAudioModeDispatcher.Stub
+ implements CallbackUtil.DispatcherStub {
+ @Override
public void register(boolean register) {
try {
if (register) {
@@ -3021,10 +3019,8 @@
}
@Override
- @SuppressLint("GuardedBy") // lock applied inside callListeners method
public void dispatchAudioModeChanged(int mode) {
- CallbackUtil.callListeners(mModeListeners, mModeListenerLock,
- (listener) -> listener.onModeChanged(mode));
+ mModeChangedListenerMgr.callListeners((listener) -> listener.onModeChanged(mode));
}
}
@@ -3037,15 +3033,8 @@
public void addOnModeChangedListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull OnModeChangedListener listener) {
- synchronized (mModeListenerLock) {
- final Pair<ArrayList<ListenerInfo<OnModeChangedListener>>, ModeDispatcherStub> res =
- CallbackUtil.addListener("addOnModeChangedListener",
- executor, listener, mModeListeners, mModeDispatcherStub,
- () -> new ModeDispatcherStub(),
- stub -> stub.register(true));
- mModeListeners = res.first;
- mModeDispatcherStub = res.second;
- }
+ mModeChangedListenerMgr.addListener(executor, listener, "addOnModeChangedListener",
+ () -> new ModeDispatcherStub());
}
/**
@@ -3054,14 +3043,7 @@
* @param listener
*/
public void removeOnModeChangedListener(@NonNull OnModeChangedListener listener) {
- synchronized (mModeListenerLock) {
- final Pair<ArrayList<ListenerInfo<OnModeChangedListener>>, ModeDispatcherStub> res =
- CallbackUtil.removeListener("removeOnModeChangedListener",
- listener, mModeListeners, mModeDispatcherStub,
- stub -> stub.register(false));
- mModeListeners = res.first;
- mModeDispatcherStub = res.second;
- }
+ mModeChangedListenerMgr.removeListener(listener, "removeOnModeChangedListener");
}
/**
@@ -7718,6 +7700,12 @@
}
/**
+ * manages the OnCommunicationDeviceChangedListener listeners and the
+ * CommunicationDeviceDispatcherStub
+ */
+ private final CallbackUtil.LazyListenerManager<OnCommunicationDeviceChangedListener>
+ mCommDeviceChangedListenerMgr = new CallbackUtil.LazyListenerManager();
+ /**
* Adds a listener for being notified of changes to the communication audio device.
* See {@link #setCommunicationDevice(AudioDeviceInfo)}.
* @param executor
@@ -7726,16 +7714,9 @@
public void addOnCommunicationDeviceChangedListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull OnCommunicationDeviceChangedListener listener) {
- synchronized (mCommDevListenerLock) {
- final Pair<ArrayList<ListenerInfo<OnCommunicationDeviceChangedListener>>,
- CommunicationDeviceDispatcherStub> res =
- CallbackUtil.addListener("addOnCommunicationDeviceChangedListener",
- executor, listener, mCommDevListeners, mCommDevDispatcherStub,
- () -> new CommunicationDeviceDispatcherStub(),
- stub -> stub.register(true));
- mCommDevListeners = res.first;
- mCommDevDispatcherStub = res.second;
- }
+ mCommDeviceChangedListenerMgr.addListener(
+ executor, listener, "addOnCommunicationDeviceChangedListener",
+ () -> new CommunicationDeviceDispatcherStub());
}
/**
@@ -7745,32 +7726,14 @@
*/
public void removeOnCommunicationDeviceChangedListener(
@NonNull OnCommunicationDeviceChangedListener listener) {
- synchronized (mCommDevListenerLock) {
- final Pair<ArrayList<ListenerInfo<OnCommunicationDeviceChangedListener>>,
- CommunicationDeviceDispatcherStub> res =
- CallbackUtil.removeListener("removeOnCommunicationDeviceChangedListener",
- listener, mCommDevListeners, mCommDevDispatcherStub,
- stub -> stub.register(false));
- mCommDevListeners = res.first;
- mCommDevDispatcherStub = res.second;
- }
+ mCommDeviceChangedListenerMgr.removeListener(listener,
+ "removeOnCommunicationDeviceChangedListener");
}
- private final Object mCommDevListenerLock = new Object();
- /**
- * List of listeners for preferred device for strategy and their associated Executor.
- * List is lazy-initialized on first registration
- */
- @GuardedBy("mCommDevListenerLock")
- private @Nullable
- ArrayList<ListenerInfo<OnCommunicationDeviceChangedListener>> mCommDevListeners;
-
- @GuardedBy("mCommDevListenerLock")
- private CommunicationDeviceDispatcherStub mCommDevDispatcherStub;
-
private final class CommunicationDeviceDispatcherStub
- extends ICommunicationDeviceDispatcher.Stub {
+ extends ICommunicationDeviceDispatcher.Stub implements CallbackUtil.DispatcherStub {
+ @Override
public void register(boolean register) {
try {
if (register) {
@@ -7784,10 +7747,9 @@
}
@Override
- @SuppressLint("GuardedBy") // lock applied inside callListeners method
public void dispatchCommunicationDeviceChanged(int portId) {
AudioDeviceInfo device = getDeviceForPortId(portId, GET_DEVICES_OUTPUTS);
- CallbackUtil.callListeners(mCommDevListeners, mCommDevListenerLock,
+ mCommDeviceChangedListenerMgr.callListeners(
(listener) -> listener.onCommunicationDeviceChanged(device));
}
}
diff --git a/media/java/android/media/CallbackUtil.java b/media/java/android/media/CallbackUtil.java
index ac39317..2b5fd25 100644
--- a/media/java/android/media/CallbackUtil.java
+++ b/media/java/android/media/CallbackUtil.java
@@ -18,11 +18,14 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.media.permission.ClearCallingIdentityContext;
import android.media.permission.SafeCloseable;
import android.util.Log;
import android.util.Pair;
+import com.android.internal.annotations.GuardedBy;
+
import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -221,4 +224,87 @@
}
}
+
+ /**
+ * Interface to be implemented by stub implementation for the events received from a server
+ * to the class managing the listener API.
+ * For an example see {@link AudioManager#ModeDispatcherStub} which registers with AudioService.
+ */
+ interface DispatcherStub {
+ /**
+ * Register/unregister the stub as a listener of the events to be forwarded to the listeners
+ * managed by LazyListenerManager.
+ * @param register true for registering, false to unregister
+ */
+ void register(boolean register);
+ }
+
+ /**
+ * Class to manage a list of listeners and their callback, and the associated stub which
+ * receives the events to be forwarded to the listeners.
+ * The list of listeners and the stub and its registration are lazily initialized and registered
+ * @param <T> the listener class
+ */
+ static class LazyListenerManager<T> {
+ private final Object mListenerLock = new Object();
+
+ @GuardedBy("mListenerLock")
+ private @Nullable ArrayList<ListenerInfo<T>> mListeners;
+
+ @GuardedBy("mListenerLock")
+ private @Nullable DispatcherStub mDispatcherStub;
+
+ LazyListenerManager() {
+ // nothing to initialize as instances of dispatcher and list of listeners
+ // are lazily initialized
+ }
+
+ /**
+ * Add a new listener / executor pair for the configured listener
+ * @param executor Executor for the callback
+ * @param listener the listener to register
+ * @param methodName the name of the method calling this utility method for easier to read
+ * exception messages
+ * @param newStub how to build a new instance of the stub receiving the events when the
+ * number of listeners goes from 0 to 1, not called until then.
+ */
+ void addListener(@NonNull Executor executor, @NonNull T listener, String methodName,
+ @NonNull java.util.function.Supplier<DispatcherStub> newStub) {
+ synchronized (mListenerLock) {
+ final Pair<ArrayList<ListenerInfo<T>>, DispatcherStub> res =
+ CallbackUtil.addListener(methodName,
+ executor, listener, mListeners, mDispatcherStub,
+ newStub,
+ stub -> stub.register(true));
+ mListeners = res.first;
+ mDispatcherStub = res.second;
+ }
+ }
+
+ /**
+ * Remove a previously registered listener
+ * @param listener the listener to unregister
+ * @param methodName the name of the method calling this utility method for easier to read
+ * exception messages
+ */
+ void removeListener(@NonNull T listener, String methodName) {
+ synchronized (mListenerLock) {
+ final Pair<ArrayList<ListenerInfo<T>>, DispatcherStub> res =
+ CallbackUtil.removeListener(methodName,
+ listener, mListeners, mDispatcherStub,
+ stub -> stub.register(false));
+ mListeners = res.first;
+ mDispatcherStub = res.second;
+ }
+ }
+
+ /**
+ * Call the registered listeners with the given callback method
+ * @param callback the listener method to invoke
+ */
+ @SuppressLint("GuardedBy") // lock applied inside callListeners method
+ void callListeners(CallbackMethod<T> callback) {
+ CallbackUtil.callListeners(mListeners, mListenerLock, callback);
+ }
+ }
}
diff --git a/media/java/android/media/Spatializer.java b/media/java/android/media/Spatializer.java
index c0793ec..f3f8bbe 100644
--- a/media/java/android/media/Spatializer.java
+++ b/media/java/android/media/Spatializer.java
@@ -29,7 +29,6 @@
import android.media.permission.SafeCloseable;
import android.os.RemoteException;
import android.util.Log;
-import android.util.Pair;
import com.android.internal.annotations.GuardedBy;
@@ -376,16 +375,8 @@
public void addOnSpatializerStateChangedListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull OnSpatializerStateChangedListener listener) {
- synchronized (mStateListenerLock) {
- final Pair<ArrayList<ListenerInfo<OnSpatializerStateChangedListener>>,
- SpatializerInfoDispatcherStub> res =
- CallbackUtil.addListener("addOnSpatializerStateChangedListener",
- executor, listener, mStateListeners, mInfoDispatcherStub,
- () -> new SpatializerInfoDispatcherStub(),
- stub -> stub.register(true));
- mStateListeners = res.first;
- mInfoDispatcherStub = res.second;
- }
+ mStateListenerMgr.addListener(executor, listener, "addOnSpatializerStateChangedListener",
+ () -> new SpatializerInfoDispatcherStub());
}
/**
@@ -396,15 +387,7 @@
*/
public void removeOnSpatializerStateChangedListener(
@NonNull OnSpatializerStateChangedListener listener) {
- synchronized (mStateListenerLock) {
- final Pair<ArrayList<ListenerInfo<OnSpatializerStateChangedListener>>,
- SpatializerInfoDispatcherStub> res =
- CallbackUtil.removeListener("removeOnSpatializerStateChangedListener",
- listener, mStateListeners, mInfoDispatcherStub,
- stub -> stub.register(false));
- mStateListeners = res.first;
- mInfoDispatcherStub = res.second;
- }
+ mStateListenerMgr.removeListener(listener, "removeOnSpatializerStateChangedListener");
}
/**
@@ -459,18 +442,16 @@
}
}
- private final Object mStateListenerLock = new Object();
/**
- * List of listeners for state listener and their associated Executor.
- * List is lazy-initialized on first registration
+ * manages the OnSpatializerStateChangedListener listeners and the
+ * SpatializerInfoDispatcherStub
*/
- @GuardedBy("mStateListenerLock")
- private @Nullable ArrayList<ListenerInfo<OnSpatializerStateChangedListener>> mStateListeners;
+ private final CallbackUtil.LazyListenerManager<OnSpatializerStateChangedListener>
+ mStateListenerMgr = new CallbackUtil.LazyListenerManager();
- @GuardedBy("mStateListenerLock")
- private @Nullable SpatializerInfoDispatcherStub mInfoDispatcherStub;
-
- private final class SpatializerInfoDispatcherStub extends ISpatializerCallback.Stub {
+ private final class SpatializerInfoDispatcherStub extends ISpatializerCallback.Stub
+ implements CallbackUtil.DispatcherStub {
+ @Override
public void register(boolean register) {
try {
if (register) {
@@ -486,7 +467,7 @@
@Override
@SuppressLint("GuardedBy") // lock applied inside callListeners method
public void dispatchSpatializerEnabledChanged(boolean enabled) {
- CallbackUtil.callListeners(mStateListeners, mStateListenerLock,
+ mStateListenerMgr.callListeners(
(listener) -> listener.onSpatializerEnabledChanged(
Spatializer.this, enabled));
}
@@ -494,7 +475,7 @@
@Override
@SuppressLint("GuardedBy") // lock applied inside callListeners method
public void dispatchSpatializerAvailableChanged(boolean available) {
- CallbackUtil.callListeners(mStateListeners, mStateListenerLock,
+ mStateListenerMgr.callListeners(
(listener) -> listener.onSpatializerAvailableChanged(
Spatializer.this, available));
}
@@ -612,16 +593,9 @@
public void addOnHeadTrackingModeChangedListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull OnHeadTrackingModeChangedListener listener) {
- synchronized (mHeadTrackingListenerLock) {
- final Pair<ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>>,
- SpatializerHeadTrackingDispatcherStub> res = CallbackUtil.addListener(
- "addOnHeadTrackingModeChangedListener", executor, listener,
- mHeadTrackingListeners, mHeadTrackingDispatcherStub,
- () -> new SpatializerHeadTrackingDispatcherStub(),
- stub -> stub.register(true));
- mHeadTrackingListeners = res.first;
- mHeadTrackingDispatcherStub = res.second;
- }
+ mHeadTrackingListenerMgr.addListener(executor, listener,
+ "addOnHeadTrackingModeChangedListener",
+ () -> new SpatializerHeadTrackingDispatcherStub());
}
/**
@@ -634,15 +608,8 @@
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
public void removeOnHeadTrackingModeChangedListener(
@NonNull OnHeadTrackingModeChangedListener listener) {
- synchronized (mHeadTrackingListenerLock) {
- final Pair<ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>>,
- SpatializerHeadTrackingDispatcherStub> res = CallbackUtil.removeListener(
- "removeOnHeadTrackingModeChangedListener", listener,
- mHeadTrackingListeners, mHeadTrackingDispatcherStub,
- stub -> stub.register(false));
- mHeadTrackingListeners = res.first;
- mHeadTrackingDispatcherStub = res.second;
- }
+ mHeadTrackingListenerMgr.removeListener(listener,
+ "removeOnHeadTrackingModeChangedListener");
}
/**
@@ -828,20 +795,17 @@
//-----------------------------------------------------------------------------
// head tracking callback management and stub
- private final Object mHeadTrackingListenerLock = new Object();
/**
- * List of listeners for head tracking mode listener and their associated Executor.
- * List is lazy-initialized on first registration
+ * manages the OnHeadTrackingModeChangedListener listeners and the
+ * SpatializerHeadTrackingDispatcherStub
*/
- @GuardedBy("mHeadTrackingListenerLock")
- private @Nullable ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>>
- mHeadTrackingListeners;
-
- @GuardedBy("mHeadTrackingListenerLock")
- private @Nullable SpatializerHeadTrackingDispatcherStub mHeadTrackingDispatcherStub;
+ private final CallbackUtil.LazyListenerManager<OnHeadTrackingModeChangedListener>
+ mHeadTrackingListenerMgr = new CallbackUtil.LazyListenerManager();
private final class SpatializerHeadTrackingDispatcherStub
- extends ISpatializerHeadTrackingModeCallback.Stub {
+ extends ISpatializerHeadTrackingModeCallback.Stub
+ implements CallbackUtil.DispatcherStub {
+ @Override
public void register(boolean register) {
try {
if (register) {
@@ -857,14 +821,14 @@
@Override
@SuppressLint("GuardedBy") // lock applied inside callListeners method
public void dispatchSpatializerActualHeadTrackingModeChanged(int mode) {
- CallbackUtil.callListeners(mHeadTrackingListeners, mHeadTrackingListenerLock,
+ mHeadTrackingListenerMgr.callListeners(
(listener) -> listener.onHeadTrackingModeChanged(Spatializer.this, mode));
}
@Override
@SuppressLint("GuardedBy") // lock applied inside callListeners method
public void dispatchSpatializerDesiredHeadTrackingModeChanged(int mode) {
- CallbackUtil.callListeners(mHeadTrackingListeners, mHeadTrackingListenerLock,
+ mHeadTrackingListenerMgr.callListeners(
(listener) -> listener.onDesiredHeadTrackingModeChanged(
Spatializer.this, mode));
}
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 52036b0..6420b4a 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -18,6 +18,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -27,6 +28,8 @@
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
+import android.media.AudioDeviceInfo;
+import android.media.AudioFormat.Encoding;
import android.media.PlaybackParams;
import android.media.tv.interactive.TvIAppManager;
import android.net.Uri;
@@ -60,6 +63,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -3221,6 +3225,16 @@
return false;
}
+ /**
+ * Override default audio sink from audio policy.
+ *
+ * @param audioType device type of the audio sink to override with.
+ * @param audioAddress device address of the audio sink to override with.
+ * @param samplingRate desired sampling rate. Use default when it's 0.
+ * @param channelMask desired channel mask. Use default when it's
+ * AudioFormat.CHANNEL_OUT_DEFAULT.
+ * @param format desired format. Use default when it's AudioFormat.ENCODING_DEFAULT.
+ */
public void overrideAudioSink(int audioType, String audioAddress, int samplingRate,
int channelMask, int format) {
try {
@@ -3230,5 +3244,27 @@
throw new RuntimeException(e);
}
}
+
+ /**
+ * Override default audio sink from audio policy.
+ *
+ * @param device {@link android.media.AudioDeviceInfo} to use.
+ * @param samplingRate desired sampling rate. Use default when it's 0.
+ * @param channelMask desired channel mask. Use default when it's
+ * AudioFormat.CHANNEL_OUT_DEFAULT.
+ * @param format desired format. Use default when it's AudioFormat.ENCODING_DEFAULT.
+ */
+ public void overrideAudioSink(@NonNull AudioDeviceInfo device,
+ @IntRange(from = 0) int samplingRate,
+ int channelMask, @Encoding int format) {
+ Objects.requireNonNull(device);
+ try {
+ mInterface.overrideAudioSink(
+ AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType()),
+ device.getAddress(), samplingRate, channelMask, format);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
}
diff --git a/native/android/choreographer.cpp b/native/android/choreographer.cpp
index deee5b1..fbd4b2e 100644
--- a/native/android/choreographer.cpp
+++ b/native/android/choreographer.cpp
@@ -71,11 +71,12 @@
const AChoreographerFrameCallbackData* data, size_t index) {
return AChoreographerFrameCallbackData_routeGetFrameTimelineVsyncId(data, index);
}
-int64_t AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentTime(
+int64_t AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentTimeNanos(
const AChoreographerFrameCallbackData* data, size_t index) {
- return AChoreographerFrameCallbackData_routeGetFrameTimelineExpectedPresentTime(data, index);
+ return AChoreographerFrameCallbackData_routeGetFrameTimelineExpectedPresentTimeNanos(data,
+ index);
}
-int64_t AChoreographerFrameCallbackData_getFrameTimelineDeadline(
+int64_t AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos(
const AChoreographerFrameCallbackData* data, size_t index) {
- return AChoreographerFrameCallbackData_routeGetFrameTimelineDeadline(data, index);
+ return AChoreographerFrameCallbackData_routeGetFrameTimelineDeadlineNanos(data, index);
}
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 3c1aa44..35c794e 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -34,8 +34,8 @@
AChoreographerFrameCallbackData_getFrameTimelinesLength; # introduced=33
AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex; # introduced=33
AChoreographerFrameCallbackData_getFrameTimelineVsyncId; # introduced=33
- AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentTime; # introduced=33
- AChoreographerFrameCallbackData_getFrameTimelineDeadline; # introduced=33
+ AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentTimeNanos; # introduced=33
+ AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos; # introduced=33
AConfiguration_copy;
AConfiguration_delete;
AConfiguration_diff;
diff --git a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java
index 7a239af..068074a 100644
--- a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java
+++ b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java
@@ -44,6 +44,7 @@
import java.io.PrintWriter;
import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
/** Basic fused location provider implementation. */
public class FusedLocationProvider extends LocationProviderBase {
@@ -69,6 +70,12 @@
private final BroadcastReceiver mUserChangeReceiver;
@GuardedBy("mLock")
+ boolean mGpsPresent;
+
+ @GuardedBy("mLock")
+ boolean mNlpPresent;
+
+ @GuardedBy("mLock")
private ProviderRequest mRequest;
@GuardedBy("mLock")
@@ -119,19 +126,28 @@
@Override
public void onFlush(OnFlushCompleteCallback callback) {
- OnFlushCompleteCallback wrapper = new OnFlushCompleteCallback() {
- private int mFlushCount = 2;
+ synchronized (mLock) {
+ AtomicInteger flushCount = new AtomicInteger(0);
+ if (mGpsPresent) {
+ flushCount.incrementAndGet();
+ }
+ if (mNlpPresent) {
+ flushCount.incrementAndGet();
+ }
- @Override
- public void onFlushComplete() {
- if (--mFlushCount == 0) {
+ OnFlushCompleteCallback wrapper = () -> {
+ if (flushCount.decrementAndGet() == 0) {
callback.onFlushComplete();
}
- }
- };
+ };
- mGpsListener.flush(wrapper);
- mNetworkListener.flush(wrapper);
+ if (mGpsPresent) {
+ mGpsListener.flush(wrapper);
+ }
+ if (mNlpPresent) {
+ mNetworkListener.flush(wrapper);
+ }
+ }
}
@Override
@@ -139,9 +155,19 @@
@GuardedBy("mLock")
private void updateRequirementsLocked() {
- long gpsInterval = mRequest.getQuality() < QUALITY_LOW_POWER ? mRequest.getIntervalMillis()
- : INTERVAL_DISABLED;
- long networkInterval = mRequest.getIntervalMillis();
+ // it's possible there might be race conditions on device start where a provider doesn't
+ // appear to be present yet, but once a provider is present it shouldn't go away.
+ if (!mGpsPresent) {
+ mGpsPresent = mLocationManager.hasProvider(GPS_PROVIDER);
+ }
+ if (!mNlpPresent) {
+ mNlpPresent = mLocationManager.hasProvider(NETWORK_PROVIDER);
+ }
+
+ long gpsInterval =
+ mGpsPresent && (!mNlpPresent || mRequest.getQuality() < QUALITY_LOW_POWER)
+ ? mRequest.getIntervalMillis() : INTERVAL_DISABLED;
+ long networkInterval = mNlpPresent ? mRequest.getIntervalMillis() : INTERVAL_DISABLED;
mGpsListener.resetProviderRequest(gpsInterval);
mNetworkListener.resetProviderRequest(networkInterval);
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index b266df5..fcf2282 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -49,6 +49,7 @@
"SettingsLibTwoTargetPreference",
"SettingsLibSettingsTransition",
"SettingsLibActivityEmbedding",
+ "SettingsLibButtonPreference",
],
// ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES
diff --git a/packages/SettingsLib/ButtonPreference/Android.bp b/packages/SettingsLib/ButtonPreference/Android.bp
new file mode 100644
index 0000000..39f804f
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/Android.bp
@@ -0,0 +1,23 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_library {
+ name: "SettingsLibButtonPreference",
+
+ srcs: ["src/**/*.java"],
+ resource_dirs: ["res"],
+
+ static_libs: [
+ "androidx.preference_preference",
+ "SettingsLibSettingsTheme",
+ ],
+
+ sdk_version: "system_current",
+ min_sdk_version: "21",
+}
diff --git a/packages/SettingsLib/ButtonPreference/AndroidManifest.xml b/packages/SettingsLib/ButtonPreference/AndroidManifest.xml
new file mode 100644
index 0000000..2d35c331
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.settingslib.widget">
+
+ <uses-sdk android:minSdkVersion="21" />
+
+</manifest>
diff --git a/packages/SettingsLib/ButtonPreference/res/color/settingslib_btn_colored_background_material.xml b/packages/SettingsLib/ButtonPreference/res/color/settingslib_btn_colored_background_material.xml
new file mode 100644
index 0000000..51ca4ac
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/color/settingslib_btn_colored_background_material.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Used for the text of a bordered colored button. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="?android:attr/disabledAlpha"
+ android:color="?android:attr/colorButtonNormal" />
+ <item android:color="?android:attr/colorAccent" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/color/settingslib_btn_colored_text_material.xml b/packages/SettingsLib/ButtonPreference/res/color/settingslib_btn_colored_text_material.xml
new file mode 100644
index 0000000..8dca4db
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/color/settingslib_btn_colored_text_material.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Used for the text of a bordered colored button. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="?android:attr/disabledAlpha"
+ android:color="?android:attr/textColorPrimary" />
+ <item android:color="?android:attr/textColorPrimaryInverse" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_dark.xml b/packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_dark.xml
new file mode 100644
index 0000000..1e930ea
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_dark.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:alpha="@dimen/settingslib_highlight_alpha_material_dark"
+ android:color="@android:color/white" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_light.xml b/packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_light.xml
new file mode 100644
index 0000000..378fc16
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_light.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:alpha="@dimen/settingslib_highlight_alpha_material_light"
+ android:color="@android:color/black" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_btn_colored_material.xml b/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_btn_colored_material.xml
new file mode 100644
index 0000000..bb0597d
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_btn_colored_material.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:insetLeft="4dp"
+ android:insetTop="6dp"
+ android:insetRight="4dp"
+ android:insetBottom="6dp">
+ <ripple android:color="?attr/colorControlHighlight">
+ <item>
+ <shape android:shape="rectangle"
+ android:tint="@color/settingslib_btn_colored_background_material">
+ <corners android:radius="2dp" />
+ <solid android:color="@android:color/white" />
+ <padding android:left="8dp"
+ android:top="4dp"
+ android:right="8dp"
+ android:bottom="4dp" />
+ </shape>
+ </item>
+ </ripple>
+</inset>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_button_layout.xml b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_button_layout.xml
new file mode 100644
index 0000000..1ff0990
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_button_layout.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <Button
+ android:id="@+id/settingslib_button"
+ android:drawablePadding="8dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:layout_marginStart="-4dp"
+ style="@style/SettingsLibButtonStyle" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/values-night/colors.xml b/packages/SettingsLib/ButtonPreference/res/values-night/colors.xml
new file mode 100644
index 0000000..6be7b93
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values-night/colors.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <!-- Material inverse ripple color, useful for inverted backgrounds. -->
+ <color name="settingslib_button_ripple">@color/settingslib_ripple_material_light</color>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/values-v23/styles.xml b/packages/SettingsLib/ButtonPreference/res/values-v23/styles.xml
new file mode 100644
index 0000000..202645f
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values-v23/styles.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+
+ <style name="SettingsLibButtonStyle" parent="android:Widget.Material.Button.Colored">
+ <item name="android:theme">@style/SettingsLibRoundedCornerThemeOverlay</item>
+ <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:textColor">@color/settingslib_btn_colored_text_material</item>
+ </style>
+</resources>
diff --git a/packages/SettingsLib/ButtonPreference/res/values-v28/styles.xml b/packages/SettingsLib/ButtonPreference/res/values-v28/styles.xml
new file mode 100644
index 0000000..d8c6ac3f
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values-v28/styles.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+
+ <style name="SettingsLibButtonStyle" parent="android:Widget.DeviceDefault.Button.Colored">
+ <item name="android:theme">@style/SettingsLibRoundedCornerThemeOverlay</item>
+ <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
+ <item name="android:textSize">16sp</item>
+ </style>
+</resources>
diff --git a/packages/SettingsLib/ButtonPreference/res/values-v31/styles.xml b/packages/SettingsLib/ButtonPreference/res/values-v31/styles.xml
new file mode 100644
index 0000000..12dcbbf
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values-v31/styles.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+
+ <style name="SettingsLibRoundedCornerThemeOverlay">
+ <item name="android:buttonCornerRadius">24dp</item>
+ <item name="android:paddingStart">16dp</item>
+ <item name="android:paddingEnd">16dp</item>
+ <item name="android:colorControlHighlight">@color/settingslib_button_ripple</item>
+ </style>
+</resources>
diff --git a/packages/SettingsLib/ButtonPreference/res/values/attrs.xml b/packages/SettingsLib/ButtonPreference/res/values/attrs.xml
new file mode 100644
index 0000000..9a4312a
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values/attrs.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <declare-styleable name="ButtonPreference">
+ <attr name="android:gravity" />
+ </declare-styleable>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/values/colors.xml b/packages/SettingsLib/ButtonPreference/res/values/colors.xml
new file mode 100644
index 0000000..45baeeb
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values/colors.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <!-- Material inverse ripple color, useful for inverted backgrounds. -->
+ <color name="settingslib_button_ripple">@color/settingslib_ripple_material_dark</color>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/values/dimens.xml b/packages/SettingsLib/ButtonPreference/res/values/dimens.xml
new file mode 100644
index 0000000..3d7831e
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <item name="settingslib_highlight_alpha_material_light" format="float" type="dimen">0.10</item>
+ <item name="settingslib_highlight_alpha_material_dark" format="float" type="dimen">0.10</item>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/values/styles.xml b/packages/SettingsLib/ButtonPreference/res/values/styles.xml
new file mode 100644
index 0000000..3963732
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values/styles.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+
+ <style name="SettingsLibRoundedCornerThemeOverlay">
+ <item name="android:paddingStart">16dp</item>
+ <item name="android:paddingEnd">16dp</item>
+ <item name="android:colorControlHighlight">@color/settingslib_button_ripple</item>
+ </style>
+
+ <style name="SettingsLibButtonStyle" parent="android:Widget.Material.Button">
+ <item name="android:theme">@style/SettingsLibRoundedCornerThemeOverlay</item>
+ <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:textColor">@color/settingslib_btn_colored_text_material</item>
+ <item name="android:background">@drawable/settingslib_btn_colored_material</item>
+ </style>
+</resources>
diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
new file mode 100644
index 0000000..56d2967
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
@@ -0,0 +1,197 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+import androidx.annotation.GravityInt;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+/**
+ * A preference handled a button
+ */
+public class ButtonPreference extends Preference {
+
+ private static final int ICON_SIZE = 24;
+
+ private View.OnClickListener mClickListener;
+ private Button mButton;
+ private CharSequence mTitle;
+ private Drawable mIcon;
+ @GravityInt
+ private int mGravity;
+
+ /**
+ * Constructs a new LayoutPreference with the given context's theme, the supplied
+ * attribute set, and default style attribute.
+ *
+ * @param context The Context the view is running in, through which it can
+ * access the current theme, resources, etc.
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default
+ * values for the view. Can be 0 to not look for
+ * defaults.
+ */
+ public ButtonPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(context, attrs, defStyleAttr);
+ }
+
+ /**
+ * Constructs a new LayoutPreference with the given context's theme and the supplied
+ * attribute set.
+ *
+ * @param context The Context the view is running in, through which it can
+ * access the current theme, resources, etc.
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ */
+ public ButtonPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, 0 /* defStyleAttr */);
+ }
+
+ /**
+ * Constructs a new LayoutPreference with the given context's theme and a customized view.
+ *
+ * @param context The Context the view is running in, through which it can
+ * access the current theme, resources, etc.
+ */
+ public ButtonPreference(Context context) {
+ this(context, null);
+ }
+
+ private void init(Context context, AttributeSet attrs, int defStyleAttr) {
+ setLayoutResource(R.layout.settingslib_button_layout);
+
+ if (attrs != null) {
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ androidx.preference.R.styleable.Preference, defStyleAttr,
+ 0 /*defStyleRes*/);
+ mTitle = a.getText(
+ androidx.preference.R.styleable.Preference_android_title);
+ mIcon = a.getDrawable(
+ androidx.preference.R.styleable.Preference_android_icon);
+ a.recycle();
+
+ a = context.obtainStyledAttributes(attrs,
+ R.styleable.ButtonPreference, defStyleAttr,
+ 0 /*defStyleRes*/);
+ mGravity = a.getInt(R.styleable.ButtonPreference_android_gravity, Gravity.START);
+ a.recycle();
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ mButton = (Button) holder.findViewById(R.id.settingslib_button);
+ setTitle(mTitle);
+ setIcon(mIcon);
+ setGravity(mGravity);
+ setOnClickListener(mClickListener);
+
+ if (mButton != null) {
+ final boolean selectable = isSelectable();
+ mButton.setFocusable(selectable);
+ mButton.setClickable(selectable);
+
+ mButton.setEnabled(isEnabled());
+ }
+
+ holder.setDividerAllowedAbove(false);
+ holder.setDividerAllowedBelow(false);
+ }
+
+ @Override
+ public void setTitle(CharSequence title) {
+ mTitle = title;
+ if (mButton != null) {
+ mButton.setText(title);
+ }
+ }
+
+ @Override
+ public void setIcon(Drawable icon) {
+ mIcon = icon;
+ if (mButton == null || icon == null) {
+ return;
+ }
+ //get pixel from dp
+ int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, ICON_SIZE,
+ getContext().getResources().getDisplayMetrics());
+ icon.setBounds(0, 0, size, size);
+
+ //set drawableStart
+ mButton.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null/* top */, null/* end */,
+ null/* bottom */);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ if (mButton != null) {
+ mButton.setEnabled(enabled);
+ }
+ }
+
+ /**
+ * Return Button
+ */
+ public Button getButton() {
+ return mButton;
+ }
+
+
+ /**
+ * Set a listener for button click
+ */
+ public void setOnClickListener(View.OnClickListener listener) {
+ mClickListener = listener;
+ if (mButton != null) {
+ mButton.setOnClickListener(listener);
+ }
+ }
+
+ /**
+ * Set the gravity of button
+ *
+ * @param gravity The {@link Gravity} supported CENTER_HORIZONTAL
+ * and the default value is START
+ */
+ public void setGravity(@GravityInt int gravity) {
+ if (gravity == Gravity.CENTER_HORIZONTAL || gravity == Gravity.CENTER_VERTICAL
+ || gravity == Gravity.CENTER) {
+ mGravity = Gravity.CENTER_HORIZONTAL;
+ } else {
+ mGravity = Gravity.START;
+ }
+
+ if (mButton != null) {
+ LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mButton.getLayoutParams();
+ lp.gravity = mGravity;
+ mButton.setLayoutParams(lp);
+ }
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ButtonPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ButtonPreferenceTest.java
new file mode 100644
index 0000000..625b214
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ButtonPreferenceTest.java
@@ -0,0 +1,166 @@
+/*
+ * 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.widget;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+import androidx.preference.PreferenceViewHolder;
+import androidx.preference.R;
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.shadows.ShadowDrawable;
+
+@RunWith(RobolectricTestRunner.class)
+public class ButtonPreferenceTest {
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private ButtonPreference mPreference;
+ private PreferenceViewHolder mHolder;
+
+ private boolean mClickListenerCalled;
+ private final View.OnClickListener mClickListener = v -> mClickListenerCalled = true;
+
+ @Before
+ public void setUp() {
+ mClickListenerCalled = false;
+ mPreference = new ButtonPreference(mContext);
+ setUpViewHolder();
+ }
+
+ @Test
+ public void onBindViewHolder_whenTitleSet_shouldSetButtonText() {
+ final String testTitle = "Test title";
+ mPreference.setTitle(testTitle);
+
+ mPreference.onBindViewHolder(mHolder);
+
+ final Button button = mPreference.getButton();
+ assertThat(button.getText().toString()).isEqualTo(testTitle);
+ }
+
+ @Test
+ public void onBindViewHolder_whenIconSet_shouldSetIcon() {
+ mPreference.setIcon(R.drawable.settingslib_ic_cross);
+
+ mPreference.onBindViewHolder(mHolder);
+
+ final Button button = mPreference.getButton();
+ final Drawable icon = button.getCompoundDrawablesRelative()[0];
+ final ShadowDrawable shadowDrawable = shadowOf(icon);
+ assertThat(shadowDrawable.getCreatedFromResId()).isEqualTo(R.drawable.settingslib_ic_cross);
+ }
+
+ @Test
+ public void onBindViewHolder_setEnable_shouldSetButtonEnabled() {
+ mPreference.setEnabled(true);
+
+ mPreference.onBindViewHolder(mHolder);
+
+ final Button button = mPreference.getButton();
+ assertThat(button.isEnabled()).isTrue();
+ }
+
+ @Test
+ public void onBindViewHolder_setDisable_shouldSetButtonDisabled() {
+ mPreference.setEnabled(false);
+
+ mPreference.onBindViewHolder(mHolder);
+
+ final Button button = mPreference.getButton();
+ assertThat(button.isEnabled()).isFalse();
+ }
+
+ @Test
+ public void onBindViewHolder_default_shouldReturnButtonGravityStart() {
+ mPreference.onBindViewHolder(mHolder);
+
+ final Button button = mPreference.getButton();
+ final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) button.getLayoutParams();
+ assertThat(lp.gravity).isEqualTo(Gravity.START);
+ }
+
+ @Test
+ public void onBindViewHolder_setGravityStart_shouldReturnButtonGravityStart() {
+ mPreference.setGravity(Gravity.START);
+
+ mPreference.onBindViewHolder(mHolder);
+
+ final Button button = mPreference.getButton();
+ final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) button.getLayoutParams();
+ assertThat(lp.gravity).isEqualTo(Gravity.START);
+ }
+
+ @Test
+ public void onBindViewHolder_setGravityCenter_shouldReturnButtonGravityCenterHorizontal() {
+ mPreference.setGravity(Gravity.CENTER_HORIZONTAL);
+
+ mPreference.onBindViewHolder(mHolder);
+
+ final Button button = mPreference.getButton();
+ final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) button.getLayoutParams();
+ assertThat(lp.gravity).isEqualTo(Gravity.CENTER_HORIZONTAL);
+
+ mPreference.setGravity(Gravity.CENTER_VERTICAL);
+ mPreference.onBindViewHolder(mHolder);
+ assertThat(lp.gravity).isEqualTo(Gravity.CENTER_HORIZONTAL);
+
+ mPreference.setGravity(Gravity.CENTER);
+ mPreference.onBindViewHolder(mHolder);
+ assertThat(lp.gravity).isEqualTo(Gravity.CENTER_HORIZONTAL);
+ }
+
+ @Test
+ public void onBindViewHolder_setUnsupportedGravity_shouldReturnButtonGravityStart() {
+ mPreference.setGravity(Gravity.END);
+
+ mPreference.onBindViewHolder(mHolder);
+
+ final Button button = mPreference.getButton();
+ final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) button.getLayoutParams();
+ assertThat(lp.gravity).isEqualTo(Gravity.START);
+ }
+
+ @Test
+ public void setButtonOnClickListener_setsClickListener() {
+ mPreference.setOnClickListener(mClickListener);
+
+ mPreference.onBindViewHolder(mHolder);
+ final Button button = mPreference.getButton();
+ button.callOnClick();
+
+ assertThat(mClickListenerCalled).isTrue();
+ }
+
+ private void setUpViewHolder() {
+ final View rootView =
+ View.inflate(mContext, mPreference.getLayoutResource(), null /* parent */);
+ mHolder = PreferenceViewHolder.createInstanceForTests(rootView);
+ }
+}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index a741514..e9e85f1 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -855,6 +855,12 @@
android:singleUser="true"
android:permission="android.permission.BIND_DREAM_SERVICE" />
+ <!-- Service for external clients to do media transfer -->
+ <!-- TODO(b/203800643): Export and guard with a permission. -->
+ <service
+ android:name=".media.taptotransfer.sender.MediaTttSenderService"
+ />
+
<receiver
android:name=".tuner.TunerService$ClearReceiver"
android:exported="false">
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 2b6c9f5..08fb2c6 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2152,8 +2152,8 @@
<!--- ****** Media tap-to-transfer ****** -->
<!-- Text for a button to undo the media transfer. [CHAR LIMIT=20] -->
<string name="media_transfer_undo">Undo</string>
- <!-- Text to ask the user to move their device closer to a different device (deviceName) in order to play music on the different device. [CHAR LIMIT=75] -->
- <string name="media_move_closer_to_transfer">Move closer to play on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string>
+ <!-- Text to ask the user to move their device closer to a different device (deviceName) in order to play media on the different device. [CHAR LIMIT=75] -->
+ <string name="media_move_closer_to_start_cast">Move closer to play on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string>
<!-- Text informing the user that their media is now playing on a different device (deviceName). [CHAR LIMIT=50] -->
<string name="media_transfer_playing">Playing on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string>
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index d172006..3cf5bc1 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -49,9 +49,12 @@
"PluginCoreLib",
"androidx.dynamicanimation_dynamicanimation",
"androidx.concurrent_concurrent-futures",
+ "dagger2",
+ "jsr330",
],
java_version: "1.8",
min_sdk_version: "current",
+ plugins: ["dagger2-compiler"],
}
java_library {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Main.java b/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Main.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Main.java
rename to packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Main.java
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
new file mode 100644
index 0000000..861a4ed
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.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 com.android.systemui.shared.mediattt;
+
+parcelable DeviceInfo;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.kt
new file mode 100644
index 0000000..d41aaf3
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.mediattt
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * Represents a device that can send or receive media. Includes any device information necessary for
+ * SysUI to display an informative chip to the user.
+ */
+class DeviceInfo(val name: String) : Parcelable {
+ constructor(parcel: Parcel) : this(parcel.readString())
+
+ override fun writeToParcel(dest: Parcel?, flags: Int) {
+ dest?.writeString(name)
+ }
+
+ override fun describeContents() = 0
+
+ override fun toString() = "name: $name"
+
+ companion object CREATOR : Parcelable.Creator<DeviceInfo> {
+ override fun createFromParcel(parcel: Parcel) = DeviceInfo(parcel)
+ override fun newArray(size: Int) = arrayOfNulls<DeviceInfo?>(size)
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl
new file mode 100644
index 0000000..484791d
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.mediattt;
+
+import android.media.MediaRoute2Info;
+import com.android.systemui.shared.mediattt.DeviceInfo;
+
+/**
+ * A callback interface that can be invoked to trigger media transfer events on System UI.
+ *
+ * This interface is for the *sender* device, which is the device currently playing media. This
+ * sender device can transfer the media to a different device, called the receiver.
+ *
+ * System UI will implement this interface and other services will invoke it.
+ */
+interface IDeviceSenderCallback {
+ /**
+ * Invoke to notify System UI that this device (the sender) is close to a receiver device, so
+ * the user can potentially *start* a cast to the receiver device if the user moves their device
+ * a bit closer.
+ *
+ * Important notes:
+ * - When this callback triggers, the device is close enough to inform the user that
+ * transferring is an option, but the device is *not* close enough to actually initiate a
+ * transfer yet.
+ * - This callback is for *starting* a cast. It should be used when this device is currently
+ * playing media locally and the media should be transferred to be played on the receiver
+ * device instead.
+ */
+ oneway void closeToReceiverToStartCast(
+ in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
index 8d26ddd..618d2d2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
@@ -183,8 +183,8 @@
}
// Make wallpaper visible immediately since launcher apparently won't do this.
for (int i = wallpapersCompat.length - 1; i >= 0; --i) {
- t.show(wallpapersCompat[i].leash.getSurfaceControl());
- t.setAlpha(wallpapersCompat[i].leash.getSurfaceControl(), 1.f);
+ t.show(wallpapersCompat[i].leash);
+ t.setAlpha(wallpapersCompat[i].leash, 1.f);
}
} else {
if (launcherTask != null) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index 4ec65d8..72e3e18 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -57,7 +57,7 @@
public final int activityType;
public final int taskId;
- public final SurfaceControlCompat leash;
+ public final SurfaceControl leash;
public final boolean isTranslucent;
public final Rect clipRect;
public final int prefixOrderIndex;
@@ -82,7 +82,7 @@
public RemoteAnimationTargetCompat(RemoteAnimationTarget app) {
taskId = app.taskId;
mode = app.mode;
- leash = new SurfaceControlCompat(app.leash);
+ leash = app.leash;
isTranslucent = app.isTranslucent;
clipRect = app.clipRect;
position = app.position;
@@ -119,7 +119,7 @@
public RemoteAnimationTarget unwrap() {
return new RemoteAnimationTarget(
- taskId, mode, leash.getSurfaceControl(), isTranslucent, clipRect, contentInsets,
+ taskId, mode, leash, isTranslucent, clipRect, contentInsets,
prefixOrderIndex, position, localBounds, screenSpaceBounds, windowConfiguration,
isNotInRecents, mStartLeash, startBounds, taskInfo, allowEnterPip, windowType
);
@@ -211,7 +211,7 @@
mode = newModeToLegacyMode(change.getMode());
// TODO: once we can properly sync transactions across process, then get rid of this leash.
- leash = new SurfaceControlCompat(createLeash(info, change, order, t));
+ leash = createLeash(info, change, order, t);
isTranslucent = (change.getFlags() & TransitionInfo.FLAG_TRANSLUCENT) != 0
|| (change.getFlags() & TransitionInfo.FLAG_SHOW_WALLPAPER) != 0;
@@ -273,7 +273,7 @@
info.getChanges().size() - i, info, t));
if (leashMap == null) continue;
leashMap.put(info.getChanges().get(i).getLeash(),
- out.get(out.size() - 1).leash.mSurfaceControl);
+ out.get(out.size() - 1).leash);
}
return out.toArray(new RemoteAnimationTargetCompat[out.size()]);
}
@@ -282,8 +282,8 @@
* @see SurfaceControl#release()
*/
public void release() {
- if (leash.mSurfaceControl != null) {
- leash.mSurfaceControl.release();
+ if (leash != null) {
+ leash.release();
}
if (mStartLeash != null) {
mStartLeash.release();
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index 2d5080e..2ae32c7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -149,7 +149,7 @@
}
// Also make all the wallpapers opaque since we want the visible from the start
for (int i = wallpapers.length - 1; i >= 0; --i) {
- t.setAlpha(wallpapers[i].leash.mSurfaceControl, 1);
+ t.setAlpha(wallpapers[i].leash, 1);
}
t.apply();
mRecentsSession.setup(controller, info, finishedCallback, pausingTasks, pipTask,
@@ -267,10 +267,10 @@
// We are receiving new opening tasks, so convert to onTasksAppeared.
final RemoteAnimationTargetCompat target = new RemoteAnimationTargetCompat(
openingTasks.get(i), layer, info, t);
- mLeashMap.put(mOpeningLeashes.get(i), target.leash.mSurfaceControl);
- t.reparent(target.leash.mSurfaceControl, mInfo.getRootLeash());
- t.setLayer(target.leash.mSurfaceControl, layer);
- t.hide(target.leash.mSurfaceControl);
+ mLeashMap.put(mOpeningLeashes.get(i), target.leash);
+ t.reparent(target.leash, mInfo.getRootLeash());
+ t.setLayer(target.leash, layer);
+ t.hide(target.leash);
targets[i] = target;
}
t.apply();
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceControlCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceControlCompat.java
deleted file mode 100644
index acc6913..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceControlCompat.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.shared.system;
-
-import android.view.SurfaceControl;
-import android.view.View;
-import android.view.ViewRootImpl;
-
-/**
- * TODO: Remove this class
- */
-public class SurfaceControlCompat {
- final SurfaceControl mSurfaceControl;
-
- public SurfaceControlCompat(SurfaceControl surfaceControl) {
- mSurfaceControl = surfaceControl;
- }
-
- public SurfaceControlCompat(View v) {
- ViewRootImpl viewRootImpl = v.getViewRootImpl();
- mSurfaceControl = viewRootImpl != null
- ? viewRootImpl.getSurfaceControl()
- : null;
- }
-
- public boolean isValid() {
- return mSurfaceControl != null && mSurfaceControl.isValid();
- }
-
- public SurfaceControl getSurfaceControl() {
- return mSurfaceControl;
- }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
index e281914..30c062b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
@@ -205,13 +205,6 @@
/**
* @param surface The surface to modify.
*/
- public Builder(SurfaceControlCompat surface) {
- this(surface.mSurfaceControl);
- }
-
- /**
- * @param surface The surface to modify.
- */
public Builder(SurfaceControl surface) {
this.surface = surface;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
index c043fba..43a882a5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
@@ -35,70 +35,69 @@
mTransaction.apply();
}
- public TransactionCompat show(SurfaceControlCompat surfaceControl) {
- mTransaction.show(surfaceControl.mSurfaceControl);
+ public TransactionCompat show(SurfaceControl surfaceControl) {
+ mTransaction.show(surfaceControl);
return this;
}
- public TransactionCompat hide(SurfaceControlCompat surfaceControl) {
- mTransaction.hide(surfaceControl.mSurfaceControl);
+ public TransactionCompat hide(SurfaceControl surfaceControl) {
+ mTransaction.hide(surfaceControl);
return this;
}
- public TransactionCompat setPosition(SurfaceControlCompat surfaceControl, float x, float y) {
- mTransaction.setPosition(surfaceControl.mSurfaceControl, x, y);
+ public TransactionCompat setPosition(SurfaceControl surfaceControl, float x, float y) {
+ mTransaction.setPosition(surfaceControl, x, y);
return this;
}
- public TransactionCompat setSize(SurfaceControlCompat surfaceControl, int w, int h) {
- mTransaction.setBufferSize(surfaceControl.mSurfaceControl, w, h);
+ public TransactionCompat setSize(SurfaceControl surfaceControl, int w, int h) {
+ mTransaction.setBufferSize(surfaceControl, w, h);
return this;
}
- public TransactionCompat setLayer(SurfaceControlCompat surfaceControl, int z) {
- mTransaction.setLayer(surfaceControl.mSurfaceControl, z);
+ public TransactionCompat setLayer(SurfaceControl surfaceControl, int z) {
+ mTransaction.setLayer(surfaceControl, z);
return this;
}
- public TransactionCompat setAlpha(SurfaceControlCompat surfaceControl, float alpha) {
- mTransaction.setAlpha(surfaceControl.mSurfaceControl, alpha);
+ public TransactionCompat setAlpha(SurfaceControl surfaceControl, float alpha) {
+ mTransaction.setAlpha(surfaceControl, alpha);
return this;
}
- public TransactionCompat setOpaque(SurfaceControlCompat surfaceControl, boolean opaque) {
- mTransaction.setOpaque(surfaceControl.mSurfaceControl, opaque);
+ public TransactionCompat setOpaque(SurfaceControl surfaceControl, boolean opaque) {
+ mTransaction.setOpaque(surfaceControl, opaque);
return this;
}
- public TransactionCompat setMatrix(SurfaceControlCompat surfaceControl, float dsdx, float dtdx,
+ public TransactionCompat setMatrix(SurfaceControl surfaceControl, float dsdx, float dtdx,
float dtdy, float dsdy) {
- mTransaction.setMatrix(surfaceControl.mSurfaceControl, dsdx, dtdx, dtdy, dsdy);
+ mTransaction.setMatrix(surfaceControl, dsdx, dtdx, dtdy, dsdy);
return this;
}
- public TransactionCompat setMatrix(SurfaceControlCompat surfaceControl, Matrix matrix) {
- mTransaction.setMatrix(surfaceControl.mSurfaceControl, matrix, mTmpValues);
+ public TransactionCompat setMatrix(SurfaceControl surfaceControl, Matrix matrix) {
+ mTransaction.setMatrix(surfaceControl, matrix, mTmpValues);
return this;
}
- public TransactionCompat setWindowCrop(SurfaceControlCompat surfaceControl, Rect crop) {
- mTransaction.setWindowCrop(surfaceControl.mSurfaceControl, crop);
+ public TransactionCompat setWindowCrop(SurfaceControl surfaceControl, Rect crop) {
+ mTransaction.setWindowCrop(surfaceControl, crop);
return this;
}
- public TransactionCompat setCornerRadius(SurfaceControlCompat surfaceControl, float radius) {
- mTransaction.setCornerRadius(surfaceControl.mSurfaceControl, radius);
+ public TransactionCompat setCornerRadius(SurfaceControl surfaceControl, float radius) {
+ mTransaction.setCornerRadius(surfaceControl, radius);
return this;
}
- public TransactionCompat setBackgroundBlurRadius(SurfaceControlCompat surfaceControl,
- int radius) {
- mTransaction.setBackgroundBlurRadius(surfaceControl.mSurfaceControl, radius);
+ public TransactionCompat setBackgroundBlurRadius(SurfaceControl surfaceControl, int radius) {
+ mTransaction.setBackgroundBlurRadius(surfaceControl, radius);
return this;
}
- public TransactionCompat setColor(SurfaceControlCompat surfaceControl, float[] color) {
- mTransaction.setColor(surfaceControl.mSurfaceControl, color);
+ public TransactionCompat setColor(SurfaceControl surfaceControl, float[] color) {
+ mTransaction.setColor(surfaceControl, color);
return this;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedComponent.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
new file mode 100644
index 0000000..ac62cf9
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedComponent.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.systemui.unfold
+
+import android.content.ContentResolver
+import android.content.Context
+import android.hardware.SensorManager
+import android.hardware.devicestate.DeviceStateManager
+import android.os.Handler
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
+import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix
+import dagger.BindsInstance
+import dagger.Component
+import java.util.Optional
+import java.util.concurrent.Executor
+import javax.inject.Singleton
+
+/**
+ * Provides [UnfoldTransitionProgressProvider]. The [Optional] is empty when the transition
+ * animation is disabled.
+ *
+ * This component is meant to be used for places that don't use dagger. By providing those
+ * parameters to the factory, all dagger objects are correctly instantiated. See
+ * [createUnfoldTransitionProgressProvider] for an example.
+ */
+@Singleton
+@Component(modules = [UnfoldSharedModule::class])
+internal interface UnfoldSharedComponent {
+
+ @Component.Factory
+ interface Factory {
+ fun create(
+ @BindsInstance context: Context,
+ @BindsInstance config: UnfoldTransitionConfig,
+ @BindsInstance screenStatusProvider: ScreenStatusProvider,
+ @BindsInstance deviceStateManager: DeviceStateManager,
+ @BindsInstance sensorManager: SensorManager,
+ @BindsInstance @Main handler: Handler,
+ @BindsInstance @Main executor: Executor,
+ @BindsInstance @UnfoldTransitionATracePrefix tracingTagPrefix: String,
+ @BindsInstance contentResolver: ContentResolver = context.contentResolver
+ ): UnfoldSharedComponent
+ }
+
+ val unfoldTransitionProvider: Optional<UnfoldTransitionProgressProvider>
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedModule.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedModule.kt
new file mode 100644
index 0000000..23e4c97
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedModule.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.unfold
+
+import android.hardware.SensorManager
+import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider
+import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider
+import com.android.systemui.unfold.updates.DeviceFoldStateProvider
+import com.android.systemui.unfold.updates.FoldStateProvider
+import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider
+import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
+import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider
+import com.android.systemui.unfold.util.ATraceLoggerTransitionProgressListener
+import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider
+import dagger.Module
+import dagger.Provides
+import java.util.Optional
+import javax.inject.Singleton
+
+@Module
+class UnfoldSharedModule {
+ @Provides
+ @Singleton
+ fun unfoldTransitionProgressProvider(
+ config: UnfoldTransitionConfig,
+ scaleAwareProviderFactory: ScaleAwareTransitionProgressProvider.Factory,
+ tracingListener: ATraceLoggerTransitionProgressListener,
+ foldStateProvider: FoldStateProvider
+ ): Optional<UnfoldTransitionProgressProvider> =
+ if (!config.isEnabled) {
+ Optional.empty()
+ } else {
+ val baseProgressProvider =
+ if (config.isHingeAngleEnabled) {
+ PhysicsBasedUnfoldTransitionProgressProvider(foldStateProvider)
+ } else {
+ FixedTimingTransitionProgressProvider(foldStateProvider)
+ }
+ Optional.of(
+ scaleAwareProviderFactory.wrap(baseProgressProvider).apply {
+ // Always present callback that logs animation beginning and end.
+ addCallback(tracingListener)
+ })
+ }
+
+ @Provides
+ @Singleton
+ fun provideFoldStateProvider(
+ deviceFoldStateProvider: DeviceFoldStateProvider
+ ): FoldStateProvider = deviceFoldStateProvider
+
+ @Provides
+ fun hingeAngleProvider(
+ config: UnfoldTransitionConfig,
+ sensorManager: SensorManager
+ ): HingeAngleProvider =
+ if (config.isHingeAngleEnabled) {
+ HingeSensorAngleProvider(sensorManager)
+ } else {
+ EmptyHingeAngleProvider
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
index 953b0e0..d5d6362 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
@@ -23,22 +23,16 @@
import android.os.Handler
import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig
import com.android.systemui.unfold.config.UnfoldTransitionConfig
-import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider
-import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider
-import com.android.systemui.unfold.updates.DeviceFoldStateProvider
-import com.android.systemui.unfold.updates.FoldStateProvider
-import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider
-import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
-import com.android.systemui.unfold.util.ATraceLoggerTransitionProgressListener
-import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider
import java.util.concurrent.Executor
/**
* Factory for [UnfoldTransitionProgressProvider].
*
- * This is needed as Launcher has to create the object manually. Sysui create it using dagger (see
- * [UnfoldTransitionModule]).
+ * This is needed as Launcher has to create the object manually. If dagger is available, this object
+ * is provided in [UnfoldSharedModule].
+ *
+ * This should **never** be called from sysui, as the object is already provided in that process.
*/
fun createUnfoldTransitionProgressProvider(
context: Context,
@@ -49,62 +43,21 @@
mainHandler: Handler,
mainExecutor: Executor,
tracingTagPrefix: String
-): UnfoldTransitionProgressProvider {
-
- if (!config.isEnabled) {
- throw IllegalStateException(
- "Trying to create " +
- "UnfoldTransitionProgressProvider when the transition is disabled")
- }
-
- val foldStateProvider =
- createFoldStateProvider(
+): UnfoldTransitionProgressProvider =
+ DaggerUnfoldSharedComponent.factory()
+ .create(
context,
config,
screenStatusProvider,
deviceStateManager,
sensorManager,
mainHandler,
- mainExecutor)
-
- val unfoldTransitionProgressProvider =
- if (config.isHingeAngleEnabled) {
- PhysicsBasedUnfoldTransitionProgressProvider(foldStateProvider)
- } else {
- FixedTimingTransitionProgressProvider(foldStateProvider)
- }
-
- return ScaleAwareTransitionProgressProvider(
- unfoldTransitionProgressProvider, context.contentResolver)
- .apply {
- // Always present callback that logs animation beginning and end.
- addCallback(ATraceLoggerTransitionProgressListener(tracingTagPrefix))
- }
-}
-
-fun createFoldStateProvider(
- context: Context,
- config: UnfoldTransitionConfig,
- screenStatusProvider: ScreenStatusProvider,
- deviceStateManager: DeviceStateManager,
- sensorManager: SensorManager,
- mainHandler: Handler,
- mainExecutor: Executor
-): FoldStateProvider {
- val hingeAngleProvider =
- if (config.isHingeAngleEnabled) {
- HingeSensorAngleProvider(sensorManager)
- } else {
- EmptyHingeAngleProvider()
- }
-
- return DeviceFoldStateProvider(
- context,
- hingeAngleProvider,
- screenStatusProvider,
- deviceStateManager,
- mainExecutor,
- mainHandler)
-}
+ mainExecutor,
+ tracingTagPrefix)
+ .unfoldTransitionProvider
+ .orElse(null)
+ ?: throw IllegalStateException(
+ "Trying to create " +
+ "UnfoldTransitionProgressProvider when the transition is disabled")
fun createConfig(context: Context): UnfoldTransitionConfig = ResourceUnfoldTransitionConfig(context)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index cd1ea21..204ae09 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -22,6 +22,7 @@
import android.util.Log
import androidx.annotation.VisibleForTesting
import androidx.core.util.Consumer
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
import com.android.systemui.unfold.updates.hinge.FULLY_CLOSED_DEGREES
@@ -29,14 +30,17 @@
import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import java.util.concurrent.Executor
+import javax.inject.Inject
-class DeviceFoldStateProvider(
+class DeviceFoldStateProvider
+@Inject
+constructor(
context: Context,
private val hingeAngleProvider: HingeAngleProvider,
private val screenStatusProvider: ScreenStatusProvider,
private val deviceStateManager: DeviceStateManager,
- private val mainExecutor: Executor,
- private val handler: Handler
+ @Main private val mainExecutor: Executor,
+ @Main private val handler: Handler
) : FoldStateProvider {
private val outputListeners: MutableList<FoldUpdatesListener> = mutableListOf()
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt
index 9b58b1f..4ca1a53 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt
@@ -2,7 +2,7 @@
import androidx.core.util.Consumer
-internal class EmptyHingeAngleProvider : HingeAngleProvider {
+internal object EmptyHingeAngleProvider : HingeAngleProvider {
override fun start() {
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt
similarity index 76%
rename from packages/SystemUI/shared/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressProvider.kt
rename to packages/SystemUI/shared/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt
index f3eeb32..1574c8d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt
@@ -2,6 +2,8 @@
import android.os.Trace
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import javax.inject.Inject
+import javax.inject.Qualifier
/**
* Listener that logs start and end of the fold-unfold transition.
@@ -9,7 +11,10 @@
* [tracePrefix] arg helps in differentiating those. Currently, this is expected to be logged twice
* for each fold/unfold: in (1) systemui and (2) launcher process.
*/
-class ATraceLoggerTransitionProgressListener(tracePrefix: String) : TransitionProgressListener {
+class ATraceLoggerTransitionProgressListener
+@Inject
+internal constructor(@UnfoldTransitionATracePrefix tracePrefix: String) :
+ TransitionProgressListener {
private val traceName = "$tracePrefix#$UNFOLD_TRANSITION_TRACE_NAME"
@@ -27,3 +32,5 @@
}
private const val UNFOLD_TRANSITION_TRACE_NAME = "FoldUnfoldTransitionInProgress"
+
+@Qualifier annotation class UnfoldTransitionATracePrefix
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/JankMonitorTransitionProgressListener.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/JankMonitorTransitionProgressListener.kt
new file mode 100644
index 0000000..2b38f3d
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/JankMonitorTransitionProgressListener.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.unfold.util
+
+import android.view.View
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.jank.InteractionJankMonitor.CUJ_UNFOLD_ANIM
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import java.util.function.Supplier
+
+class JankMonitorTransitionProgressListener(private val attachedViewProvider: Supplier<View>) :
+ TransitionProgressListener {
+
+ private val interactionJankMonitor = InteractionJankMonitor.getInstance()
+
+ override fun onTransitionStarted() {
+ interactionJankMonitor.begin(attachedViewProvider.get(), CUJ_UNFOLD_ANIM)
+ }
+
+ override fun onTransitionFinished() {
+ interactionJankMonitor.end(CUJ_UNFOLD_ANIM)
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
index df9078a..ee79b87 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
@@ -6,15 +6,20 @@
import android.provider.Settings
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
/** Wraps [UnfoldTransitionProgressProvider] to disable transitions when animations are disabled. */
-class ScaleAwareTransitionProgressProvider(
- unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
+class ScaleAwareTransitionProgressProvider
+@AssistedInject
+constructor(
+ @Assisted progressProviderToWrap: UnfoldTransitionProgressProvider,
private val contentResolver: ContentResolver
) : UnfoldTransitionProgressProvider {
private val scopedUnfoldTransitionProgressProvider =
- ScopedUnfoldTransitionProgressProvider(unfoldTransitionProgressProvider)
+ ScopedUnfoldTransitionProgressProvider(progressProviderToWrap)
private val animatorDurationScaleObserver = object : ContentObserver(null) {
override fun onChange(selfChange: Boolean) {
@@ -47,4 +52,11 @@
contentResolver.unregisterContentObserver(animatorDurationScaleObserver)
scopedUnfoldTransitionProgressProvider.destroy()
}
+
+ @AssistedFactory
+ interface Factory {
+ fun wrap(
+ progressProvider: UnfoldTransitionProgressProvider
+ ): ScaleAwareTransitionProgressProvider
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
index 099dd5d..75579b0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
@@ -30,6 +30,8 @@
import android.view.ViewGroup;
import android.widget.TextView;
+import androidx.annotation.Nullable;
+
import com.android.internal.policy.SystemBarUtils;
import com.android.settingslib.Utils;
import com.android.systemui.R;
@@ -57,6 +59,11 @@
private ColorStateList mNextMessageColorState = ColorStateList.valueOf(DEFAULT_COLOR);
private boolean mBouncerVisible;
private boolean mAltBouncerShowing;
+ /**
+ * Container that wraps the KeyguardMessageArea - may be null if current view hierarchy doesn't
+ * contain {@link R.id.keyguard_message_area_container}.
+ */
+ @Nullable
private ViewGroup mContainer;
private int mTopMargin;
@@ -75,6 +82,9 @@
}
void onConfigChanged() {
+ if (mContainer == null) {
+ return;
+ }
final int newTopMargin = SystemBarUtils.getStatusBarHeight(getContext());
if (mTopMargin == newTopMargin) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index 558f0e6..d1fe7d4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -16,6 +16,7 @@
package com.android.systemui.media.dagger;
+import android.app.Service;
import android.content.Context;
import android.view.WindowManager;
@@ -30,6 +31,7 @@
import com.android.systemui.media.taptotransfer.MediaTttFlags;
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
+import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService;
import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -38,8 +40,11 @@
import javax.inject.Named;
+import dagger.Binds;
import dagger.Module;
import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
/** Dagger module for the media package. */
@Module
@@ -128,4 +133,10 @@
mediaTttChipControllerReceiver,
mainExecutor));
}
+
+ /** Inject into MediaTttSenderService. */
+ @Binds
+ @IntoMap
+ @ClassKey(MediaTttSenderService.class)
+ Service bindMediaTttSenderService(MediaTttSenderService service);
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 83d581f..4e039279 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -539,8 +539,7 @@
}
boolean isVolumeControlEnabled(@NonNull MediaDevice device) {
- // TODO(b/202500642): Also enable volume control for remote non-group sessions.
- return !isActiveRemoteDevice(device)
+ return !device.getFeatures().contains(MediaRoute2Info.FEATURE_REMOTE_GROUP_PLAYBACK)
|| mVolumeAdjustmentForRemoteGroupSessions;
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
index 5a86723..460d38f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -16,9 +16,14 @@
package com.android.systemui.media.taptotransfer
+import android.content.ComponentName
import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
import android.graphics.Color
import android.graphics.drawable.Icon
+import android.media.MediaRoute2Info
+import android.os.IBinder
import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.systemui.R
@@ -27,9 +32,12 @@
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver
import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender
-import com.android.systemui.media.taptotransfer.sender.MoveCloserToTransfer
+import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService
+import com.android.systemui.media.taptotransfer.sender.MoveCloserToStartCast
import com.android.systemui.media.taptotransfer.sender.TransferInitiated
import com.android.systemui.media.taptotransfer.sender.TransferSucceeded
+import com.android.systemui.shared.mediattt.DeviceInfo
+import com.android.systemui.shared.mediattt.IDeviceSenderCallback
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -44,11 +52,14 @@
@SysUISingleton
class MediaTttCommandLineHelper @Inject constructor(
commandRegistry: CommandRegistry,
- context: Context,
+ private val context: Context,
private val mediaTttChipControllerSender: MediaTttChipControllerSender,
private val mediaTttChipControllerReceiver: MediaTttChipControllerReceiver,
@Main private val mainExecutor: DelayableExecutor,
) {
+ private var senderCallback: IDeviceSenderCallback? = null
+ private val senderServiceConnection = SenderServiceConnection()
+
private val appIconDrawable =
Icon.createWithResource(context, R.drawable.ic_avatar_user).loadDrawable(context).also {
it.setTint(Color.YELLOW)
@@ -68,14 +79,20 @@
inner class AddChipCommandSender : Command {
override fun execute(pw: PrintWriter, args: List<String>) {
val otherDeviceName = args[0]
+ val mediaInfo = MediaRoute2Info.Builder("id", "Test Name")
+ .addFeature("feature")
+ .build()
+ val otherDeviceInfo = DeviceInfo(otherDeviceName)
+
when (args[1]) {
- MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME -> {
- mediaTttChipControllerSender.displayChip(
- MoveCloserToTransfer(
- appIconDrawable, APP_ICON_CONTENT_DESCRIPTION, otherDeviceName
- )
- )
+ MOVE_CLOSER_TO_START_CAST_COMMAND_NAME -> {
+ runOnService { senderCallback ->
+ senderCallback.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo)
+ }
}
+
+ // TODO(b/203800643): Migrate other commands to invoke the service instead of the
+ // controller.
TRANSFER_INITIATED_COMMAND_NAME -> {
val futureTask = FutureTask { fakeUndoRunnable }
mediaTttChipControllerSender.displayChip(
@@ -101,7 +118,7 @@
}
else -> {
pw.println("Chip type must be one of " +
- "$MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME, " +
+ "$MOVE_CLOSER_TO_START_CAST_COMMAND_NAME, " +
"$TRANSFER_INITIATED_COMMAND_NAME, " +
TRANSFER_SUCCEEDED_COMMAND_NAME
)
@@ -114,19 +131,40 @@
"$ADD_CHIP_COMMAND_SENDER_TAG <deviceName> <chipStatus>"
)
}
+
+ private fun runOnService(command: SenderCallbackCommand) {
+ val currentServiceCallback = senderCallback
+ if (currentServiceCallback != null) {
+ command.run(currentServiceCallback)
+ } else {
+ bindService(command)
+ }
+ }
+
+ private fun bindService(command: SenderCallbackCommand) {
+ senderServiceConnection.pendingCommand = command
+ val binding = context.bindService(
+ Intent(context, MediaTttSenderService::class.java),
+ senderServiceConnection,
+ Context.BIND_AUTO_CREATE
+ )
+ Log.i(TAG, "Starting service binding? $binding")
+ }
}
/** A command to REMOVE the media ttt chip on the SENDER device. */
inner class RemoveChipCommandSender : Command {
override fun execute(pw: PrintWriter, args: List<String>) {
mediaTttChipControllerSender.removeChip()
+ if (senderCallback != null) {
+ context.unbindService(senderServiceConnection)
+ }
}
override fun help(pw: PrintWriter) {
pw.println("Usage: adb shell cmd statusbar $REMOVE_CHIP_COMMAND_SENDER_TAG")
}
}
-
/** A command to DISPLAY the media ttt chip on the RECEIVER device. */
inner class AddChipCommandReceiver : Command {
override fun execute(pw: PrintWriter, args: List<String>) {
@@ -149,6 +187,29 @@
}
}
+ /** A service connection for [IDeviceSenderCallback]. */
+ private inner class SenderServiceConnection : ServiceConnection {
+ // A command that should be run when the service gets connected.
+ var pendingCommand: SenderCallbackCommand? = null
+
+ override fun onServiceConnected(className: ComponentName, service: IBinder) {
+ val newCallback = IDeviceSenderCallback.Stub.asInterface(service)
+ senderCallback = newCallback
+ pendingCommand?.run(newCallback)
+ pendingCommand = null
+ }
+
+ override fun onServiceDisconnected(className: ComponentName) {
+ senderCallback = null
+ }
+ }
+
+ /** An interface defining a command that should be run on the sender callback. */
+ private fun interface SenderCallbackCommand {
+ /** Runs the command on the provided [senderCallback]. */
+ fun run(senderCallback: IDeviceSenderCallback)
+ }
+
private val fakeUndoRunnable = Runnable {
Log.i(TAG, "Undo runnable triggered")
}
@@ -163,7 +224,7 @@
@VisibleForTesting
const val REMOVE_CHIP_COMMAND_RECEIVER_TAG = "media-ttt-chip-remove-receiver"
@VisibleForTesting
-val MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME = MoveCloserToTransfer::class.simpleName!!
+val MOVE_CLOSER_TO_START_CAST_COMMAND_NAME = MoveCloserToStartCast::class.simpleName!!
@VisibleForTesting
val TRANSFER_INITIATED_COMMAND_NAME = TransferInitiated::class.simpleName!!
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index b1f6faa..dd434e7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -40,17 +40,18 @@
) : MediaTttChipState(appIconDrawable, appIconContentDescription)
/**
- * A state representing that the two devices are close but not close enough to initiate a transfer.
- * The chip will instruct the user to move closer in order to initiate the transfer.
+ * A state representing that the two devices are close but not close enough to *start* a cast to
+ * the receiver device. The chip will instruct the user to move closer in order to initiate the
+ * transfer to the receiver.
*/
-class MoveCloserToTransfer(
+class MoveCloserToStartCast(
appIconDrawable: Drawable,
appIconContentDescription: String,
otherDeviceName: String,
) : ChipStateSender(
appIconDrawable,
appIconContentDescription,
- R.string.media_move_closer_to_transfer,
+ R.string.media_move_closer_to_start_cast,
otherDeviceName
)
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt
new file mode 100644
index 0000000..b56a699
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.taptotransfer.sender
+
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.graphics.Color
+import android.graphics.drawable.Icon
+import android.media.MediaRoute2Info
+import android.os.IBinder
+import com.android.systemui.R
+import com.android.systemui.shared.mediattt.DeviceInfo
+import com.android.systemui.shared.mediattt.IDeviceSenderCallback
+import javax.inject.Inject
+
+/**
+ * Service that allows external handlers to trigger the media chip on the sender device.
+ */
+class MediaTttSenderService @Inject constructor(
+ context: Context,
+ val controller: MediaTttChipControllerSender
+) : Service() {
+
+ // TODO(b/203800643): Add logging when callbacks trigger.
+ private val binder: IBinder = object : IDeviceSenderCallback.Stub() {
+ override fun closeToReceiverToStartCast(
+ mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
+ ) {
+ this@MediaTttSenderService.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo)
+ }
+ }
+
+ // TODO(b/203800643): Use the app icon from the media info instead of a fake one.
+ private val fakeAppIconDrawable =
+ Icon.createWithResource(context, R.drawable.ic_avatar_user).loadDrawable(context).also {
+ it.setTint(Color.YELLOW)
+ }
+
+ override fun onBind(intent: Intent?): IBinder = binder
+
+ private fun closeToReceiverToStartCast(
+ mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
+ ) {
+ val chipState = MoveCloserToStartCast(
+ appIconDrawable = fakeAppIconDrawable,
+ appIconContentDescription = mediaInfo.name.toString(),
+ otherDeviceName = otherDeviceInfo.name
+ )
+ controller.displayChip(chipState)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 866b1b8..d3f8db38 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -247,10 +247,9 @@
boolean shouldUseSplitShade =
resources.getBoolean(R.bool.config_use_split_notification_shade);
- mStatusIconsView.setVisibility(
- shouldUseSplitShade || mUseCombinedQSHeader ? View.GONE : View.VISIBLE);
- mDatePrivacyView.setVisibility(
- shouldUseSplitShade || mUseCombinedQSHeader ? View.GONE : View.VISIBLE);
+ boolean gone = shouldUseSplitShade || mUseCombinedQSHeader || mQsDisabled;
+ mStatusIconsView.setVisibility(gone ? View.GONE : View.VISIBLE);
+ mDatePrivacyView.setVisibility(gone ? View.GONE : View.VISIBLE);
mConfigShowBatteryEstimate = resources.getBoolean(R.bool.config_showBatteryEstimateQSBH);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index b0d41f1..3449bd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -32,6 +32,8 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationContentView
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
@@ -132,12 +134,15 @@
/**
* Tracks state related to conversation notifications, and updates the UI of existing notifications
* when necessary.
+ * TODO(b/214083332) Refactor this class to use the right coordinators and controllers
*/
@SysUISingleton
class ConversationNotificationManager @Inject constructor(
private val notificationEntryManager: NotificationEntryManager,
private val notificationGroupManager: NotificationGroupManagerLegacy,
private val context: Context,
+ private val notifCollection: CommonNotifCollection,
+ private val featureFlags: NotifPipelineFlags,
@Main private val mainHandler: Handler
) {
// Need this state to be thread safe, since it's accessed from the ui thread
@@ -146,76 +151,93 @@
private var notifPanelCollapsed = true
+ private val entryManagerListener = object : NotificationEntryListener {
+ override fun onNotificationRankingUpdated(rankingMap: RankingMap) =
+ updateNotificationRanking(rankingMap)
+ override fun onEntryInflated(entry: NotificationEntry) =
+ onEntryViewBound(entry)
+ override fun onEntryReinflated(entry: NotificationEntry) = onEntryInflated(entry)
+ override fun onEntryRemoved(
+ entry: NotificationEntry,
+ visibility: NotificationVisibility?,
+ removedByUser: Boolean,
+ reason: Int
+ ) = removeTrackedEntry(entry)
+ }
+
+ private val notifCollectionListener = object : NotifCollectionListener {
+ override fun onRankingUpdate(ranking: RankingMap) =
+ updateNotificationRanking(ranking)
+
+ override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+ removeTrackedEntry(entry)
+ }
+ }
+
+ private fun updateNotificationRanking(rankingMap: RankingMap) {
+ fun getLayouts(view: NotificationContentView) =
+ sequenceOf(view.contractedChild, view.expandedChild, view.headsUpChild)
+ val ranking = Ranking()
+ val activeConversationEntries = states.keys.asSequence()
+ .mapNotNull { notificationEntryManager.getActiveNotificationUnfiltered(it) }
+ for (entry in activeConversationEntries) {
+ if (rankingMap.getRanking(entry.sbn.key, ranking) && ranking.isConversation) {
+ val important = ranking.channel.isImportantConversation
+ var changed = false
+ entry.row?.layouts?.asSequence()
+ ?.flatMap(::getLayouts)
+ ?.mapNotNull { it as? ConversationLayout }
+ ?.filterNot { it.isImportantConversation == important }
+ ?.forEach { layout ->
+ changed = true
+ if (important && entry.isMarkedForUserTriggeredMovement) {
+ // delay this so that it doesn't animate in until after
+ // the notif has been moved in the shade
+ mainHandler.postDelayed(
+ {
+ layout.setIsImportantConversation(
+ important,
+ true)
+ },
+ IMPORTANCE_ANIMATION_DELAY.toLong())
+ } else {
+ layout.setIsImportantConversation(important, false)
+ }
+ }
+ if (changed) {
+ notificationGroupManager.updateIsolation(entry)
+ }
+ }
+ }
+ }
+ fun onEntryViewBound(entry: NotificationEntry) {
+ if (!entry.ranking.isConversation) {
+ return
+ }
+ fun updateCount(isExpanded: Boolean) {
+ if (isExpanded && (!notifPanelCollapsed || entry.isPinnedAndExpanded)) {
+ resetCount(entry.key)
+ entry.row?.let(::resetBadgeUi)
+ }
+ }
+ entry.row?.setOnExpansionChangedListener { isExpanded ->
+ if (entry.row?.isShown == true && isExpanded) {
+ entry.row.performOnIntrinsicHeightReached {
+ updateCount(isExpanded)
+ }
+ } else {
+ updateCount(isExpanded)
+ }
+ }
+ updateCount(entry.row?.isExpanded == true)
+ }
+
init {
- notificationEntryManager.addNotificationEntryListener(object : NotificationEntryListener {
- override fun onNotificationRankingUpdated(rankingMap: RankingMap) {
- fun getLayouts(view: NotificationContentView) =
- sequenceOf(view.contractedChild, view.expandedChild, view.headsUpChild)
- val ranking = Ranking()
- val activeConversationEntries = states.keys.asSequence()
- .mapNotNull { notificationEntryManager.getActiveNotificationUnfiltered(it) }
- for (entry in activeConversationEntries) {
- if (rankingMap.getRanking(entry.sbn.key, ranking) && ranking.isConversation) {
- val important = ranking.channel.isImportantConversation
- var changed = false
- entry.row?.layouts?.asSequence()
- ?.flatMap(::getLayouts)
- ?.mapNotNull { it as? ConversationLayout }
- ?.filterNot { it.isImportantConversation == important }
- ?.forEach { layout ->
- changed = true
- if (important && entry.isMarkedForUserTriggeredMovement) {
- // delay this so that it doesn't animate in until after
- // the notif has been moved in the shade
- mainHandler.postDelayed(
- {
- layout.setIsImportantConversation(
- important,
- true)
- },
- IMPORTANCE_ANIMATION_DELAY.toLong())
- } else {
- layout.setIsImportantConversation(important, false)
- }
- }
- if (changed) {
- notificationGroupManager.updateIsolation(entry)
- }
- }
- }
- }
-
- override fun onEntryInflated(entry: NotificationEntry) {
- if (!entry.ranking.isConversation) {
- return
- }
- fun updateCount(isExpanded: Boolean) {
- if (isExpanded && (!notifPanelCollapsed || entry.isPinnedAndExpanded)) {
- resetCount(entry.key)
- entry.row?.let(::resetBadgeUi)
- }
- }
- entry.row?.setOnExpansionChangedListener { isExpanded ->
- if (entry.row?.isShown == true && isExpanded) {
- entry.row.performOnIntrinsicHeightReached {
- updateCount(isExpanded)
- }
- } else {
- updateCount(isExpanded)
- }
- }
- updateCount(entry.row?.isExpanded == true)
- }
-
- override fun onEntryReinflated(entry: NotificationEntry) = onEntryInflated(entry)
-
- override fun onEntryRemoved(
- entry: NotificationEntry,
- visibility: NotificationVisibility?,
- removedByUser: Boolean,
- reason: Int
- ) = removeTrackedEntry(entry)
- })
+ if (featureFlags.isNewPipelineEnabled()) {
+ notifCollection.addCollectionListener(notifCollectionListener)
+ } else {
+ notificationEntryManager.addNotificationEntryListener(entryManagerListener)
+ }
}
private fun ConversationState.shouldIncrementUnread(newBuilder: Notification.Builder) =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index ec4e039..195f367 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -31,6 +31,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.statusbar.notification.ConversationNotificationManager;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -98,6 +99,7 @@
/** How long we can delay a group while waiting for all children to inflate */
private final long mMaxGroupInflationDelay;
+ private final ConversationNotificationManager mConversationManager;
@Inject
public PreparationCoordinator(
@@ -106,7 +108,8 @@
NotifInflationErrorManager errorManager,
NotifViewBarn viewBarn,
NotifUiAdjustmentProvider adjustmentProvider,
- IStatusBarService service) {
+ IStatusBarService service,
+ ConversationNotificationManager conversationManager) {
this(
logger,
notifInflater,
@@ -114,6 +117,7 @@
viewBarn,
adjustmentProvider,
service,
+ conversationManager,
CHILD_BIND_CUTOFF,
MAX_GROUP_INFLATION_DELAY);
}
@@ -126,6 +130,7 @@
NotifViewBarn viewBarn,
NotifUiAdjustmentProvider adjustmentProvider,
IStatusBarService service,
+ ConversationNotificationManager conversationManager,
int childBindCutoff,
long maxGroupInflationDelay) {
mLogger = logger;
@@ -136,6 +141,7 @@
mStatusBarService = service;
mChildBindCutoff = childBindCutoff;
mMaxGroupInflationDelay = maxGroupInflationDelay;
+ mConversationManager = conversationManager;
}
@Override
@@ -363,6 +369,9 @@
mInflatingNotifs.remove(entry);
mViewBarn.registerViewForEntry(entry, controller);
mInflationStates.put(entry, STATE_INFLATED);
+ // NOTE: under the new pipeline there's no way to register for an inflation callback,
+ // so this one method is called by the PreparationCoordinator directly.
+ mConversationManager.onEntryViewBound(entry);
mNotifInflatingFilter.invalidateList();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
index bd84520..31c7006 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
@@ -47,6 +47,8 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
+import com.android.systemui.unfold.util.JankMonitorTransitionProgressListener;
import java.util.Optional;
@@ -81,7 +83,8 @@
WindowManager windowManager,
IWindowManager iWindowManager,
StatusBarContentInsetsProvider contentInsetsProvider,
- @Main Resources resources) {
+ @Main Resources resources,
+ Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider) {
mContext = context;
mWindowManager = windowManager;
mIWindowManager = iWindowManager;
@@ -94,6 +97,10 @@
if (mBarHeight < 0) {
mBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
}
+ unfoldTransitionProgressProvider.ifPresent(
+ unfoldProgressProvider -> unfoldProgressProvider.addCallback(
+ new JankMonitorTransitionProgressListener(
+ /* attachedViewProvider=*/ () -> mStatusBarWindowView)));
}
public int getStatusBarHeight() {
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index c2fd34c..178d014 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -17,87 +17,38 @@
package com.android.systemui.unfold
import android.content.Context
-import android.hardware.SensorManager
-import android.hardware.devicestate.DeviceStateManager
-import android.os.Handler
import android.view.IWindowManager
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.LifecycleScreenStatusProvider
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.updates.FoldStateProvider
+import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix
import com.android.systemui.util.time.SystemClockImpl
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider
import dagger.Lazy
import dagger.Module
import dagger.Provides
import java.util.Optional
-import java.util.concurrent.Executor
import javax.inject.Named
import javax.inject.Singleton
-@Module
+@Module(includes = [UnfoldSharedModule::class])
class UnfoldTransitionModule {
- @Provides
- @Singleton
- fun provideUnfoldTransitionProgressProvider(
- context: Context,
- config: UnfoldTransitionConfig,
- screenStatusProvider: Lazy<LifecycleScreenStatusProvider>,
- deviceStateManager: DeviceStateManager,
- sensorManager: SensorManager,
- @Main executor: Executor,
- @Main handler: Handler
- ): Optional<UnfoldTransitionProgressProvider> =
- if (config.isEnabled) {
- Optional.of(
- createUnfoldTransitionProgressProvider(
- context,
- config,
- screenStatusProvider.get(),
- deviceStateManager,
- sensorManager,
- handler,
- executor,
- tracingTagPrefix = "systemui"))
- } else {
- Optional.empty()
- }
-
- @Provides
- @Singleton
- fun provideFoldStateProvider(
- context: Context,
- config: UnfoldTransitionConfig,
- screenStatusProvider: Lazy<LifecycleScreenStatusProvider>,
- deviceStateManager: DeviceStateManager,
- sensorManager: SensorManager,
- @Main executor: Executor,
- @Main handler: Handler
- ): Optional<FoldStateProvider> =
- if (!config.isHingeAngleEnabled) {
- Optional.empty()
- } else {
- Optional.of(
- createFoldStateProvider(
- context,
- config,
- screenStatusProvider.get(),
- deviceStateManager,
- sensorManager,
- handler,
- executor))
- }
+ @Provides @UnfoldTransitionATracePrefix fun tracingTagPrefix() = "systemui"
@Provides
@Singleton
fun providesFoldStateLoggingProvider(
- optionalFoldStateProvider: Optional<FoldStateProvider>
+ config: UnfoldTransitionConfig,
+ foldStateProvider: Lazy<FoldStateProvider>
): Optional<FoldStateLoggingProvider> =
- optionalFoldStateProvider.map { foldStateProvider ->
- FoldStateLoggingProviderImpl(foldStateProvider, SystemClockImpl())
+ if (config.isHingeAngleEnabled) {
+ Optional.of(FoldStateLoggingProviderImpl(foldStateProvider.get(), SystemClockImpl()))
+ } else {
+ Optional.empty()
}
@Provides
@@ -144,6 +95,9 @@
} else {
ShellUnfoldProgressProvider.NO_PROVIDER
}
+
+ @Provides
+ fun screenStatusProvider(impl: LifecycleScreenStatusProvider): ScreenStatusProvider = impl
}
const val UNFOLD_STATUS_BAR = "unfold_status_bar"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
index dec5a10..4839bde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
@@ -16,23 +16,28 @@
package com.android.systemui.media.taptotransfer
+import android.content.ComponentName
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
-import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender
-import com.android.systemui.media.taptotransfer.sender.MoveCloserToTransfer
-import com.android.systemui.media.taptotransfer.sender.TransferInitiated
-import com.android.systemui.media.taptotransfer.sender.TransferSucceeded
+import com.android.systemui.media.taptotransfer.sender.*
+import com.android.systemui.shared.mediattt.DeviceInfo
+import com.android.systemui.shared.mediattt.IDeviceSenderCallback
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
+import org.mockito.Mockito.anyString
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
import java.io.PrintWriter
import java.io.StringWriter
@@ -51,10 +56,19 @@
private lateinit var mediaTttChipControllerSender: MediaTttChipControllerSender
@Mock
private lateinit var mediaTttChipControllerReceiver: MediaTttChipControllerReceiver
+ @Mock
+ private lateinit var mediaSenderService: IDeviceSenderCallback.Stub
+ private lateinit var mediaSenderServiceComponentName: ComponentName
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+
+ mediaSenderServiceComponentName = ComponentName(context, MediaTttSenderService::class.java)
+ context.addMockService(mediaSenderServiceComponentName, mediaSenderService)
+ whenever(mediaSenderService.queryLocalInterface(anyString())).thenReturn(mediaSenderService)
+ whenever(mediaSenderService.asBinder()).thenReturn(mediaSenderService)
+
mediaTttCommandLineHelper =
MediaTttCommandLineHelper(
commandRegistry,
@@ -102,10 +116,14 @@
}
@Test
- fun sender_moveCloserToTransfer_chipDisplayWithCorrectState() {
- commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand())
+ fun sender_moveCloserToStartCast_serviceCallbackCalled() {
+ commandRegistry.onShellCommand(pw, getMoveCloserToStartCastCommand())
- verify(mediaTttChipControllerSender).displayChip(any(MoveCloserToTransfer::class.java))
+ assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue()
+
+ val deviceInfoCaptor = argumentCaptor<DeviceInfo>()
+ verify(mediaSenderService).closeToReceiverToStartCast(any(), capture(deviceInfoCaptor))
+ assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME)
}
@Test
@@ -143,11 +161,11 @@
verify(mediaTttChipControllerReceiver).removeChip()
}
- private fun getMoveCloserToTransferCommand(): Array<String> =
+ private fun getMoveCloserToStartCastCommand(): Array<String> =
arrayOf(
ADD_CHIP_COMMAND_SENDER_TAG,
DEVICE_NAME,
- MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME
+ MOVE_CLOSER_TO_START_CAST_COMMAND_NAME
)
private fun getTransferInitiatedCommand(): Array<String> =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index caef5b9..ecc4c46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -66,8 +66,8 @@
}
@Test
- fun moveCloserToTransfer_appIcon_chipTextContainsDeviceName_noLoadingIcon_noUndo() {
- controllerSender.displayChip(moveCloserToTransfer())
+ fun moveCloserToStartCast_appIcon_chipTextContainsDeviceName_noLoadingIcon_noUndo() {
+ controllerSender.displayChip(moveCloserToStartCast())
val chipView = getChipView()
assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
@@ -192,8 +192,8 @@
}
@Test
- fun changeFromCloserToTransferToTransferInitiated_loadingIconAppears() {
- controllerSender.displayChip(moveCloserToTransfer())
+ fun changeFromCloserToStartToTransferInitiated_loadingIconAppears() {
+ controllerSender.displayChip(moveCloserToStartCast())
controllerSender.displayChip(transferInitiated())
assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
@@ -216,9 +216,9 @@
}
@Test
- fun changeFromTransferSucceededToMoveCloser_undoButtonDisappears() {
+ fun changeFromTransferSucceededToMoveCloserToStart_undoButtonDisappears() {
controllerSender.displayChip(transferSucceeded())
- controllerSender.displayChip(moveCloserToTransfer())
+ controllerSender.displayChip(moveCloserToStartCast())
assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.GONE)
}
@@ -240,8 +240,8 @@
}
/** Helper method providing default parameters to not clutter up the tests. */
- private fun moveCloserToTransfer() =
- MoveCloserToTransfer(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME)
+ private fun moveCloserToStartCast() =
+ MoveCloserToStartCast(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME)
/** Helper method providing default parameters to not clutter up the tests. */
private fun transferInitiated(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt
new file mode 100644
index 0000000..8f64698
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt
@@ -0,0 +1,48 @@
+package com.android.systemui.media.taptotransfer.sender
+
+import android.media.MediaRoute2Info
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.mediattt.DeviceInfo
+import com.android.systemui.shared.mediattt.IDeviceSenderCallback
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class MediaTttSenderServiceTest : SysuiTestCase() {
+
+ private lateinit var service: MediaTttSenderService
+ private lateinit var callback: IDeviceSenderCallback
+
+ @Mock
+ private lateinit var controller: MediaTttChipControllerSender
+
+ private val mediaInfo = MediaRoute2Info.Builder("id", "Test Name")
+ .addFeature("feature")
+ .build()
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ service = MediaTttSenderService(context, controller)
+ callback = IDeviceSenderCallback.Stub.asInterface(service.onBind(null))
+ }
+
+ @Test
+ fun closeToReceiverToStartCast_controllerTriggeredWithMoveCloserToStartCastState() {
+ val name = "Fake name"
+ callback.closeToReceiverToStartCast(mediaInfo, DeviceInfo(name))
+
+ val chipStateCaptor = argumentCaptor<MoveCloserToStartCast>()
+ verify(controller).displayChip(capture(chipStateCaptor))
+
+ val chipState = chipStateCaptor.value!!
+ assertThat(chipState.otherDeviceName).isEqualTo(name)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
index f70330d..bde6734 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
@@ -40,6 +40,7 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.notification.ConversationNotificationManager;
import com.android.systemui.statusbar.notification.SectionClassifier;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
@@ -92,6 +93,7 @@
@Mock private NotifSection mNotifSection;
@Mock private NotifPipeline mNotifPipeline;
@Mock private IStatusBarService mService;
+ @Mock private ConversationNotificationManager mConvoManager;
@Spy private FakeNotifInflater mNotifInflater = new FakeNotifInflater();
private final SectionClassifier mSectionClassifier = new SectionClassifier();
private final NotifUiAdjustmentProvider mAdjustmentProvider =
@@ -119,6 +121,7 @@
mock(NotifViewBarn.class),
mAdjustmentProvider,
mService,
+ mConvoManager,
TEST_CHILD_BIND_CUTOFF,
TEST_MAX_GROUP_DELAY);
@@ -405,6 +408,13 @@
}
@Test
+ public void testCallConversationManagerBindWhenInflated() {
+ mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
+ mNotifInflater.getInflateCallback(mEntry).onInflationFinished(mEntry, null);
+ verify(mConvoManager, times(1)).onEntryViewBound(eq(mEntry));
+ }
+
+ @Test
public void testPartiallyInflatedGroupsAreReleasedAfterTimeout() {
// GIVEN a newly-posted group with a summary and two children
final GroupEntry group = new GroupEntryBuilder()
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index 1914164..93fc0e72 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -23,7 +23,7 @@
import static android.content.ComponentName.createRelative;
import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
-import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
+import static com.android.server.companion.PackageUtils.enforceUsesCompanionDeviceFeature;
import static com.android.server.companion.PermissionsUtils.enforcePermissionsForAssociation;
import static com.android.server.companion.RolesUtils.isRoleHolder;
@@ -31,6 +31,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.companion.AssociationInfo;
@@ -102,8 +103,9 @@
* @see #processAssociationRequestApproval(AssociationRequest, IAssociationRequestCallback,
* ResultReceiver, MacAddress)
*/
+@SuppressLint("LongLogTag")
class AssociationRequestsProcessor {
- private static final String TAG = LOG_TAG + ".AssociationRequestsProcessor";
+ private static final String TAG = "CompanionDevice_AssociationRequestsProcessor";
private static final ComponentName ASSOCIATION_REQUEST_APPROVAL_ACTIVITY =
createRelative(COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME, ".CompanionDeviceActivity");
@@ -161,7 +163,7 @@
// 1. Enforce permissions and other requirements.
enforcePermissionsForAssociation(mContext, request, packageUid);
- mService.checkUsesFeature(packageName, userId);
+ enforceUsesCompanionDeviceFeature(mContext, userId, packageName);
// 2. Check if association can be created without launching UI (i.e. CDM needs NEITHER
// to perform discovery NOR to collect user consent).
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
new file mode 100644
index 0000000..777917c
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
@@ -0,0 +1,119 @@
+/*
+ * 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.companion;
+
+import static android.content.Context.BIND_IMPORTANT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
+import android.companion.AssociationInfo;
+import android.companion.CompanionDeviceService;
+import android.companion.ICompanionDeviceService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.internal.infra.ServiceConnector;
+
+/**
+ * Manages a connection (binding) to an instance of {@link CompanionDeviceService} running in the
+ * application process.
+ */
+@SuppressLint("LongLogTag")
+class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDeviceService> {
+ private static final String TAG = "CompanionDevice_ServiceConnector";
+ private static final boolean DEBUG = false;
+ private static final int BINDING_FLAGS = BIND_IMPORTANT;
+
+ /** Listener for changes to the state of the {@link CompanionDeviceServiceConnector} */
+ interface Listener {
+ void onBindingDied(@UserIdInt int userId, @NonNull String packageName);
+ }
+
+ private final @UserIdInt int mUserId;
+ private final @NonNull ComponentName mComponentName;
+ private @Nullable Listener mListener;
+
+ CompanionDeviceServiceConnector(@NonNull Context context, @UserIdInt int userId,
+ @NonNull ComponentName componentName) {
+ super(context, buildIntent(componentName), BINDING_FLAGS, userId, null);
+ mUserId = userId;
+ mComponentName = componentName;
+ }
+
+ void setListener(@Nullable Listener listener) {
+ mListener = listener;
+ }
+
+ void postOnDeviceAppeared(@NonNull AssociationInfo associationInfo) {
+ post(companionService -> companionService.onDeviceAppeared(associationInfo));
+ }
+
+ void postOnDeviceDisappeared(@NonNull AssociationInfo associationInfo) {
+ post(companionService -> companionService.onDeviceDisappeared(associationInfo));
+ }
+
+ /**
+ * Post "unbind" job, which will run *after* all previously posted jobs complete.
+ *
+ * IMPORTANT: use this method instead of invoking {@link ServiceConnector#unbind()} directly,
+ * because the latter may cause previously posted callback, such as
+ * {@link ICompanionDeviceService#onDeviceDisappeared(AssociationInfo)} to be dropped.
+ */
+ void postUnbind() {
+ post(it -> unbind());
+ }
+
+ @Override
+ protected void onServiceConnectionStatusChanged(
+ @NonNull ICompanionDeviceService service, boolean isConnected) {
+ if (DEBUG) {
+ Log.d(TAG, "onServiceConnection_StatusChanged() " + mComponentName.toShortString()
+ + " connected=" + isConnected);
+ }
+ }
+
+ @Override
+ public void onBindingDied(@NonNull ComponentName name) {
+ // IMPORTANT: call super!
+ super.onBindingDied(name);
+
+ if (DEBUG) Log.d(TAG, "onBindingDied() " + mComponentName.toShortString());
+
+ mListener.onBindingDied(mUserId, mComponentName.getPackageName());
+ }
+
+ @Override
+ protected ICompanionDeviceService binderAsInterface(@NonNull IBinder service) {
+ return ICompanionDeviceService.Stub.asInterface(service);
+ }
+
+ @Override
+ protected long getAutoDisconnectTimeoutMs() {
+ // Do NOT auto-disconnect.
+ return -1;
+ }
+
+ private static @NonNull Intent buildIntent(@NonNull ComponentName componentName) {
+ return new Intent(CompanionDeviceService.SERVICE_INTERFACE)
+ .setComponent(componentName);
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/PackageUtils.java b/services/companion/java/com/android/server/companion/PackageUtils.java
new file mode 100644
index 0000000..985daa3
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/PackageUtils.java
@@ -0,0 +1,126 @@
+/*
+ * 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.companion;
+
+import static android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP;
+import static android.content.pm.PackageManager.GET_CONFIGURATIONS;
+import static android.content.pm.PackageManager.GET_META_DATA;
+import static android.content.pm.PackageManager.GET_PERMISSIONS;
+
+import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.companion.CompanionDeviceService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.PackageInfoFlags;
+import android.content.pm.PackageManager.ResolveInfoFlags;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Binder;
+import android.util.Slog;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Utility methods for working with {@link PackageInfo}-s.
+ */
+final class PackageUtils {
+ private static final Intent COMPANION_SERVICE_INTENT =
+ new Intent(CompanionDeviceService.SERVICE_INTERFACE);
+ private static final String META_DATA_KEY_PRIMARY = "primary";
+
+ static @Nullable PackageInfo getPackageInfo(@NonNull Context context,
+ @UserIdInt int userId, @NonNull String packageName) {
+ final PackageManager pm = context.getPackageManager();
+ final PackageInfoFlags flags = PackageInfoFlags.of(GET_PERMISSIONS | GET_CONFIGURATIONS);
+ return Binder.withCleanCallingIdentity(() ->
+ pm.getPackageInfoAsUser(packageName, flags , userId));
+ }
+
+ static void enforceUsesCompanionDeviceFeature(@NonNull Context context,
+ @UserIdInt int userId, @NonNull String packageName) {
+ final boolean requested = ArrayUtils.contains(
+ getPackageInfo(context, userId, packageName).reqFeatures,
+ FEATURE_COMPANION_DEVICE_SETUP);
+
+ if (requested) {
+ throw new IllegalStateException("Must declare uses-feature "
+ + FEATURE_COMPANION_DEVICE_SETUP
+ + " in manifest to use this API");
+ }
+ }
+
+ /**
+ * @return list of {@link CompanionDeviceService}-s per package for a given user.
+ * Services marked as "primary" would always appear at the head of the lists, *before*
+ * all non-primary services.
+ */
+ static @NonNull Map<String, List<ComponentName>> getCompanionServicesForUser(
+ @NonNull Context context, @UserIdInt int userId) {
+ final PackageManager pm = context.getPackageManager();
+ final ResolveInfoFlags flags = ResolveInfoFlags.of(GET_META_DATA);
+ final List<ResolveInfo> companionServices =
+ pm.queryIntentServicesAsUser(COMPANION_SERVICE_INTENT, flags, userId);
+
+ final Map<String, List<ComponentName>> packageNameToServiceInfoList = new HashMap<>();
+
+ for (ResolveInfo resolveInfo : companionServices) {
+ final ServiceInfo service = resolveInfo.serviceInfo;
+
+ final boolean requiresPermission = Manifest.permission.BIND_COMPANION_DEVICE_SERVICE
+ .equals(resolveInfo.serviceInfo.permission);
+ if (!requiresPermission) {
+ Slog.w(LOG_TAG, "CompanionDeviceService "
+ + service.getComponentName().flattenToShortString() + " must require "
+ + "android.permission.BIND_COMPANION_DEVICE_SERVICE");
+ continue;
+ }
+
+ // Use LinkedList, because we'll need to prepend "primary" services, while appending the
+ // other (non-primary) services to the list.
+ final LinkedList<ComponentName> services =
+ (LinkedList<ComponentName>) packageNameToServiceInfoList.computeIfAbsent(
+ service.packageName, it -> new LinkedList<>());
+
+ final ComponentName componentName = service.getComponentName();
+ if (isPrimaryCompanionDeviceService(service)) {
+ // "Primary" service should be at the head of the list.
+ services.addFirst(componentName);
+ } else {
+ services.addLast(componentName);
+ }
+ }
+
+ return packageNameToServiceInfoList;
+ }
+
+ private static boolean isPrimaryCompanionDeviceService(ServiceInfo service) {
+ return service.metaData != null && service.metaData.getBoolean(META_DATA_KEY_PRIMARY);
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index e98b63e..8e71dd3 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -16,6 +16,7 @@
package com.android.server.companion.virtual;
+import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
@@ -27,9 +28,10 @@
import android.content.pm.ActivityInfo;
import android.os.Build;
import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Slog;
import android.window.DisplayWindowPolicyController;
-import java.util.HashSet;
import java.util.List;
@@ -38,6 +40,8 @@
*/
class GenericWindowPolicyController extends DisplayWindowPolicyController {
+ private static final String TAG = "VirtualDeviceManager";
+
/**
* If required, allow the secure activity to display on remote device since
* {@link android.os.Build.VERSION_CODES#TIRAMISU}.
@@ -45,10 +49,13 @@
@ChangeId
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
public static final long ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE = 201712607L;
+ @NonNull private final ArraySet<UserHandle> mAllowedUsers;
- @NonNull final HashSet<Integer> mRunningUids = new HashSet<>();
+ @NonNull final ArraySet<Integer> mRunningUids = new ArraySet<>();
- GenericWindowPolicyController(int windowFlags, int systemWindowFlags) {
+ GenericWindowPolicyController(int windowFlags, int systemWindowFlags,
+ @NonNull ArraySet<UserHandle> allowedUsers) {
+ mAllowedUsers = allowedUsers;
setInterestedWindowFlags(windowFlags, systemWindowFlags);
}
@@ -58,7 +65,7 @@
final int activityCount = activities.size();
for (int i = 0; i < activityCount; i++) {
final ActivityInfo aInfo = activities.get(i);
- if ((aInfo.flags & ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
+ if (!canContainActivity(aInfo, /* windowFlags= */ 0, /* systemWindowFlags= */ 0)) {
return false;
}
}
@@ -68,21 +75,7 @@
@Override
public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags,
int systemWindowFlags) {
- if ((activityInfo.flags & ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
- return false;
- }
- if (!CompatChanges.isChangeEnabled(ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE,
- activityInfo.packageName,
- UserHandle.getUserHandleForUid(activityInfo.applicationInfo.uid))) {
- // TODO(b/201712607): Add checks for the apps that use SurfaceView#setSecure.
- if ((windowFlags & FLAG_SECURE) != 0) {
- return false;
- }
- if ((systemWindowFlags & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) != 0) {
- return false;
- }
- }
- return true;
+ return canContainActivity(activityInfo, windowFlags, systemWindowFlags);
}
@Override
@@ -105,4 +98,28 @@
boolean containsUid(int uid) {
return mRunningUids.contains(uid);
}
+
+ private boolean canContainActivity(ActivityInfo activityInfo, int windowFlags,
+ int systemWindowFlags) {
+ if ((activityInfo.flags & FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
+ return false;
+ }
+ final UserHandle activityUser =
+ UserHandle.getUserHandleForUid(activityInfo.applicationInfo.uid);
+ if (!mAllowedUsers.contains(activityUser)) {
+ Slog.d(TAG, "Virtual device activity not allowed from user " + activityUser);
+ return false;
+ }
+ if (!CompatChanges.isChangeEnabled(ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE,
+ activityInfo.packageName, activityUser)) {
+ // TODO(b/201712607): Add checks for the apps that use SurfaceView#setSecure.
+ if ((windowFlags & FLAG_SECURE) != 0) {
+ return false;
+ }
+ if ((systemWindowFlags & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index ca35e03..1bb95f8 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -16,14 +16,20 @@
package com.android.server.companion.virtual;
+import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_ENABLED;
+import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
+import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import android.annotation.NonNull;
+import android.app.admin.DevicePolicyManager;
import android.companion.AssociationInfo;
import android.companion.virtual.IVirtualDevice;
+import android.companion.virtual.VirtualDeviceParams;
import android.content.Context;
import android.graphics.Point;
+import android.hardware.display.DisplayManager;
import android.hardware.input.VirtualKeyEvent;
import android.hardware.input.VirtualMouseButtonEvent;
import android.hardware.input.VirtualMouseRelativeEvent;
@@ -32,6 +38,9 @@
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.ArraySet;
import android.util.SparseArray;
import android.window.DisplayWindowPolicyController;
@@ -56,6 +65,7 @@
final List<Integer> mVirtualDisplayIds = new ArrayList<>();
private final OnDeviceCloseListener mListener;
private final IBinder mAppToken;
+ private final VirtualDeviceParams mParams;
/**
* A mapping from the virtual display ID to its corresponding
@@ -65,17 +75,21 @@
new SparseArray<>();
VirtualDeviceImpl(Context context, AssociationInfo associationInfo,
- IBinder token, int ownerUid, OnDeviceCloseListener listener) {
- this(context, associationInfo, token, ownerUid, /* inputController= */ null, listener);
+ IBinder token, int ownerUid, OnDeviceCloseListener listener,
+ VirtualDeviceParams params) {
+ this(context, associationInfo, token, ownerUid, /* inputController= */ null, listener,
+ params);
}
@VisibleForTesting
VirtualDeviceImpl(Context context, AssociationInfo associationInfo, IBinder token,
- int ownerUid, InputController inputController, OnDeviceCloseListener listener) {
+ int ownerUid, InputController inputController, OnDeviceCloseListener listener,
+ VirtualDeviceParams params) {
mContext = context;
mAssociationInfo = associationInfo;
mOwnerUid = ownerUid;
mAppToken = token;
+ mParams = params;
if (inputController == null) {
mInputController = new InputController(mVirtualDeviceLock);
} else {
@@ -89,7 +103,19 @@
}
}
- @Override
+ /**
+ * Returns the flags that should be added to any virtual displays created on this virtual
+ * device.
+ */
+ int getBaseVirtualDisplayFlags() {
+ int flags = 0;
+ if (mParams.getLockState() == VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED) {
+ flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
+ }
+ return flags;
+ }
+
+ @Override // Binder call
public int getAssociationId() {
return mAssociationInfo.getId();
}
@@ -267,11 +293,29 @@
mVirtualDisplayIds.add(displayId);
final GenericWindowPolicyController dwpc =
new GenericWindowPolicyController(FLAG_SECURE,
- SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+ SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, getAllowedUserHandles());
mWindowPolicyControllers.put(displayId, dwpc);
return dwpc;
}
+ private ArraySet<UserHandle> getAllowedUserHandles() {
+ ArraySet<UserHandle> result = new ArraySet<>();
+ DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+ UserManager userManager = mContext.getSystemService(UserManager.class);
+ for (UserHandle profile : userManager.getAllProfiles()) {
+ int nearbyAppStreamingPolicy = dpm.getNearbyAppStreamingPolicy(profile.getIdentifier());
+ if (nearbyAppStreamingPolicy == NEARBY_STREAMING_ENABLED
+ || nearbyAppStreamingPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY) {
+ result.add(profile);
+ } else if (nearbyAppStreamingPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY) {
+ if (mParams.getUsersWithMatchingAccounts().contains(profile)) {
+ result.add(profile);
+ }
+ }
+ }
+ return result;
+ }
+
void onVirtualDisplayRemovedLocked(int displayId) {
if (!mVirtualDisplayIds.contains(displayId)) {
throw new IllegalStateException(
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 0db670e..12df79d 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -24,6 +24,7 @@
import android.companion.CompanionDeviceManager.OnAssociationsChangedListener;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.IVirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
import android.content.Context;
import android.os.IBinder;
import android.os.Parcel;
@@ -131,7 +132,10 @@
@Override // Binder call
public IVirtualDevice createVirtualDevice(
- IBinder token, String packageName, int associationId) {
+ IBinder token,
+ String packageName,
+ int associationId,
+ @NonNull VirtualDeviceParams params) {
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
"createVirtualDevice");
@@ -160,7 +164,7 @@
mVirtualDevices.remove(associationId);
}
}
- });
+ }, params);
mVirtualDevices.put(associationInfo.getId(), virtualDevice);
return virtualDevice;
}
@@ -238,6 +242,11 @@
}
@Override
+ public int getBaseVirtualDisplayFlags(IVirtualDevice virtualDevice) {
+ return ((VirtualDeviceImpl) virtualDevice).getBaseVirtualDisplayFlags();
+ }
+
+ @Override
public boolean isAppOwnerOfAnyVirtualDevice(int uid) {
synchronized (mVirtualDeviceManagerLock) {
int size = mVirtualDevices.size();
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 262933d..37ff879 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -20,7 +20,6 @@
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
-import static android.os.UserHandle.USER_SYSTEM;
import static android.permission.PermissionCheckerManager.PERMISSION_HARD_DENIED;
import android.Manifest;
@@ -65,6 +64,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.IUserRestrictionsListener;
import android.os.Looper;
import android.os.Message;
import android.os.PowerExemptionManager;
@@ -88,9 +88,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.pm.UserManagerInternal;
-import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
-import com.android.server.pm.UserRestrictionsUtils;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -265,30 +262,34 @@
}
};
- private final UserRestrictionsListener mUserRestrictionsListener =
- new UserRestrictionsListener() {
+ private final IUserRestrictionsListener mUserRestrictionsListener =
+ new IUserRestrictionsListener.Stub() {
@Override
public void onUserRestrictionsChanged(int userId, Bundle newRestrictions,
Bundle prevRestrictions) {
- if (UserRestrictionsUtils.restrictionsChanged(prevRestrictions, newRestrictions,
- UserManager.DISALLOW_BLUETOOTH_SHARING)) {
+ final boolean newDisallowBluetoothSharing = newRestrictions
+ .getBoolean(UserManager.DISALLOW_BLUETOOTH_SHARING, false);
+ final boolean prevDisallowBluetoothSharing = prevRestrictions
+ .getBoolean(UserManager.DISALLOW_BLUETOOTH_SHARING, false);
+ if (newDisallowBluetoothSharing != prevDisallowBluetoothSharing) {
updateOppLauncherComponentState(userId,
newRestrictions.getBoolean(UserManager.DISALLOW_BLUETOOTH_SHARING));
}
+ final boolean newDisallowBluetooth = newRestrictions
+ .getBoolean(UserManager.DISALLOW_BLUETOOTH, false);
+ final boolean prevDisallowBluetooth = prevRestrictions
+ .getBoolean(UserManager.DISALLOW_BLUETOOTH, false);
// DISALLOW_BLUETOOTH can only be set by DO or PO on the system user.
- if (userId == USER_SYSTEM
- && UserRestrictionsUtils.restrictionsChanged(prevRestrictions,
- newRestrictions, UserManager.DISALLOW_BLUETOOTH)) {
- if (userId == USER_SYSTEM && newRestrictions.getBoolean(
- UserManager.DISALLOW_BLUETOOTH)) {
+ final boolean isUserSystem = userId == UserHandle.SYSTEM.getIdentifier();
+ if (isUserSystem && newDisallowBluetooth != prevDisallowBluetooth) {
+ if (isUserSystem && newDisallowBluetooth) {
updateOppLauncherComponentState(userId, true); // Sharing disallowed
sendDisableMsg(BluetoothProtoEnums.ENABLE_DISABLE_REASON_DISALLOWED,
mContext.getPackageName());
} else {
- updateOppLauncherComponentState(userId, newRestrictions.getBoolean(
- UserManager.DISALLOW_BLUETOOTH_SHARING));
+ updateOppLauncherComponentState(userId, newDisallowBluetoothSharing);
}
}
}
@@ -541,7 +542,7 @@
if (!noHome) {
PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
systemUiUid = pm.getPackageUid(pm.getSystemUiServiceComponent().getPackageName(),
- MATCH_SYSTEM_ONLY, USER_SYSTEM);
+ MATCH_SYSTEM_ONLY, UserHandle.SYSTEM.getIdentifier());
}
if (systemUiUid >= 0) {
Slog.d(TAG, "Detected SystemUiUid: " + Integer.toString(systemUiUid));
@@ -1396,9 +1397,8 @@
Slog.d(TAG, "Bluetooth boot completed");
}
mAppOps = mContext.getSystemService(AppOpsManager.class);
- UserManagerInternal userManagerInternal =
- LocalServices.getService(UserManagerInternal.class);
- userManagerInternal.addUserRestrictionsListener(mUserRestrictionsListener);
+ UserManager userManager = mContext.getSystemService(UserManager.class);
+ userManager.addUserRestrictionsListener(mUserRestrictionsListener);
final boolean isBluetoothDisallowed = isBluetoothDisallowed();
if (isBluetoothDisallowed) {
return;
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index 39fa3f2..135276e 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -50,6 +50,12 @@
public abstract void onVirtualDisplayRemoved(IVirtualDevice virtualDevice, int displayId);
/**
+ * Returns the flags that should be added to any virtual displays created on this virtual
+ * device.
+ */
+ public abstract int getBaseVirtualDisplayFlags(IVirtualDevice virtualDevice);
+
+ /**
* Returns true if the given {@code uid} is the owner of any virtual devices that are
* currently active.
*/
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index def9685..d0ce9ef 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -37,6 +37,8 @@
* </p>
*/
abstract class DisplayDevice {
+ private static final Display.Mode EMPTY_DISPLAY_MODE = new Display.Mode.Builder().build();
+
private final DisplayAdapter mDisplayAdapter;
private final IBinder mDisplayToken;
private final String mUniqueId;
@@ -213,6 +215,13 @@
public void setUserPreferredDisplayModeLocked(Display.Mode mode) { }
/**
+ * Returns the user preferred display mode.
+ */
+ public Display.Mode getUserPreferredDisplayModeLocked() {
+ return EMPTY_DISPLAY_MODE;
+ }
+
+ /**
* Sets the requested color mode.
*/
public void setRequestedColorModeLocked(int colorMode) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index be889e4..3feffc6 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -873,11 +873,11 @@
private void updateUserPreferredDisplayModeSettingsLocked() {
final float refreshRate = Settings.Global.getFloat(mContext.getContentResolver(),
- Settings.Global.USER_PREFERRED_REFRESH_RATE, 0.0f);
+ Settings.Global.USER_PREFERRED_REFRESH_RATE, Display.INVALID_DISPLAY_REFRESH_RATE);
final int height = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.USER_PREFERRED_RESOLUTION_HEIGHT, -1);
+ Settings.Global.USER_PREFERRED_RESOLUTION_HEIGHT, Display.INVALID_DISPLAY_HEIGHT);
final int width = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, -1);
+ Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, Display.INVALID_DISPLAY_WIDTH);
Display.Mode mode = new Display.Mode(width, height, refreshRate);
mUserPreferredMode = isResolutionAndRefreshRateValid(mode) ? mode : null;
}
@@ -1250,6 +1250,14 @@
}
final Surface surface = virtualDisplayConfig.getSurface();
int flags = virtualDisplayConfig.getFlags();
+ if (virtualDevice != null) {
+ final VirtualDeviceManagerInternal vdm =
+ getLocalService(VirtualDeviceManagerInternal.class);
+ if (!vdm.isValidVirtualDevice(virtualDevice)) {
+ throw new SecurityException("Invalid virtual device");
+ }
+ flags |= vdm.getBaseVirtualDisplayFlags(virtualDevice);
+ }
if (surface != null && surface.isSingleBuffered()) {
throw new IllegalArgumentException("Surface can't be single-buffered");
@@ -1282,14 +1290,6 @@
}
}
- if (virtualDevice != null) {
- final VirtualDeviceManagerInternal vdm =
- getLocalService(VirtualDeviceManagerInternal.class);
- if (!vdm.isValidVirtualDevice(virtualDevice)) {
- throw new SecurityException("Invalid virtual device");
- }
- }
-
if (callingUid != Process.SYSTEM_UID
&& (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
if (!canProjectVideo(projection)) {
@@ -1560,8 +1560,11 @@
}
if (mUserPreferredMode != null) {
device.setUserPreferredDisplayModeLocked(mUserPreferredMode);
+ } else {
+ configurePreferredDisplayModeLocked(display);
}
addDisplayPowerControllerLocked(display);
+
mDisplayStates.append(displayId, Display.STATE_UNKNOWN);
final float brightnessDefault = display.getDisplayInfoLocked().brightnessDefault;
@@ -1688,6 +1691,24 @@
}
}
+ private void configurePreferredDisplayModeLocked(LogicalDisplay display) {
+ final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
+ final Point userPreferredResolution =
+ mPersistentDataStore.getUserPreferredResolution(device);
+ final float refreshRate = mPersistentDataStore.getUserPreferredRefreshRate(device);
+ if (userPreferredResolution == null && Float.isNaN(refreshRate)) {
+ return;
+ }
+ Display.Mode.Builder modeBuilder = new Display.Mode.Builder();
+ if (userPreferredResolution != null) {
+ modeBuilder.setResolution(userPreferredResolution.x, userPreferredResolution.y);
+ }
+ if (!Float.isNaN(refreshRate)) {
+ modeBuilder.setRefreshRate(refreshRate);
+ }
+ device.setUserPreferredDisplayModeLocked(modeBuilder.build());
+ }
+
// If we've never recorded stable device stats for this device before and they aren't
// explicitly configured, go ahead and record the stable device stats now based on the status
// of the default display at first boot.
@@ -1731,36 +1752,79 @@
return mWideColorSpace.getId();
}
- void setUserPreferredDisplayModeInternal(Display.Mode mode) {
+ void setUserPreferredDisplayModeInternal(int displayId, Display.Mode mode) {
synchronized (mSyncRoot) {
- if (Objects.equals(mUserPreferredMode, mode)) {
+ if (Objects.equals(mUserPreferredMode, mode) && displayId == Display.INVALID_DISPLAY) {
return;
}
- if (mode != null && !isResolutionAndRefreshRateValid(mode)) {
+ if (mode != null && !isResolutionAndRefreshRateValid(mode)
+ && displayId == Display.INVALID_DISPLAY) {
throw new IllegalArgumentException("width, height and refresh rate of mode should "
- + "be greater than 0");
+ + "be greater than 0 when setting the global user preferred display mode.");
}
- mUserPreferredMode = mode;
- final int resolutionHeight = mode == null ? -1 : mode.getPhysicalHeight();
- final int resolutionWidth = mode == null ? -1 : mode.getPhysicalWidth();
- final float refreshRate = mode == null ? 0.0f : mode.getRefreshRate();
- Settings.Global.putFloat(mContext.getContentResolver(),
- Settings.Global.USER_PREFERRED_REFRESH_RATE, refreshRate);
- Settings.Global.putInt(mContext.getContentResolver(),
- Settings.Global.USER_PREFERRED_RESOLUTION_HEIGHT, resolutionHeight);
- Settings.Global.putInt(mContext.getContentResolver(),
- Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, resolutionWidth);
- mDisplayDeviceRepo.forEachLocked((DisplayDevice device) -> {
- device.setUserPreferredDisplayModeLocked(mode);
- });
+ final int resolutionHeight = mode == null ? Display.INVALID_DISPLAY_HEIGHT
+ : mode.getPhysicalHeight();
+ final int resolutionWidth = mode == null ? Display.INVALID_DISPLAY_WIDTH
+ : mode.getPhysicalWidth();
+ final float refreshRate = mode == null ? Display.INVALID_DISPLAY_REFRESH_RATE
+ : mode.getRefreshRate();
+
+ storeModeInPersistentDataStoreLocked(
+ displayId, resolutionWidth, resolutionHeight, refreshRate);
+ if (displayId != Display.INVALID_DISPLAY) {
+ setUserPreferredModeForDisplayLocked(displayId, mode);
+ } else {
+ mUserPreferredMode = mode;
+ storeModeInGlobalSettingsLocked(
+ resolutionWidth, resolutionHeight, refreshRate, mode);
+ }
}
}
- private Display.Mode getUserPreferredDisplayModeInternal() {
+ private void storeModeInPersistentDataStoreLocked(int displayId, int resolutionWidth,
+ int resolutionHeight, float refreshRate) {
+ DisplayDevice displayDevice = getDeviceForDisplayLocked(displayId);
+ if (displayDevice == null) {
+ return;
+ }
+ mPersistentDataStore.setUserPreferredResolution(
+ displayDevice, resolutionWidth, resolutionHeight);
+ mPersistentDataStore.setUserPreferredRefreshRate(displayDevice, refreshRate);
+ }
+
+ private void setUserPreferredModeForDisplayLocked(int displayId, Display.Mode mode) {
+ DisplayDevice displayDevice = getDeviceForDisplayLocked(displayId);
+ if (displayDevice == null) {
+ return;
+ }
+ displayDevice.setUserPreferredDisplayModeLocked(mode);
+ }
+
+ private void storeModeInGlobalSettingsLocked(
+ int resolutionWidth, int resolutionHeight, float refreshRate, Display.Mode mode) {
+ Settings.Global.putFloat(mContext.getContentResolver(),
+ Settings.Global.USER_PREFERRED_REFRESH_RATE, refreshRate);
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.USER_PREFERRED_RESOLUTION_HEIGHT, resolutionHeight);
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, resolutionWidth);
+ mDisplayDeviceRepo.forEachLocked((DisplayDevice device) -> {
+ device.setUserPreferredDisplayModeLocked(mode);
+ });
+ }
+
+ Display.Mode getUserPreferredDisplayModeInternal(int displayId) {
synchronized (mSyncRoot) {
- return mUserPreferredMode;
+ if (displayId == Display.INVALID_DISPLAY) {
+ return mUserPreferredMode;
+ }
+ DisplayDevice displayDevice = getDeviceForDisplayLocked(displayId);
+ if (displayDevice == null) {
+ return null;
+ }
+ return displayDevice.getUserPreferredDisplayModeLocked();
}
}
@@ -2373,7 +2437,7 @@
pw.println(" mMinimumBrightnessCurve=" + mMinimumBrightnessCurve);
if (mUserPreferredMode != null) {
- pw.println(mUserPreferredMode.toString());
+ pw.println(mUserPreferredMode);
}
pw.println();
@@ -3379,23 +3443,23 @@
}
@Override // Binder call
- public void setUserPreferredDisplayMode(Display.Mode mode) {
+ public void setUserPreferredDisplayMode(int displayId, Display.Mode mode) {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE,
"Permission required to set the user preferred display mode.");
final long token = Binder.clearCallingIdentity();
try {
- setUserPreferredDisplayModeInternal(mode);
+ setUserPreferredDisplayModeInternal(displayId, mode);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override // Binder call
- public Display.Mode getUserPreferredDisplayMode() {
+ public Display.Mode getUserPreferredDisplayMode(int displayId) {
final long token = Binder.clearCallingIdentity();
try {
- return getUserPreferredDisplayModeInternal();
+ return getUserPreferredDisplayModeInternal(displayId);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index 9a7ddcb..a9a1f08 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -113,13 +113,18 @@
pw.println(" constrain-launcher-metrics [true|false]");
pw.println(" Sets if Display#getRealSize and getRealMetrics should be constrained for ");
pw.println(" Launcher.");
- pw.println(" set-user-preferred-display-mode WIDTH HEIGHT REFRESH-RATE");
+ pw.println(" set-user-preferred-display-mode WIDTH HEIGHT REFRESH-RATE "
+ + "DISPLAY_ID (optional)");
pw.println(" Sets the user preferred display mode which has fields WIDTH, HEIGHT and "
- + "REFRESH-RATE");
- pw.println(" clear-user-preferred-display-mode");
- pw.println(" Clears the user preferred display mode");
- pw.println(" get-user-preferred-display-mode");
- pw.println(" Returns the user preferred display mode or null id no mode is set by user");
+ + "REFRESH-RATE. If DISPLAY_ID is passed, the mode change is applied to display"
+ + "with id = DISPLAY_ID, else mode change is applied globally.");
+ pw.println(" clear-user-preferred-display-mode DISPLAY_ID (optional)");
+ pw.println(" Clears the user preferred display mode. If DISPLAY_ID is passed, the mode"
+ + " is cleared for display with id = DISPLAY_ID, else mode is cleared globally.");
+ pw.println(" get-user-preferred-display-mode DISPLAY_ID (optional)");
+ pw.println(" Returns the user preferred display mode or null if no mode is set by user."
+ + "If DISPLAY_ID is passed, the mode for display with id = DISPLAY_ID is "
+ + "returned, else global display mode is returned.");
pw.println(" set-match-content-frame-rate-pref PREFERENCE");
pw.println(" Sets the match content frame rate preference as PREFERENCE ");
pw.println(" get-match-content-frame-rate-pref");
@@ -235,28 +240,54 @@
getErrPrintWriter().println("Error: invalid format of width, height or refresh rate");
return 1;
}
- if (width < 0 || height < 0 || refreshRate <= 0.0f) {
- getErrPrintWriter().println("Error: invalid value of width, height or refresh rate");
+ if ((width < 0 || height < 0) && refreshRate <= 0.0f) {
+ getErrPrintWriter().println("Error: invalid value of resolution (width, height)"
+ + " and refresh rate");
return 1;
}
- final Context context = mService.getContext();
- final DisplayManager dm = context.getSystemService(DisplayManager.class);
- dm.setUserPreferredDisplayMode(new Display.Mode(width, height, refreshRate));
+ final String displayIdText = getNextArg();
+ int displayId = Display.INVALID_DISPLAY;
+ if (displayIdText != null) {
+ try {
+ displayId = Integer.parseInt(displayIdText);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: invalid format of display ID");
+ return 1;
+ }
+ }
+ mService.setUserPreferredDisplayModeInternal(
+ displayId, new Display.Mode(width, height, refreshRate));
return 0;
}
private int clearUserPreferredDisplayMode() {
- final Context context = mService.getContext();
- final DisplayManager dm = context.getSystemService(DisplayManager.class);
- dm.clearUserPreferredDisplayMode();
+ final String displayIdText = getNextArg();
+ int displayId = Display.INVALID_DISPLAY;
+ if (displayIdText != null) {
+ try {
+ displayId = Integer.parseInt(displayIdText);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: invalid format of display ID");
+ return 1;
+ }
+ }
+ mService.setUserPreferredDisplayModeInternal(displayId, null);
return 0;
}
private int getUserPreferredDisplayMode() {
- final Context context = mService.getContext();
- final DisplayManager dm = context.getSystemService(DisplayManager.class);
- final Display.Mode mode = dm.getUserPreferredDisplayMode();
+ final String displayIdText = getNextArg();
+ int displayId = Display.INVALID_DISPLAY;
+ if (displayIdText != null) {
+ try {
+ displayId = Integer.parseInt(displayIdText);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: invalid format of display ID");
+ return 1;
+ }
+ }
+ final Display.Mode mode = mService.getUserPreferredDisplayModeInternal(displayId);
if (mode == null) {
getOutPrintWriter().println("User preferred display mode: null");
return 0;
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 300f59e..84de822 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -847,6 +847,10 @@
public void setUserPreferredDisplayModeLocked(Display.Mode mode) {
final int oldModeId = getPreferredModeId();
mUserPreferredMode = mode;
+ if (mode != null && (mode.isRefreshRateSet() ^ mode.isResolutionSet())) {
+ mUserPreferredMode = findMode(mode.getPhysicalWidth(),
+ mode.getPhysicalHeight(), mode.getRefreshRate());
+ }
mUserPreferredModeId = findUserPreferredModeIdLocked(mode);
if (oldModeId != getPreferredModeId()) {
@@ -855,6 +859,11 @@
}
@Override
+ public Display.Mode getUserPreferredDisplayModeLocked() {
+ return mUserPreferredMode;
+ }
+
+ @Override
public void setRequestedColorModeLocked(int colorMode) {
requestColorModeLocked(colorMode);
}
@@ -1062,6 +1071,18 @@
return matchingModeId;
}
+ // Returns a mode with resolution (width, height) and/or refreshRate. If any one of the
+ // resolution or refresh-rate is valid, a mode having the valid parameters is returned.
+ private Display.Mode findMode(int width, int height, float refreshRate) {
+ for (int i = 0; i < mSupportedModes.size(); i++) {
+ Display.Mode supportedMode = mSupportedModes.valueAt(i).mMode;
+ if (supportedMode.matchesIfValid(width, height, refreshRate)) {
+ return supportedMode;
+ }
+ }
+ return null;
+ }
+
private int findUserPreferredModeIdLocked(Display.Mode userPreferredMode) {
if (userPreferredMode != null) {
for (int i = 0; i < mSupportedModes.size(); i++) {
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index 4b0d43b..2eba080 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -291,6 +291,54 @@
return false;
}
+ public boolean setUserPreferredRefreshRate(DisplayDevice displayDevice, float refreshRate) {
+ final String displayDeviceUniqueId = displayDevice.getUniqueId();
+ if (!displayDevice.hasStableUniqueId() || displayDeviceUniqueId == null) {
+ return false;
+ }
+ DisplayState state = getDisplayState(displayDevice.getUniqueId(), true);
+ if (state.setRefreshRate(refreshRate)) {
+ setDirty();
+ return true;
+ }
+ return false;
+ }
+
+ public float getUserPreferredRefreshRate(DisplayDevice device) {
+ if (device == null || !device.hasStableUniqueId()) {
+ return Float.NaN;
+ }
+ final DisplayState state = getDisplayState(device.getUniqueId(), false);
+ if (state == null) {
+ return Float.NaN;
+ }
+ return state.getRefreshRate();
+ }
+
+ public boolean setUserPreferredResolution(DisplayDevice displayDevice, int width, int height) {
+ final String displayDeviceUniqueId = displayDevice.getUniqueId();
+ if (!displayDevice.hasStableUniqueId() || displayDeviceUniqueId == null) {
+ return false;
+ }
+ DisplayState state = getDisplayState(displayDevice.getUniqueId(), true);
+ if (state.setResolution(width, height)) {
+ setDirty();
+ return true;
+ }
+ return false;
+ }
+
+ public Point getUserPreferredResolution(DisplayDevice displayDevice) {
+ if (displayDevice == null || !displayDevice.hasStableUniqueId()) {
+ return null;
+ }
+ final DisplayState state = getDisplayState(displayDevice.getUniqueId(), false);
+ if (state == null) {
+ return null;
+ }
+ return state.getResolution();
+ }
+
public Point getStableDisplaySize() {
loadIfNeeded();
return mStableDeviceValues.getDisplaySize();
@@ -536,6 +584,9 @@
private static final class DisplayState {
private int mColorMode;
private float mBrightness;
+ private int mWidth;
+ private int mHeight;
+ private float mRefreshRate;
// Brightness configuration by user
private BrightnessConfigurations mDisplayBrightnessConfigurations =
@@ -576,6 +627,31 @@
return mDisplayBrightnessConfigurations.mConfigurations.get(userSerial);
}
+ public boolean setResolution(int width, int height) {
+ if (width == mWidth && height == mHeight) {
+ return false;
+ }
+ mWidth = width;
+ mHeight = height;
+ return true;
+ }
+
+ public Point getResolution() {
+ return new Point(mWidth, mHeight);
+ }
+
+ public boolean setRefreshRate(float refreshRate) {
+ if (refreshRate == mRefreshRate) {
+ return false;
+ }
+ mRefreshRate = refreshRate;
+ return true;
+ }
+
+ public float getRefreshRate() {
+ return mRefreshRate;
+ }
+
public void loadFromXml(TypedXmlPullParser parser)
throws IOException, XmlPullParserException {
final int outerDepth = parser.getDepth();
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 399ae53..a5edfed 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -6674,31 +6674,33 @@
// package or a registered listener can enqueue. Prevents DOS attacks and deals with leaks.
if (!isSystemNotification && !isNotificationFromListener) {
final int callingUid = Binder.getCallingUid();
- if (mNotificationsByKey.get(r.getSbn().getKey()) == null
- && isCallerInstantApp(callingUid, userId)) {
- // Ephemeral apps have some special constraints for notifications.
- // They are not allowed to create new notifications however they are allowed to
- // update notifications created by the system (e.g. a foreground service
- // notification).
- throw new SecurityException("Instant app " + pkg
- + " cannot create notifications");
- }
+ synchronized (mNotificationLock) {
+ if (mNotificationsByKey.get(r.getSbn().getKey()) == null
+ && isCallerInstantApp(callingUid, userId)) {
+ // Ephemeral apps have some special constraints for notifications.
+ // They are not allowed to create new notifications however they are allowed to
+ // update notifications created by the system (e.g. a foreground service
+ // notification).
+ throw new SecurityException("Instant app " + pkg
+ + " cannot create notifications");
+ }
- // rate limit updates that aren't completed progress notifications
- if (mNotificationsByKey.get(r.getSbn().getKey()) != null
- && !r.getNotification().hasCompletedProgress()
- && !isAutogroup) {
+ // rate limit updates that aren't completed progress notifications
+ if (mNotificationsByKey.get(r.getSbn().getKey()) != null
+ && !r.getNotification().hasCompletedProgress()
+ && !isAutogroup) {
- final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
- if (appEnqueueRate > mMaxPackageEnqueueRate) {
- mUsageStats.registerOverRateQuota(pkg);
- final long now = SystemClock.elapsedRealtime();
- if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) {
- Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate
- + ". Shedding " + r.getSbn().getKey() + ". package=" + pkg);
- mLastOverRateLogTime = now;
+ final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
+ if (appEnqueueRate > mMaxPackageEnqueueRate) {
+ mUsageStats.registerOverRateQuota(pkg);
+ final long now = SystemClock.elapsedRealtime();
+ if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) {
+ Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate
+ + ". Shedding " + r.getSbn().getKey() + ". package=" + pkg);
+ mLastOverRateLogTime = now;
+ }
+ return false;
}
- return false;
}
}
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index c285e27..a1c97a8 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -413,9 +413,11 @@
throws PackageManagerException;
/**
- * Get a map of system services defined in an apex mapped to the jar files they reside in.
+ * Get a list of apex system services implemented in an apex.
+ *
+ * <p>The list is sorted by initOrder for consistency.
*/
- public abstract Map<String, String> getApexSystemServices();
+ public abstract List<ApexSystemServiceInfo> getApexSystemServices();
/**
* Dumps various state information to the provided {@link PrintWriter} object.
@@ -448,7 +450,7 @@
* Map of all apex system services to the jar files they are contained in.
*/
@GuardedBy("mLock")
- private Map<String, String> mApexSystemServices = new ArrayMap<>();
+ private List<ApexSystemServiceInfo> mApexSystemServices = new ArrayList<>();
/**
* Contains the list of {@code packageName}s of apks-in-apex for given
@@ -604,14 +606,19 @@
}
String name = service.getName();
- if (mApexSystemServices.containsKey(name)) {
- throw new IllegalStateException(String.format(
- "Duplicate apex-system-service %s from %s, %s",
- name, mApexSystemServices.get(name), service.getJarPath()));
+ for (ApexSystemServiceInfo info : mApexSystemServices) {
+ if (info.getName().equals(name)) {
+ throw new IllegalStateException(String.format(
+ "Duplicate apex-system-service %s from %s, %s",
+ name, info.mJarPath, service.getJarPath()));
+ }
}
- mApexSystemServices.put(name, service.getJarPath());
+ ApexSystemServiceInfo info = new ApexSystemServiceInfo(
+ service.getName(), service.getJarPath(), service.getInitOrder());
+ mApexSystemServices.add(info);
}
+ Collections.sort(mApexSystemServices);
mPackageNameToApexModuleName.put(packageInfo.packageName, ai.moduleName);
if (ai.isActive) {
if (activePackagesSet.contains(packageInfo.packageName)) {
@@ -1132,7 +1139,7 @@
}
@Override
- public Map<String, String> getApexSystemServices() {
+ public List<ApexSystemServiceInfo> getApexSystemServices() {
synchronized (mLock) {
Preconditions.checkState(mApexSystemServices != null,
"APEX packages have not been scanned");
@@ -1418,10 +1425,10 @@
}
@Override
- public Map<String, String> getApexSystemServices() {
+ public List<ApexSystemServiceInfo> getApexSystemServices() {
// TODO(satayev): we can't really support flattened apex use case, and need to migrate
// the manifest entries into system's manifest asap.
- return Collections.emptyMap();
+ return Collections.emptyList();
}
@Override
diff --git a/services/core/java/com/android/server/pm/ApexSystemServiceInfo.java b/services/core/java/com/android/server/pm/ApexSystemServiceInfo.java
new file mode 100644
index 0000000..f75ba6d
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ApexSystemServiceInfo.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.annotation.Nullable;
+
+/**
+ * A helper class that contains information about apex-system-service to be used within system
+ * server process.
+ */
+public final class ApexSystemServiceInfo implements Comparable<ApexSystemServiceInfo> {
+
+ final String mName;
+ @Nullable
+ final String mJarPath;
+ final int mInitOrder;
+
+ public ApexSystemServiceInfo(String name, String jarPath, int initOrder) {
+ this.mName = name;
+ this.mJarPath = jarPath;
+ this.mInitOrder = initOrder;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public String getJarPath() {
+ return mJarPath;
+ }
+
+ public int getInitOrder() {
+ return mInitOrder;
+ }
+
+ @Override
+ public int compareTo(ApexSystemServiceInfo other) {
+ if (mInitOrder == other.mInitOrder) {
+ return mName.compareTo(other.mName);
+ }
+ // higher initOrder values take precedence
+ return -Integer.compare(mInitOrder, other.mInitOrder);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 9302aad..14c0761 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1969,14 +1969,15 @@
reconciledPkg.mPrepareResult.mExistingPackage.getPackageName());
if ((reconciledPkg.mInstallArgs.mInstallFlags & PackageManager.DONT_KILL_APP)
== 0) {
- if (ps1.getOldCodePaths() == null) {
- ps1.setOldCodePaths(new ArraySet<>());
+ Set<String> oldCodePaths = ps1.getOldCodePaths();
+ if (oldCodePaths == null) {
+ oldCodePaths = new ArraySet<>();
}
- Collections.addAll(ps1.getOldCodePaths(), oldPackage.getBaseApkPath());
+ Collections.addAll(oldCodePaths, oldPackage.getBaseApkPath());
if (oldPackage.getSplitCodePaths() != null) {
- Collections.addAll(ps1.getOldCodePaths(),
- oldPackage.getSplitCodePaths());
+ Collections.addAll(oldCodePaths, oldPackage.getSplitCodePaths());
}
+ ps1.setOldCodePaths(oldCodePaths);
} else {
ps1.setOldCodePaths(null);
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 8ebd254..b3bb26c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -513,19 +513,25 @@
if (!valid) {
Slog.w(TAG, "Remove old session: " + session.sessionId);
// Remove expired sessions as well as child sessions if any
- mSessions.remove(session.sessionId);
- // Since this is early during boot we don't send
- // any observer events about the session, but we
- // keep details around for dumpsys.
- addHistoricalSessionLocked(session);
- for (PackageInstallerSession child : session.getChildSessions()) {
- mSessions.remove(child.sessionId);
- addHistoricalSessionLocked(child);
- }
+ removeActiveSession(session);
}
}
}
+ /**
+ * Moves a session (including the child sessions) from mSessions to mHistoricalSessions.
+ * This should only be called on a root session.
+ */
+ @GuardedBy("mSessions")
+ private void removeActiveSession(PackageInstallerSession session) {
+ mSessions.remove(session.sessionId);
+ addHistoricalSessionLocked(session);
+ for (PackageInstallerSession child : session.getChildSessions()) {
+ mSessions.remove(child.sessionId);
+ addHistoricalSessionLocked(child);
+ }
+ }
+
@GuardedBy("mSessions")
private void addHistoricalSessionLocked(PackageInstallerSession session) {
CharArrayWriter writer = new CharArrayWriter();
@@ -1654,10 +1660,11 @@
mStagingManager.abortSession(session.mStagedSession);
}
synchronized (mSessions) {
- if (!session.isStaged() || !success) {
- mSessions.remove(session.sessionId);
+ // Child sessions will be removed along with its parent as a whole
+ if (!session.hasParentSessionId()
+ && (!session.isStaged() || session.isDestroyed())) {
+ removeActiveSession(session);
}
- addHistoricalSessionLocked(session);
final File appIconFile = buildAppIconFile(session.sessionId);
if (appIconFile.exists()) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 304ad72..c813317 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -27,6 +27,7 @@
import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
import static android.content.pm.PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SPLIT;
+import static android.content.pm.PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
import static android.content.pm.PackageManager.INSTALL_STAGED;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
@@ -2097,10 +2098,10 @@
} else {
// Session is sealed and committed but could not be verified, we need to destroy it.
destroy();
- // Dispatch message to remove session from PackageInstallerService.
- dispatchSessionFinished(error, msg, null);
- maybeFinishChildSessions(error, msg);
}
+ // Dispatch message to remove session from PackageInstallerService.
+ dispatchSessionFinished(error, msg, null);
+ maybeFinishChildSessions(error, msg);
}
private void onSessionInstallationFailure(int error, String detailedMessage) {
@@ -2301,7 +2302,7 @@
if (params.isStaged) {
// TODO(b/136257624): CTS test fails if we don't send session finished broadcast, even
// though ideally, we just need to send session committed broadcast.
- dispatchSessionFinished(INSTALL_SUCCEEDED, "Session staged", null);
+ sendUpdateToRemoteStatusReceiver(INSTALL_SUCCEEDED, "Session staged", null);
mStagedSession.verifySession();
} else {
@@ -2549,6 +2550,9 @@
mStagedSession.notifyEndPreRebootVerification();
if (error == SessionInfo.SESSION_NO_ERROR) {
mStagingManager.commitSession(mStagedSession);
+ } else {
+ dispatchSessionFinished(INSTALL_FAILED_VERIFICATION_FAILURE, msg, null);
+ maybeFinishChildSessions(INSTALL_FAILED_VERIFICATION_FAILURE, msg);
}
});
return;
@@ -2578,7 +2582,7 @@
// Do not try to install staged apex session. Parent session will have at least one apk
// session.
if (!isMultiPackage() && isApexSession() && params.isStaged) {
- sendUpdateToRemoteStatusReceiver(INSTALL_SUCCEEDED,
+ dispatchSessionFinished(INSTALL_SUCCEEDED,
"Apex package should have been installed by apexd", null);
return null;
}
@@ -2592,14 +2596,12 @@
@Override
public void onPackageInstalled(String basePackageName, int returnCode, String msg,
Bundle extras) {
- if (isStaged()) {
- sendUpdateToRemoteStatusReceiver(returnCode, msg, extras);
- } else {
+ if (!isStaged()) {
// We've reached point of no return; call into PMS to install the stage.
// Regardless of success or failure we always destroy session.
destroyInternal();
- dispatchSessionFinished(returnCode, msg, extras);
}
+ dispatchSessionFinished(returnCode, msg, extras);
}
};
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index 4bcc2a3..f87063a 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -56,27 +56,6 @@
]
},
{
- "name": "CtsAppSecurityHostTestCases",
- "options": [
- {
- "include-filter": "android.appsecurity.cts.PrivilegedUpdateTests"
- }
- ]
- },
- {
- "name": "CtsAppSecurityHostTestCases",
- "file_patterns": [
- "core/java/.*Install.*",
- "services/core/.*Install.*",
- "services/core/java/com/android/server/pm/.*"
- ],
- "options": [
- {
- "include-filter": "android.appsecurity.cts.SplitTests"
- }
- ]
- },
- {
"name": "PackageManagerServiceHostTests",
"options": [
{
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 7f50cd6..07a5849 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -3435,7 +3435,8 @@
convertTimeZoneSuggestionToProtoBytes(
metricsState.getLatestTelephonySuggestion()),
convertTimeZoneSuggestionToProtoBytes(
- metricsState.getLatestGeolocationSuggestion())
+ metricsState.getLatestGeolocationSuggestion()),
+ metricsState.isTelephonyTimeZoneFallbackSupported()
));
} catch (RuntimeException e) {
Slog.e(TAG, "Getting time zone detection state failed: ", e);
@@ -4587,7 +4588,8 @@
int matchContentFrameRateUserPreference =
displayManager.getMatchContentFrameRateUserPreference();
byte[] userDisabledHdrTypes = toBytes(displayManager.getUserDisabledHdrTypes());
- Display.Mode userPreferredDisplayMode = displayManager.getUserPreferredDisplayMode();
+ Display.Mode userPreferredDisplayMode =
+ displayManager.getGlobalUserPreferredDisplayMode();
int userPreferredWidth = userPreferredDisplayMode != null
? userPreferredDisplayMode.getPhysicalWidth() : -1;
int userPreferredHeight = userPreferredDisplayMode != null
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 217fc09..4fc591d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2781,7 +2781,10 @@
if (displayMetricsChanged || physicalDisplayChanged) {
if (physicalDisplayChanged) {
// Reapply the window settings as the underlying physical display has changed.
- mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this);
+ // Do not include rotation settings here, postpone them until the display
+ // metrics are updated as rotation settings might depend on them
+ mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this,
+ /* includeRotationSettings */ false);
}
// If there is an override set for base values - use it, otherwise use new values.
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index f0e8b8f..6e94dfed 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1122,6 +1122,7 @@
if (!mNavButtonForcedVisible) {
inOutFrame.inset(windowState.getLayoutingAttrs(
displayFrames.mRotation).providedInternalInsets);
+ inOutFrame.inset(win.mGivenContentInsets);
}
},
@@ -1190,9 +1191,12 @@
break;
}
mDisplayContent.setInsetProvider(insetsType, win, (displayFrames,
- windowState, inOutFrame) -> inOutFrame.inset(
- windowState.getLayoutingAttrs(displayFrames.mRotation)
- .providedInternalInsets), imeFrameProvider);
+ windowState, inOutFrame) -> {
+ inOutFrame.inset(
+ windowState.getLayoutingAttrs(displayFrames.mRotation)
+ .providedInternalInsets);
+ inOutFrame.inset(win.mGivenContentInsets);
+ }, imeFrameProvider);
mInsetsSourceWindowsExceptIme.add(win);
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index 8260fd6..483c799 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -244,6 +244,10 @@
}
void applySettingsToDisplayLocked(DisplayContent dc) {
+ applySettingsToDisplayLocked(dc, /* includeRotationSettings */ true);
+ }
+
+ void applySettingsToDisplayLocked(DisplayContent dc, boolean includeRotationSettings) {
final DisplayInfo displayInfo = dc.getDisplayInfo();
final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo);
@@ -282,6 +286,8 @@
boolean dontMoveToTop = settings.mDontMoveToTop != null
? settings.mDontMoveToTop : false;
dc.mDontMoveToTop = dontMoveToTop;
+
+ if (includeRotationSettings) applyRotationSettingsToDisplayLocked(dc);
}
void applyRotationSettingsToDisplayLocked(DisplayContent dc) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 5fcee9b..bd790ba 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -152,6 +152,7 @@
import com.android.server.os.SchedulingPolicyService;
import com.android.server.people.PeopleService;
import com.android.server.pm.ApexManager;
+import com.android.server.pm.ApexSystemServiceInfo;
import com.android.server.pm.CrossProfileAppsService;
import com.android.server.pm.DataLoaderManagerService;
import com.android.server.pm.DynamicCodeLoggingService;
@@ -220,8 +221,8 @@
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
+import java.util.List;
import java.util.Locale;
-import java.util.Map;
import java.util.Timer;
import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
@@ -1459,7 +1460,7 @@
// TelecomLoader hooks into classes with defined HFP logic,
// so check for either telephony or microphone.
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MICROPHONE) ||
- mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
t.traceBegin("StartTelecomLoaderService");
mSystemServiceManager.startService(TelecomLoaderService.class);
t.traceEnd();
@@ -1467,7 +1468,7 @@
t.traceBegin("StartTelephonyRegistry");
telephonyRegistry = new TelephonyRegistry(
- context, new TelephonyRegistry.ConfigurationProvider());
+ context, new TelephonyRegistry.ConfigurationProvider());
ServiceManager.addService("telephony.registry", telephonyRegistry);
t.traceEnd();
@@ -2998,7 +2999,9 @@
t.traceEnd();
t.traceBegin("MakeTelephonyRegistryReady");
try {
- if (telephonyRegistryF != null) telephonyRegistryF.systemRunning();
+ if (telephonyRegistryF != null) {
+ telephonyRegistryF.systemRunning();
+ }
} catch (Throwable e) {
reportWtf("Notifying TelephonyRegistry running", e);
}
@@ -3063,10 +3066,12 @@
*/
private void startApexServices(@NonNull TimingsTraceAndSlog t) {
t.traceBegin("startApexServices");
- Map<String, String> services = ApexManager.getInstance().getApexSystemServices();
- // TODO(satayev): introduce android:order for services coming the same apexes
- for (String name : new TreeSet<>(services.keySet())) {
- String jarPath = services.get(name);
+ // TODO(b/192880996): get the list from "android" package, once the manifest entries
+ // are migrated to system manifest.
+ List<ApexSystemServiceInfo> services = ApexManager.getInstance().getApexSystemServices();
+ for (ApexSystemServiceInfo info : services) {
+ String name = info.getName();
+ String jarPath = info.getJarPath();
t.traceBegin("starting " + name);
if (TextUtils.isEmpty(jarPath)) {
mSystemServiceManager.startService(name);
diff --git a/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp b/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp
index 16d6241..0a9b7b1 100644
--- a/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp
+++ b/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp
@@ -32,7 +32,7 @@
name: "test_com.android.server",
manifest: "manifest.json",
androidManifest: "AndroidManifest.xml",
- java_libs: ["FakeApexSystemService"],
+ java_libs: ["FakeApexSystemServices"],
file_contexts: ":apex.test-file_contexts",
key: "test_com.android.server.key",
updatable: false,
diff --git a/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml b/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml
index eb741ca..6bec284 100644
--- a/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml
+++ b/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml
@@ -21,21 +21,29 @@
<application android:hasCode="false" android:testOnly="true">
<apex-system-service
android:name="com.android.server.testing.FakeApexSystemService"
- android:path="/apex/test_com.android.server/javalib/FakeApexSystemService.jar"
- android:minSdkVersion="30"/>
+ android:path="/apex/test_com.android.server/javalib/FakeApexSystemServices.jar"
+ android:minSdkVersion="30"
+ />
+
+ <apex-system-service
+ android:name="com.android.server.testing.FakeApexSystemService2"
+ android:path="/apex/test_com.android.server/javalib/FakeApexSystemServices.jar"
+ android:minSdkVersion="30"
+ android:initOrder="1"
+ />
<!-- Always inactive system service, since maxSdkVersion is low -->
<apex-system-service
- android:name="com.android.apex.test.OldApexSystemService"
- android:path="/apex/com.android.apex.test/javalib/fake.jar"
+ android:name="com.android.server.testing.OldApexSystemService"
+ android:path="/apex/test_com.android.server/javalib/fake.jar"
android:minSdkVersion="1"
android:maxSdkVersion="1"
/>
<!-- Always inactive system service, since minSdkVersion is high -->
<apex-system-service
- android:name="com.android.apex.test.NewApexSystemService"
- android:path="/apex/com.android.apex.test/javalib/fake.jar"
+ android:name="com.android.server.testing.NewApexSystemService"
+ android:path="/apex/test_com.android.server/javalib/fake.jar"
android:minSdkVersion="999999"
/>
</application>
diff --git a/services/tests/apexsystemservices/service/Android.bp b/services/tests/apexsystemservices/services/Android.bp
similarity index 94%
rename from services/tests/apexsystemservices/service/Android.bp
rename to services/tests/apexsystemservices/services/Android.bp
index 9d04f39..477ea4c 100644
--- a/services/tests/apexsystemservices/service/Android.bp
+++ b/services/tests/apexsystemservices/services/Android.bp
@@ -8,7 +8,7 @@
}
java_library {
- name: "FakeApexSystemService",
+ name: "FakeApexSystemServices",
srcs: ["**/*.java"],
sdk_version: "system_server_current",
libs: [
diff --git a/services/tests/apexsystemservices/service/src/com/android/server/testing/FakeApexSystemService.java b/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService.java
similarity index 100%
rename from services/tests/apexsystemservices/service/src/com/android/server/testing/FakeApexSystemService.java
rename to services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService.java
diff --git a/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService2.java b/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService2.java
new file mode 100644
index 0000000..e83343b
--- /dev/null
+++ b/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService2.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.testing;
+
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.SystemService;
+
+/**
+ * A fake system service that just logs when it is started.
+ */
+public class FakeApexSystemService2 extends SystemService {
+
+ private static final String TAG = "FakeApexSystemService";
+
+ public FakeApexSystemService2(@NonNull Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ Log.d(TAG, "FakeApexSystemService2 onStart");
+ }
+}
diff --git a/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java b/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java
index 2b453a9..7ab7b6ed 100644
--- a/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java
+++ b/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java
@@ -37,6 +37,10 @@
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
+import java.util.Objects;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
@RunWith(DeviceJUnit4ClassRunner.class)
public class ApexSystemServicesTestCases extends BaseHostJUnit4Test {
@@ -67,7 +71,7 @@
}
@Test
- public void noApexSystemServerStartsWithoutApex() throws Exception {
+ public void testNoApexSystemServiceStartsWithoutApex() throws Exception {
mPreparer.reboot();
assertThat(getFakeApexSystemServiceLogcat())
@@ -75,7 +79,7 @@
}
@Test
- public void apexSystemServerStarts() throws Exception {
+ public void testApexSystemServiceStarts() throws Exception {
// Pre-install the apex
String apex = "test_com.android.server.apex";
mPreparer.pushResourceFile(apex, "/system/apex/" + apex);
@@ -86,9 +90,40 @@
.contains("FakeApexSystemService onStart");
}
+ @Test
+ public void testInitOrder() throws Exception {
+ // Pre-install the apex
+ String apex = "test_com.android.server.apex";
+ mPreparer.pushResourceFile(apex, "/system/apex/" + apex);
+ // Reboot activates the apex
+ mPreparer.reboot();
+
+ assertThat(getFakeApexSystemServiceLogcat().lines()
+ .map(ApexSystemServicesTestCases::getDebugMessage)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList()))
+ .containsExactly(
+ // Second service has a higher initOrder and must be started first
+ "FakeApexSystemService2 onStart",
+ "FakeApexSystemService onStart"
+ )
+ .inOrder();
+ }
+
private String getFakeApexSystemServiceLogcat() throws DeviceNotAvailableException {
return mDevice.executeAdbCommand("logcat", "-v", "brief", "-d", "FakeApexSystemService:D",
"*:S");
}
+ private static final Pattern DEBUG_MESSAGE =
+ Pattern.compile("(FakeApexSystemService[0-9]* onStart)");
+
+ private static String getDebugMessage(String logcatLine) {
+ return DEBUG_MESSAGE.matcher(logcatLine)
+ .results()
+ .map(m -> m.group(1))
+ .findFirst()
+ .orElse(null);
+ }
+
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index c7c0756..d79a833 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -26,6 +26,7 @@
import static org.testng.Assert.assertThrows;
import android.Manifest;
+import android.companion.virtual.VirtualDeviceParams;
import android.content.Context;
import android.content.ContextWrapper;
import android.graphics.Point;
@@ -84,7 +85,7 @@
mInputController = new InputController(new Object(), mNativeWrapperMock);
mDeviceImpl = new VirtualDeviceImpl(mContext,
/* association info */ null, new Binder(), /* uid */ 0, mInputController,
- (int associationId) -> {});
+ (int associationId) -> {}, new VirtualDeviceParams.Builder().build());
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
new file mode 100644
index 0000000..77f1e24
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.virtual;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.companion.virtual.VirtualDeviceParams;
+import android.os.Parcel;
+import android.os.UserHandle;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+public class VirtualDeviceParamsTest {
+
+ @Test
+ public void parcelable_shouldRecreateSuccessfully() {
+ VirtualDeviceParams originalParams = new VirtualDeviceParams.Builder()
+ .setLockState(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED)
+ .setUsersWithMatchingAccounts(Set.of(UserHandle.of(123), UserHandle.of(456)))
+ .build();
+ Parcel parcel = Parcel.obtain();
+ originalParams.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ VirtualDeviceParams params = VirtualDeviceParams.CREATOR.createFromParcel(parcel);
+ assertThat(params).isEqualTo(originalParams);
+ assertThat(params.getLockState()).isEqualTo(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED);
+ assertThat(params.getUsersWithMatchingAccounts())
+ .containsExactly(UserHandle.of(123), UserHandle.of(456));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
index 7f7c716..2f5993d1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -61,7 +61,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.Map;
+import java.util.List;
@SmallTest
@Presubmit
@@ -136,9 +136,10 @@
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
- Map<String, String> services = mApexManager.getApexSystemServices();
+ List<ApexSystemServiceInfo> services = mApexManager.getApexSystemServices();
assertThat(services).hasSize(1);
- assertThat(services).containsKey("com.android.apex.test.ApexSystemService");
+ assertThat(services.stream().map(ApexSystemServiceInfo::getName).findFirst().orElse(null))
+ .matches("com.android.apex.test.ApexSystemService");
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/wm/OWNERS b/services/tests/servicestests/src/com/android/server/wm/OWNERS
new file mode 100644
index 0000000..361760d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 6970005..2b131e1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -322,4 +322,26 @@
assertFalse(navBarSource.getFrame().isEmpty());
assertTrue(imeSource.getFrame().contains(navBarSource.getFrame()));
}
+
+ @UseTestDisplay(addWindows = { W_NAVIGATION_BAR })
+ @Test
+ public void testInsetsGivenContentFrame() {
+ final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = 1000;
+ displayInfo.logicalHeight = 2000;
+ displayInfo.rotation = ROTATION_0;
+
+ WindowManager.LayoutParams attrs = mNavBarWindow.mAttrs;
+ displayPolicy.addWindowLw(mNavBarWindow, attrs);
+ mNavBarWindow.setRequestedSize(attrs.width, attrs.height);
+ mNavBarWindow.getControllableInsetProvider().setServerVisible(true);
+
+ mNavBarWindow.mGivenContentInsets.set(0, 10, 0, 0);
+
+ displayPolicy.layoutWindowLw(mNavBarWindow, null, mDisplayContent.mDisplayFrames);
+ final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
+ final InsetsSource navBarSource = state.peekSource(ITYPE_NAVIGATION_BAR);
+ assertEquals(attrs.height - 10, navBarSource.getFrame().height());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
index 9e4cd16..365e749 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -51,6 +51,7 @@
import com.android.server.LocalServices;
import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
import org.junit.Before;
import org.junit.Test;
@@ -447,6 +448,21 @@
assertEquals(456, config.densityDpi);
}
+ @Test
+ public void testDisplayRotationSettingsAppliedOnCreation() {
+ // Create new displays with different rotation settings
+ final SettingsEntry settingsEntry1 = new SettingsEntry();
+ settingsEntry1.mIgnoreOrientationRequest = false;
+ final DisplayContent dcDontIgnoreOrientation = createMockSimulatedDisplay(settingsEntry1);
+ final SettingsEntry settingsEntry2 = new SettingsEntry();
+ settingsEntry2.mIgnoreOrientationRequest = true;
+ final DisplayContent dcIgnoreOrientation = createMockSimulatedDisplay(settingsEntry2);
+
+ // Verify that newly created displays are created with correct rotation settings
+ assertFalse(dcDontIgnoreOrientation.getIgnoreOrientationRequest());
+ assertTrue(dcIgnoreOrientation.getIgnoreOrientationRequest());
+ }
+
public final class TestSettingsProvider implements DisplayWindowSettings.SettingsProvider {
Map<DisplayInfo, SettingsEntry> mOverrideSettingsCache = new HashMap<>();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index 3065e7d..8b0716c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -30,6 +30,7 @@
import static org.mockito.ArgumentMatchers.any;
+import android.annotation.Nullable;
import android.content.res.Configuration;
import android.graphics.Insets;
import android.graphics.Rect;
@@ -38,6 +39,8 @@
import android.view.DisplayCutout;
import android.view.DisplayInfo;
+import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
+
class TestDisplayContent extends DisplayContent {
public static final int DEFAULT_LOGICAL_DISPLAY_DENSITY = 300;
@@ -81,6 +84,7 @@
protected final ActivityTaskManagerService mService;
private boolean mSystemDecorations = false;
private int mStatusBarHeight = 0;
+ private SettingsEntry mOverrideSettings;
Builder(ActivityTaskManagerService service, int width, int height) {
mService = service;
@@ -104,6 +108,10 @@
private String generateUniqueId() {
return "TEST_DISPLAY_CONTENT_" + System.currentTimeMillis();
}
+ Builder setOverrideSettings(@Nullable SettingsEntry overrideSettings) {
+ mOverrideSettings = overrideSettings;
+ return this;
+ }
Builder setSystemDecorations(boolean yes) {
mSystemDecorations = yes;
return this;
@@ -151,6 +159,11 @@
TestDisplayContent build() {
SystemServicesTestRule.checkHoldsLock(mService.mGlobalLock);
+ if (mOverrideSettings != null) {
+ mService.mWindowManager.mDisplayWindowSettingsProvider
+ .updateOverrideSettings(mInfo, mOverrideSettings);
+ }
+
final int displayId = SystemServicesTestRule.sNextDisplayId++;
final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
mInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 34038c5..59a2068 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -106,6 +106,7 @@
import com.android.internal.policy.AttributeCache;
import com.android.internal.util.ArrayUtils;
+import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
import org.junit.After;
import org.junit.Before;
@@ -720,18 +721,21 @@
/** Creates a {@link DisplayContent} and adds it to the system. */
private DisplayContent createNewDisplayWithImeSupport(@DisplayImePolicy int imePolicy) {
- return createNewDisplay(mDisplayInfo, imePolicy);
+ return createNewDisplay(mDisplayInfo, imePolicy, /* overrideSettings */ null);
}
/** Creates a {@link DisplayContent} that supports IME and adds it to the system. */
DisplayContent createNewDisplay(DisplayInfo info) {
- return createNewDisplay(info, DISPLAY_IME_POLICY_LOCAL);
+ return createNewDisplay(info, DISPLAY_IME_POLICY_LOCAL, /* overrideSettings */ null);
}
/** Creates a {@link DisplayContent} and adds it to the system. */
- private DisplayContent createNewDisplay(DisplayInfo info, @DisplayImePolicy int imePolicy) {
+ private DisplayContent createNewDisplay(DisplayInfo info, @DisplayImePolicy int imePolicy,
+ @Nullable SettingsEntry overrideSettings) {
final DisplayContent display =
- new TestDisplayContent.Builder(mAtm, info).build();
+ new TestDisplayContent.Builder(mAtm, info)
+ .setOverrideSettings(overrideSettings)
+ .build();
final DisplayContent dc = display.mDisplayContent;
// this display can show IME.
dc.mWmService.mDisplayWindowSettings.setDisplayImePolicy(dc, imePolicy);
@@ -749,7 +753,7 @@
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.copyFrom(mDisplayInfo);
displayInfo.state = displayState;
- return createNewDisplay(displayInfo, DISPLAY_IME_POLICY_LOCAL);
+ return createNewDisplay(displayInfo, DISPLAY_IME_POLICY_LOCAL, /* overrideSettings */ null);
}
/** Creates a {@link TestWindowState} */
@@ -761,11 +765,15 @@
/** Creates a {@link DisplayContent} as parts of simulate display info for test. */
DisplayContent createMockSimulatedDisplay() {
+ return createMockSimulatedDisplay(/* overrideSettings */ null);
+ }
+
+ DisplayContent createMockSimulatedDisplay(@Nullable SettingsEntry overrideSettings) {
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.copyFrom(mDisplayInfo);
displayInfo.type = Display.TYPE_VIRTUAL;
displayInfo.ownerUid = SYSTEM_UID;
- return createNewDisplay(displayInfo, DISPLAY_IME_POLICY_FALLBACK_DISPLAY);
+ return createNewDisplay(displayInfo, DISPLAY_IME_POLICY_FALLBACK_DISPLAY, overrideSettings);
}
IDisplayWindowInsetsController createDisplayWindowInsetsController() {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 5ad95b8..0c56de8 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -12615,12 +12615,15 @@
if (carriers == null || !SubscriptionManager.isValidPhoneId(slotIndex)) {
return -1;
}
- // Execute the method setCarrierRestrictionRules with an empty excluded list and
- // indicating priority for the allowed list.
+ // Execute the method setCarrierRestrictionRules with an empty excluded list.
+ // If the allowed list is empty, it means that all carriers are allowed (default allowed),
+ // otherwise it means that only specified carriers are allowed (default not allowed).
CarrierRestrictionRules carrierRestrictionRules = CarrierRestrictionRules.newBuilder()
.setAllowedCarriers(carriers)
.setDefaultCarrierRestriction(
- CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED)
+ carriers.isEmpty()
+ ? CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_ALLOWED
+ : CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED)
.build();
int result = setCarrierRestrictionRules(carrierRestrictionRules);
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index ba95841..39ab7eb 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -592,6 +592,7 @@
int RIL_UNSOL_UNTHROTTLE_APN = 1052;
int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED = 1053;
int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED = 1054;
+ int RIL_UNSOL_SLICING_CONFIG_CHANGED = 1055;
/* The following unsols are not defined in RIL.h */
int RIL_UNSOL_HAL_NON_RIL_BASE = 1100;
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index dcb5c86..8f2803e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -261,7 +261,7 @@
testSpec.assertWm {
this.isAppWindowOnTop(LAUNCHER_COMPONENT)
.then()
- .isAppWindowVisible(FlickerComponentName.SNAPSHOT)
+ .isAppWindowVisible(FlickerComponentName.SNAPSHOT, isOptional = true)
.then()
.isAppWindowVisible(testApp.component)
}
@@ -342,4 +342,4 @@
)
}
}
-}
\ No newline at end of file
+}