Merge changes from topics "wifi-copy-xmlutils", "wifi-systemmessageproto"
* changes:
wifi-service: Remove jarjar of XmlUtils
Statically link SystemMessageProto into wifi-service
diff --git a/Android.bp b/Android.bp
index ff6ecb0..908274d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -486,7 +486,7 @@
"framework-minus-apex",
"updatable_media_stubs",
"framework_mediaprovider_stubs",
- "framework-appsearch", // TODO(b/146218515): should be framework-appsearch-stubs
+ "framework-appsearch-stubs",
"framework-sdkextensions-stubs-systemapi",
// TODO(b/146167933): Use framework-statsd-stubs instead.
"framework-statsd",
diff --git a/StubLibraries.bp b/StubLibraries.bp
index d195047..baa3c61 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -41,10 +41,9 @@
]
stubs_defaults {
- name: "metalava-api-stubs-default",
+ name: "metalava-non-updatable-api-stubs-default",
srcs: [
":framework-non-updatable-sources",
- ":framework-updatable-sources",
"core/java/**/*.logtags",
":opt-telephony-srcs",
":opt-net-voip-srcs",
@@ -64,14 +63,23 @@
"sdk-dir",
"api-versions-jars-dir",
],
- sdk_version: "core_platform",
filter_packages: packages_to_document,
}
+stubs_defaults {
+ name: "metalava-api-stubs-default",
+ defaults: ["metalava-non-updatable-api-stubs-default"],
+ srcs: [":framework-updatable-sources"],
+ sdk_version: "core_platform",
+}
+
/////////////////////////////////////////////////////////////////////
// *-api-stubs-docs modules providing source files for the stub libraries
/////////////////////////////////////////////////////////////////////
+// api-stubs-docs, system-api-stubs-docs, and test-api-stubs-docs have APIs
+// from the non-updatable part of the platform as well as from the updatable
+// modules
droidstubs {
name: "api-stubs-docs",
defaults: ["metalava-api-stubs-default"],
@@ -112,7 +120,10 @@
arg_files: [
"core/res/AndroidManifest.xml",
],
- args: metalava_framework_docs_args + " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\)",
+ args: metalava_framework_docs_args +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\)",
check_api: {
current: {
api_file: "api/system-current.txt",
@@ -155,6 +166,111 @@
}
/////////////////////////////////////////////////////////////////////
+// Following droidstubs modules are for extra APIs for modules.
+// The framework currently have two more API surfaces for modules:
+// @SystemApi(client=MODULE_APPS) and @SystemApi(client=MODULE_LIBRARIES)
+/////////////////////////////////////////////////////////////////////
+
+// TODO(b/146727827) remove the *-api modules when we can teach metalava
+// about the relationship among the API surfaces. Currently, these modules are only to generate
+// the API signature files and ensure that the APIs evolve in a backwards compatible manner.
+// They however are NOT used for building the API stub.
+droidstubs {
+ name: "module-app-api",
+ defaults: ["metalava-non-updatable-api-stubs-default"],
+ libs: ["framework-all"],
+ arg_files: ["core/res/AndroidManifest.xml"],
+ args: metalava_framework_docs_args +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\)",
+ check_api: {
+ current: {
+ api_file: "api/module-app-current.txt",
+ removed_api_file: "api/module-app-removed.txt",
+ },
+ // TODO(b/147559833) enable the compatibility check against the last release API
+ // and the API lint
+ //last_released: {
+ // api_file: ":last-released-module-app-api",
+ // removed_api_file: "api/module-app-removed.txt",
+ // baseline_file: ":module-app-api-incompatibilities-with-last-released"
+ //},
+ //api_lint: {
+ // enabled: true,
+ // new_since: ":last-released-module-app-api",
+ // baseline_file: "api/module-app-lint-baseline.txt",
+ //},
+ },
+ //jdiff_enabled: true,
+}
+
+droidstubs {
+ name: "module-lib-api",
+ defaults: ["metalava-non-updatable-api-stubs-default"],
+ libs: ["framework-all"],
+ arg_files: ["core/res/AndroidManifest.xml"],
+ args: metalava_framework_docs_args +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," +
+ "process=android.annotation.SystemApi.Process.ALL\\)",
+ check_api: {
+ current: {
+ api_file: "api/module-lib-current.txt",
+ removed_api_file: "api/module-lib-removed.txt",
+ },
+ // TODO(b/147559833) enable the compatibility check against the last release API
+ // and the API lint
+ //last_released: {
+ // api_file: ":last-released-module-lib-api",
+ // removed_api_file: "api/module-lib-removed.txt",
+ // baseline_file: ":module-lib-api-incompatibilities-with-last-released"
+ //},
+ //api_lint: {
+ // enabled: true,
+ // new_since: ":last-released-module-lib-api",
+ // baseline_file: "api/module-lib-lint-baseline.txt",
+ //},
+ },
+ //jdiff_enabled: true,
+}
+
+// The following two droidstubs modules generate source files for the API stub libraries for
+// modules. Note that they not only include their own APIs but also other APIs that have
+// narrower scope. For example, module-lib-api-stubs-docs includes all @SystemApis not just
+// the ones with 'client=MODULE_LIBRARIES'.
+droidstubs {
+ name: "module-app-api-stubs-docs",
+ defaults: ["metalava-non-updatable-api-stubs-default"],
+ libs: ["framework-all"],
+ arg_files: ["core/res/AndroidManifest.xml"],
+ args: metalava_framework_docs_args +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\)" +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\)",
+}
+
+droidstubs {
+ name: "module-lib-api-stubs-docs",
+ defaults: ["metalava-non-updatable-api-stubs-default"],
+ libs: ["framework-all"],
+ arg_files: ["core/res/AndroidManifest.xml"],
+ args: metalava_framework_docs_args +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\)" +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\)" +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," +
+ "process=android.annotation.SystemApi.Process.ALL\\)",
+}
+
+/////////////////////////////////////////////////////////////////////
// android_*_stubs_current modules are the stubs libraries compiled
// from *-api-stubs-docs
/////////////////////////////////////////////////////////////////////
@@ -169,7 +285,6 @@
java_resources: [
":notices-for-framework-stubs",
],
- sdk_version: "core_current",
system_modules: "none",
java_version: "1.8",
compile_dex: true,
@@ -187,6 +302,7 @@
"private-stub-annotations-jar",
],
defaults: ["framework-stubs-default"],
+ sdk_version: "core_current",
}
java_library_static {
@@ -201,6 +317,7 @@
"private-stub-annotations-jar",
],
defaults: ["framework-stubs-default"],
+ sdk_version: "core_current",
}
java_library_static {
@@ -215,6 +332,37 @@
"private-stub-annotations-jar",
],
defaults: ["framework-stubs-default"],
+ sdk_version: "core_current",
+}
+
+java_library_static {
+ name: "framework_module_app_stubs_current",
+ srcs: [
+ ":module-app-api-stubs-docs",
+ ],
+ libs: [
+ "stub-annotations",
+ "framework-all",
+ ],
+ static_libs: [
+ "private-stub-annotations-jar",
+ ],
+ defaults: ["framework-stubs-default"],
+}
+
+java_library_static {
+ name: "framework_module_lib_stubs_current",
+ srcs: [
+ ":module-lib-api-stubs-docs",
+ ],
+ libs: [
+ "stub-annotations",
+ "framework-all",
+ ],
+ static_libs: [
+ "private-stub-annotations-jar",
+ ],
+ defaults: ["framework-stubs-default"],
}
/////////////////////////////////////////////////////////////////////
diff --git a/apex/Android.bp b/apex/Android.bp
index 56f7db2..abebfa3 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -37,6 +37,36 @@
stubs_defaults {
name: "framework-module-stubs-defaults-systemapi",
- args: mainline_stubs_args + " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\) ",
+ args: mainline_stubs_args +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\) ",
+ installable: false,
+}
+
+stubs_defaults {
+ name: "framework-module-stubs-defaults-module_apps_api",
+ args: mainline_stubs_args +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\) " +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\) ",
+ installable: false,
+}
+
+stubs_defaults {
+ name: "framework-module-stubs-defaults-module_libs_api",
+ args: mainline_stubs_args +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\) " +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\) " +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," +
+ "process=android.annotation.SystemApi.Process.ALL\\) ",
installable: false,
}
diff --git a/apex/appsearch/framework/Android.bp b/apex/appsearch/framework/Android.bp
index 1f30dda..60cc3be 100644
--- a/apex/appsearch/framework/Android.bp
+++ b/apex/appsearch/framework/Android.bp
@@ -30,29 +30,10 @@
libs: [
"framework-minus-apex", // TODO(b/146218515) should be framework-system-stubs
],
- visibility: [
- "//frameworks/base/apex/appsearch:__subpackages__",
- // TODO(b/146218515) remove this when framework is built with the stub of appsearch
- "//frameworks/base",
- ],
+ visibility: ["//frameworks/base/apex/appsearch:__subpackages__"],
apex_available: ["com.android.appsearch"],
}
-metalava_appsearch_docs_args =
- "--hide-package com.android.server " +
- "--error UnhiddenSystemApi " +
- "--hide RequiresPermission " +
- "--hide MissingPermission " +
- "--hide BroadcastBehavior " +
- "--hide HiddenSuperclass " +
- "--hide DeprecationMismatch " +
- "--hide UnavailableSymbol " +
- "--hide SdkConstant " +
- "--hide HiddenTypeParameter " +
- "--hide Todo --hide Typo " +
- "--hide HiddenTypedefConstant " +
- "--show-annotation android.annotation.SystemApi "
-
droidstubs {
name: "framework-appsearch-stubs-srcs",
srcs: [
@@ -62,9 +43,8 @@
aidl: {
include_dirs: ["frameworks/base/core/java"],
},
- args: metalava_appsearch_docs_args,
- sdk_version: "core_current",
- libs: ["android_system_stubs_current"],
+ defaults: ["framework-module-stubs-defaults-systemapi"],
+ sdk_version: "system_current",
}
java_library {
@@ -75,7 +55,6 @@
"java",
],
},
- sdk_version: "core_current",
- libs: ["android_system_stubs_current"],
+ sdk_version: "system_current",
installable: false,
}
diff --git a/apex/appsearch/service/Android.bp b/apex/appsearch/service/Android.bp
index 4ebafce8..e7abcd9 100644
--- a/apex/appsearch/service/Android.bp
+++ b/apex/appsearch/service/Android.bp
@@ -20,8 +20,10 @@
libs: [
"framework",
"services.core",
+ "framework-appsearch",
],
static_libs: [
"icing-java-proto-lite",
- ]
+ ],
+ apex_available: [ "com.android.appsearch" ],
}
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 3f58c72..f8b2f32 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -761,6 +761,7 @@
@Override
public void onTrigger(TriggerEvent event) {
synchronized (DeviceIdleController.this) {
+ // One_shot sensors (which call onTrigger) are unregistered when onTrigger is called
active = false;
motionLocked();
}
@@ -769,6 +770,9 @@
@Override
public void onSensorChanged(SensorEvent event) {
synchronized (DeviceIdleController.this) {
+ // Since one_shot sensors are unregistered when onTrigger is called, unregister
+ // listeners here so that the MotionListener is in a consistent state when it calls
+ // out to motionLocked.
mSensorManager.unregisterListener(this, mMotionSensor);
active = false;
motionLocked();
diff --git a/apex/statsd/service/Android.bp b/apex/statsd/service/Android.bp
index f3a8989..9103848 100644
--- a/apex/statsd/service/Android.bp
+++ b/apex/statsd/service/Android.bp
@@ -13,4 +13,8 @@
"framework-minus-apex",
"services.core",
],
+ apex_available: [
+ "com.android.os.statsd",
+ "test_com.android.os.statsd",
+ ],
}
diff --git a/api/current.txt b/api/current.txt
index 8c751b1..00a5815 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2925,6 +2925,7 @@
method public int getShowMode();
method public boolean removeOnShowModeChangedListener(@NonNull android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener);
method public boolean setShowMode(int);
+ method public boolean switchToInputMethod(@NonNull String);
}
public static interface AccessibilityService.SoftKeyboardController.OnShowModeChangedListener {
@@ -6763,6 +6764,7 @@
method @NonNull public java.util.List<java.lang.String> getDelegatedScopes(@Nullable android.content.ComponentName, @NonNull String);
method public CharSequence getDeviceOwnerLockScreenInfo();
method public CharSequence getEndUserSessionMessage(@NonNull android.content.ComponentName);
+ method @Nullable public android.app.admin.FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(@Nullable android.content.ComponentName);
method @Nullable public String getGlobalPrivateDnsHost(@NonNull android.content.ComponentName);
method public int getGlobalPrivateDnsMode(@NonNull android.content.ComponentName);
method @NonNull public java.util.List<byte[]> getInstalledCaCerts(@Nullable android.content.ComponentName);
@@ -6881,6 +6883,7 @@
method public void setDelegatedScopes(@NonNull android.content.ComponentName, @NonNull String, @NonNull java.util.List<java.lang.String>);
method public void setDeviceOwnerLockScreenInfo(@NonNull android.content.ComponentName, CharSequence);
method public void setEndUserSessionMessage(@NonNull android.content.ComponentName, @Nullable CharSequence);
+ method public void setFactoryResetProtectionPolicy(@NonNull android.content.ComponentName, @Nullable android.app.admin.FactoryResetProtectionPolicy);
method public int setGlobalPrivateDnsModeOpportunistic(@NonNull android.content.ComponentName);
method @WorkerThread public int setGlobalPrivateDnsModeSpecifiedHost(@NonNull android.content.ComponentName, @NonNull String);
method public void setGlobalSetting(@NonNull android.content.ComponentName, String, String);
@@ -7115,6 +7118,21 @@
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DnsEvent> CREATOR;
}
+ public final class FactoryResetProtectionPolicy implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<java.lang.String> getFactoryResetProtectionAccounts();
+ method public boolean isFactoryResetProtectionDisabled();
+ method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.FactoryResetProtectionPolicy> CREATOR;
+ }
+
+ public static class FactoryResetProtectionPolicy.Builder {
+ ctor public FactoryResetProtectionPolicy.Builder();
+ method @NonNull public android.app.admin.FactoryResetProtectionPolicy build();
+ method @NonNull public android.app.admin.FactoryResetProtectionPolicy.Builder setFactoryResetProtectionAccounts(@NonNull java.util.List<java.lang.String>);
+ method @NonNull public android.app.admin.FactoryResetProtectionPolicy.Builder setFactoryResetProtectionDisabled(boolean);
+ }
+
public class FreezePeriod {
ctor public FreezePeriod(java.time.MonthDay, java.time.MonthDay);
method public java.time.MonthDay getEnd();
@@ -10008,6 +10026,7 @@
field public static final String MEDIA_ROUTER_SERVICE = "media_router";
field public static final String MEDIA_SESSION_SERVICE = "media_session";
field public static final String MIDI_SERVICE = "midi";
+ field public static final String MMS_SERVICE = "mms";
field public static final int MODE_APPEND = 32768; // 0x8000
field public static final int MODE_ENABLE_WRITE_AHEAD_LOGGING = 8; // 0x8
field @Deprecated public static final int MODE_MULTI_PROCESS = 4; // 0x4
@@ -17061,13 +17080,13 @@
method public abstract void close();
method @NonNull public abstract android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int) throws android.hardware.camera2.CameraAccessException;
method @NonNull public android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int, java.util.Set<java.lang.String>) throws android.hardware.camera2.CameraAccessException;
- method public abstract void createCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method @Deprecated public abstract void createCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public void createCaptureSession(android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException;
- method public abstract void createCaptureSessionByOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
- method public abstract void createConstrainedHighSpeedCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method @Deprecated public abstract void createCaptureSessionByOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method @Deprecated public abstract void createConstrainedHighSpeedCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method @NonNull public abstract android.hardware.camera2.CaptureRequest.Builder createReprocessCaptureRequest(@NonNull android.hardware.camera2.TotalCaptureResult) throws android.hardware.camera2.CameraAccessException;
- method public abstract void createReprocessableCaptureSession(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
- method public abstract void createReprocessableCaptureSessionByConfigurations(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method @Deprecated public abstract void createReprocessableCaptureSession(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method @Deprecated public abstract void createReprocessableCaptureSessionByConfigurations(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public int getCameraAudioRestriction() throws android.hardware.camera2.CameraAccessException;
method @NonNull public abstract String getId();
method public boolean isSessionConfigurationSupported(@NonNull android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException;
@@ -26297,6 +26316,59 @@
field public static final int SURFACE = 2; // 0x2
}
+ public final class MediaRoute2Info implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getConnectionState();
+ method @Nullable public CharSequence getDescription();
+ method public int getDeviceType();
+ method @Nullable public android.os.Bundle getExtras();
+ method @NonNull public java.util.List<java.lang.String> getFeatures();
+ method @Nullable public android.net.Uri getIconUri();
+ method @NonNull public String getId();
+ method @NonNull public CharSequence getName();
+ method public int getVolume();
+ method public int getVolumeHandling();
+ method public int getVolumeMax();
+ method public boolean hasAnyFeatures(@NonNull java.util.Collection<java.lang.String>);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2
+ field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1
+ field public static final int CONNECTION_STATE_DISCONNECTED = 0; // 0x0
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.MediaRoute2Info> CREATOR;
+ field public static final int DEVICE_TYPE_BLUETOOTH = 3; // 0x3
+ field public static final int DEVICE_TYPE_REMOTE_SPEAKER = 2; // 0x2
+ field public static final int DEVICE_TYPE_REMOTE_TV = 1; // 0x1
+ field public static final int DEVICE_TYPE_UNKNOWN = 0; // 0x0
+ field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
+ field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1
+ }
+
+ public static final class MediaRoute2Info.Builder {
+ ctor public MediaRoute2Info.Builder(@NonNull String, @NonNull CharSequence);
+ ctor public MediaRoute2Info.Builder(@NonNull android.media.MediaRoute2Info);
+ method @NonNull public android.media.MediaRoute2Info.Builder addFeature(@NonNull String);
+ method @NonNull public android.media.MediaRoute2Info.Builder addFeatures(@NonNull java.util.Collection<java.lang.String>);
+ method @NonNull public android.media.MediaRoute2Info build();
+ method @NonNull public android.media.MediaRoute2Info.Builder clearFeatures();
+ method @NonNull public android.media.MediaRoute2Info.Builder setClientPackageName(@Nullable String);
+ method @NonNull public android.media.MediaRoute2Info.Builder setConnectionState(int);
+ method @NonNull public android.media.MediaRoute2Info.Builder setDescription(@Nullable CharSequence);
+ method @NonNull public android.media.MediaRoute2Info.Builder setDeviceType(int);
+ method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle);
+ method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri);
+ method @NonNull public android.media.MediaRoute2Info.Builder setVolume(int);
+ method @NonNull public android.media.MediaRoute2Info.Builder setVolumeHandling(int);
+ method @NonNull public android.media.MediaRoute2Info.Builder setVolumeMax(int);
+ }
+
+ public abstract class MediaRoute2ProviderService extends android.app.Service {
+ ctor public MediaRoute2ProviderService();
+ method public final void notifyRoutes(@NonNull java.util.Collection<android.media.MediaRoute2Info>);
+ method @NonNull public android.os.IBinder onBind(@NonNull android.content.Intent);
+ method public void onDiscoveryPreferenceChanged(@NonNull android.media.RouteDiscoveryPreference);
+ field public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService";
+ }
+
public class MediaRouter {
method public void addCallback(int, android.media.MediaRouter.Callback);
method public void addCallback(int, android.media.MediaRouter.Callback, int);
@@ -26420,6 +26492,20 @@
method public abstract void onVolumeUpdateRequest(android.media.MediaRouter.RouteInfo, int);
}
+ public class MediaRouter2 {
+ method @NonNull public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context);
+ method @NonNull public java.util.List<android.media.MediaRoute2Info> getRoutes();
+ method public void registerRouteCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteCallback, @NonNull android.media.RouteDiscoveryPreference);
+ method public void unregisterRouteCallback(@NonNull android.media.MediaRouter2.RouteCallback);
+ }
+
+ public static class MediaRouter2.RouteCallback {
+ ctor public MediaRouter2.RouteCallback();
+ method public void onRoutesAdded(@NonNull java.util.List<android.media.MediaRoute2Info>);
+ method public void onRoutesChanged(@NonNull java.util.List<android.media.MediaRoute2Info>);
+ method public void onRoutesRemoved(@NonNull java.util.List<android.media.MediaRoute2Info>);
+ }
+
public class MediaScannerConnection implements android.content.ServiceConnection {
ctor public MediaScannerConnection(android.content.Context, android.media.MediaScannerConnection.MediaScannerConnectionClient);
method public void connect();
@@ -26778,6 +26864,22 @@
field public static final int URI_COLUMN_INDEX = 2; // 0x2
}
+ public final class RouteDiscoveryPreference implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<java.lang.String> getPreferredFeatures();
+ method public boolean isActiveScan();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteDiscoveryPreference> CREATOR;
+ }
+
+ public static final class RouteDiscoveryPreference.Builder {
+ ctor public RouteDiscoveryPreference.Builder(@NonNull java.util.List<java.lang.String>, boolean);
+ ctor public RouteDiscoveryPreference.Builder(@NonNull android.media.RouteDiscoveryPreference);
+ method @NonNull public android.media.RouteDiscoveryPreference build();
+ method @NonNull public android.media.RouteDiscoveryPreference.Builder setActiveScan(boolean);
+ method @NonNull public android.media.RouteDiscoveryPreference.Builder setPreferredFeatures(@NonNull java.util.List<java.lang.String>);
+ }
+
public final class Session2Command implements android.os.Parcelable {
ctor public Session2Command(int);
ctor public Session2Command(@NonNull String, @Nullable android.os.Bundle);
@@ -28702,7 +28804,7 @@
method public boolean isEncrypted();
method public boolean isHardOfHearing();
method public boolean isSpokenSubtitle();
- method public void writeToParcel(android.os.Parcel, int);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.TvTrackInfo> CREATOR;
field public static final int TYPE_AUDIO = 0; // 0x0
field public static final int TYPE_SUBTITLE = 2; // 0x2
@@ -28711,21 +28813,21 @@
public static final class TvTrackInfo.Builder {
ctor public TvTrackInfo.Builder(int, @NonNull String);
- method public android.media.tv.TvTrackInfo build();
- method public android.media.tv.TvTrackInfo.Builder setAudioChannelCount(int);
+ method @NonNull public android.media.tv.TvTrackInfo build();
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setAudioChannelCount(int);
method @NonNull public android.media.tv.TvTrackInfo.Builder setAudioDescription(boolean);
- method public android.media.tv.TvTrackInfo.Builder setAudioSampleRate(int);
- method public android.media.tv.TvTrackInfo.Builder setDescription(CharSequence);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setAudioSampleRate(int);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setDescription(@NonNull CharSequence);
method @NonNull public android.media.tv.TvTrackInfo.Builder setEncrypted(boolean);
- method public android.media.tv.TvTrackInfo.Builder setExtra(android.os.Bundle);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setExtra(@NonNull android.os.Bundle);
method @NonNull public android.media.tv.TvTrackInfo.Builder setHardOfHearing(boolean);
- method public android.media.tv.TvTrackInfo.Builder setLanguage(String);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setLanguage(@NonNull String);
method @NonNull public android.media.tv.TvTrackInfo.Builder setSpokenSubtitle(boolean);
- method public android.media.tv.TvTrackInfo.Builder setVideoActiveFormatDescription(byte);
- method public android.media.tv.TvTrackInfo.Builder setVideoFrameRate(float);
- method public android.media.tv.TvTrackInfo.Builder setVideoHeight(int);
- method public android.media.tv.TvTrackInfo.Builder setVideoPixelAspectRatio(float);
- method public android.media.tv.TvTrackInfo.Builder setVideoWidth(int);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoActiveFormatDescription(byte);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoFrameRate(float);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoHeight(int);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoPixelAspectRatio(float);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoWidth(int);
}
public class TvView extends android.view.ViewGroup {
@@ -30542,6 +30644,7 @@
field public static final String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK";
field public static final String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE";
field public static final String ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION = "android.net.wifi.action.WIFI_NETWORK_SUGGESTION_POST_CONNECTION";
+ field public static final String ACTION_WIFI_SCAN_AVAILABLE = "android.net.wifi.action.WIFI_SCAN_AVAILABLE";
field @Deprecated public static final int ERROR_AUTHENTICATING = 1; // 0x1
field @Deprecated public static final String EXTRA_BSSID = "bssid";
field public static final String EXTRA_NETWORK_INFO = "networkInfo";
@@ -30550,6 +30653,7 @@
field @Deprecated public static final String EXTRA_NEW_STATE = "newState";
field public static final String EXTRA_PREVIOUS_WIFI_STATE = "previous_wifi_state";
field public static final String EXTRA_RESULTS_UPDATED = "resultsUpdated";
+ field public static final String EXTRA_SCAN_AVAILABLE = "android.net.wifi.extra.SCAN_AVAILABLE";
field @Deprecated public static final String EXTRA_SUPPLICANT_CONNECTED = "connected";
field @Deprecated public static final String EXTRA_SUPPLICANT_ERROR = "supplicantError";
field @Deprecated public static final String EXTRA_WIFI_INFO = "wifiInfo";
@@ -42687,14 +42791,19 @@
method public android.content.Intent createReEnrollIntent();
method public android.content.Intent createUnEnrollIntent();
method public int getParameter(int);
+ method public int getSupportedAudioCapabilities();
method public int getSupportedRecognitionModes();
method @Nullable public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int);
method public int setParameter(int, int);
method public boolean startRecognition(int);
method public boolean stopRecognition();
+ field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1
+ field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2
field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0
field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2
field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1
+ field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4
+ field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8
field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2
field public static final int RECOGNITION_MODE_VOICE_TRIGGER = 1; // 0x1
field public static final int STATE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe
@@ -45053,7 +45162,7 @@
field public static final String KEY_ALLOW_LOCAL_DTMF_TONES_BOOL = "allow_local_dtmf_tones_bool";
field public static final String KEY_ALLOW_MERGE_WIFI_CALLS_WHEN_VOWIFI_OFF_BOOL = "allow_merge_wifi_calls_when_vowifi_off_bool";
field public static final String KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL = "allow_non_emergency_calls_in_ecm_bool";
- field public static final String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL = "always_show_emergency_alert_onoff_bool";
+ field @Deprecated public static final String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL = "always_show_emergency_alert_onoff_bool";
field public static final String KEY_APN_EXPAND_BOOL = "apn_expand_bool";
field public static final String KEY_AUTO_RETRY_ENABLED_BOOL = "auto_retry_enabled_bool";
field public static final String KEY_CALL_BARRING_SUPPORTS_DEACTIVATE_ALL_BOOL = "call_barring_supports_deactivate_all_bool";
@@ -45544,6 +45653,11 @@
method @Nullable public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, @NonNull java.util.concurrent.Executor, android.telephony.mbms.StreamingServiceCallback);
}
+ public class MmsManager {
+ method public void downloadMultimediaMessage(int, @NonNull String, @NonNull android.net.Uri, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent);
+ method public void sendMultimediaMessage(int, @NonNull android.net.Uri, @Nullable String, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent);
+ }
+
@Deprecated public class NeighboringCellInfo implements android.os.Parcelable {
ctor @Deprecated public NeighboringCellInfo();
ctor @Deprecated public NeighboringCellInfo(int, int);
@@ -45781,8 +45895,8 @@
method public String createAppSpecificSmsToken(android.app.PendingIntent);
method @Nullable public String createAppSpecificSmsTokenWithPackageInfo(@Nullable String, @NonNull android.app.PendingIntent);
method public java.util.ArrayList<java.lang.String> divideMessage(String);
- method public void downloadMultimediaMessage(android.content.Context, String, android.net.Uri, android.os.Bundle, android.app.PendingIntent);
- method @Nullable public android.os.Bundle getCarrierConfigValues();
+ method @Deprecated public void downloadMultimediaMessage(android.content.Context, String, android.net.Uri, android.os.Bundle, android.app.PendingIntent);
+ method @NonNull public android.os.Bundle getCarrierConfigValues();
method public static android.telephony.SmsManager getDefault();
method public static int getDefaultSmsSubscriptionId();
method public static android.telephony.SmsManager getSmsManagerForSubscriptionId(int);
@@ -45791,7 +45905,7 @@
method public int getSubscriptionId();
method public void injectSmsPdu(byte[], String, android.app.PendingIntent);
method public void sendDataMessage(String, String, short, byte[], android.app.PendingIntent, android.app.PendingIntent);
- method public void sendMultimediaMessage(android.content.Context, android.net.Uri, String, android.os.Bundle, android.app.PendingIntent);
+ method @Deprecated public void sendMultimediaMessage(android.content.Context, android.net.Uri, String, android.os.Bundle, android.app.PendingIntent);
method public void sendMultipartTextMessage(String, String, java.util.ArrayList<java.lang.String>, java.util.ArrayList<android.app.PendingIntent>, java.util.ArrayList<android.app.PendingIntent>);
method public void sendTextMessage(String, String, String, android.app.PendingIntent, android.app.PendingIntent);
method @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.SEND_SMS}) public void sendTextMessageWithoutPersisting(String, String, String, android.app.PendingIntent, android.app.PendingIntent);
diff --git a/api/module-app-current.txt b/api/module-app-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/api/module-app-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/api/module-app-removed.txt b/api/module-app-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/api/module-app-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/api/module-lib-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/api/module-lib-removed.txt b/api/module-lib-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/api/module-lib-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/api/system-current.txt b/api/system-current.txt
index 1c5d763b..8e3b11d 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -190,6 +190,7 @@
field public static final String REVIEW_ACCESSIBILITY_SERVICES = "android.permission.REVIEW_ACCESSIBILITY_SERVICES";
field public static final String REVOKE_RUNTIME_PERMISSIONS = "android.permission.REVOKE_RUNTIME_PERMISSIONS";
field public static final String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS";
+ field public static final String SECURE_ELEMENT_PRIVILEGED = "android.permission.SECURE_ELEMENT_PRIVILEGED";
field public static final String SEND_DEVICE_CUSTOMIZATION_READY = "android.permission.SEND_DEVICE_CUSTOMIZATION_READY";
field public static final String SEND_SHOW_SUSPENDED_APP_DETAILS = "android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS";
field public static final String SEND_SMS_NO_CONFIRMATION = "android.permission.SEND_SMS_NO_CONFIRMATION";
@@ -282,6 +283,7 @@
field public static final int config_helpIntentNameKey = 17039390; // 0x104001e
field public static final int config_helpPackageNameKey = 17039387; // 0x104001b
field public static final int config_helpPackageNameValue = 17039388; // 0x104001c
+ field public static final int config_systemGallery = 17039402; // 0x104002a
}
public static final class R.style {
@@ -812,7 +814,9 @@
field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED";
field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
field public static final String ACTION_PROVISION_FINALIZATION = "android.app.action.PROVISION_FINALIZATION";
+ 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 public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED = "android.app.action.RESET_PROTECTION_POLICY_CHANGED";
field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
field public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
@@ -1329,6 +1333,10 @@
field public static final String SERVICE_INTERFACE = "android.app.usage.CacheQuotaService";
}
+ public class NetworkStatsManager {
+ method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.net.netstats.provider.NetworkStatsProviderCallback registerNetworkStatsProvider(@NonNull String, @NonNull android.net.netstats.provider.AbstractNetworkStatsProvider);
+ }
+
public static final class UsageEvents.Event {
method public int getInstanceId();
method @Nullable public String getNotificationChannelId();
@@ -2407,7 +2415,7 @@
package android.hardware.camera2 {
public abstract class CameraDevice implements java.lang.AutoCloseable {
- method public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method @Deprecated public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
field public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED = 1; // 0x1
field public static final int SESSION_OPERATION_MODE_NORMAL = 0; // 0x0
field public static final int SESSION_OPERATION_MODE_VENDOR_START = 32768; // 0x8000
@@ -3566,7 +3574,10 @@
public static final class SoundTrigger.ModuleProperties implements android.os.Parcelable {
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
+ field public static final int CAPABILITY_ECHO_CANCELLATION = 1; // 0x1
+ field public static final int CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> CREATOR;
+ field public final int audioCapabilities;
field @NonNull public final String description;
field public final int id;
field @NonNull public final String implementor;
@@ -4362,6 +4373,8 @@
method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public boolean stopRecognition();
field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2
field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1
+ field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4
+ field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8
}
public abstract static class SoundTriggerDetector.Callback {
@@ -4611,6 +4624,28 @@
method public abstract int getType();
}
+ public class Lnb implements java.lang.AutoCloseable {
+ method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void close();
+ method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int sendDiseqcMessage(@NonNull byte[]);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setSatellitePosition(int);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setTone(int);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setVoltage(int);
+ field public static final int POSITION_A = 1; // 0x1
+ field public static final int POSITION_B = 2; // 0x2
+ field public static final int POSITION_UNDEFINED = 0; // 0x0
+ field public static final int TONE_CONTINUOUS = 1; // 0x1
+ field public static final int TONE_NONE = 0; // 0x0
+ field public static final int VOLTAGE_11V = 2; // 0x2
+ field public static final int VOLTAGE_12V = 3; // 0x3
+ field public static final int VOLTAGE_13V = 4; // 0x4
+ field public static final int VOLTAGE_14V = 5; // 0x5
+ field public static final int VOLTAGE_15V = 6; // 0x6
+ field public static final int VOLTAGE_18V = 7; // 0x7
+ field public static final int VOLTAGE_19V = 8; // 0x8
+ field public static final int VOLTAGE_5V = 1; // 0x1
+ field public static final int VOLTAGE_NONE = 0; // 0x0
+ }
+
public final class Tuner implements java.lang.AutoCloseable {
ctor public Tuner(@NonNull android.content.Context);
method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.Tuner.Descrambler openDescrambler();
@@ -4633,6 +4668,13 @@
field public static final int FILTER_STATUS_HIGH_WATER = 4; // 0x4
field public static final int FILTER_STATUS_LOW_WATER = 2; // 0x2
field public static final int FILTER_STATUS_OVERFLOW = 8; // 0x8
+ field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
+ field public static final int RESULT_INVALID_STATE = 3; // 0x3
+ field public static final int RESULT_NOT_INITIALIZED = 2; // 0x2
+ field public static final int RESULT_OUT_OF_MEMORY = 5; // 0x5
+ field public static final int RESULT_SUCCESS = 0; // 0x0
+ field public static final int RESULT_UNAVAILABLE = 1; // 0x1
+ field public static final int RESULT_UNKNOWN_ERROR = 6; // 0x6
}
}
@@ -5847,7 +5889,9 @@
public final class SoftApConfiguration implements android.os.Parcelable {
method public int describeContents();
+ method @NonNull public java.util.List<android.net.MacAddress> getAllowedClientList();
method public int getBand();
+ method @NonNull public java.util.List<android.net.MacAddress> getBlockedClientList();
method @Nullable public android.net.MacAddress getBssid();
method public int getChannel();
method public int getMaxNumberOfClients();
@@ -5855,6 +5899,7 @@
method public int getSecurityType();
method public int getShutdownTimeoutMillis();
method @Nullable public String getSsid();
+ method public boolean isClientControlByUserEnabled();
method public boolean isHiddenSsid();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field public static final int BAND_2GHZ = 1; // 0x1
@@ -5872,9 +5917,11 @@
ctor public SoftApConfiguration.Builder();
ctor public SoftApConfiguration.Builder(@NonNull android.net.wifi.SoftApConfiguration);
method @NonNull public android.net.wifi.SoftApConfiguration build();
+ method @NonNull public android.net.wifi.SoftApConfiguration.Builder enableClientControlByUser(boolean);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setBand(int);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setBssid(@Nullable android.net.MacAddress);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setChannel(int, int);
+ method @NonNull public android.net.wifi.SoftApConfiguration.Builder setClientList(@NonNull java.util.List<android.net.MacAddress>, @NonNull java.util.List<android.net.MacAddress>);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setHiddenSsid(boolean);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setMaxNumberOfClients(int);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setPassphrase(@Nullable String, int);
@@ -6113,6 +6160,8 @@
field public static final int IFACE_IP_MODE_UNSPECIFIED = -1; // 0xffffffff
field public static final int PASSPOINT_HOME_NETWORK = 0; // 0x0
field public static final int PASSPOINT_ROAMING_NETWORK = 1; // 0x1
+ field public static final int SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER = 0; // 0x0
+ field public static final int SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS = 1; // 0x1
field public static final int SAP_START_FAILURE_GENERAL = 0; // 0x0
field public static final int SAP_START_FAILURE_NO_CHANNEL = 1; // 0x1
field public static final int SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION = 2; // 0x2
@@ -6154,6 +6203,7 @@
}
public static interface WifiManager.SoftApCallback {
+ method public default void onBlockedClientConnecting(@NonNull android.net.wifi.WifiClient, int);
method public default void onCapabilityChanged(@NonNull android.net.wifi.SoftApCapability);
method public default void onConnectedClientsChanged(@NonNull java.util.List<android.net.wifi.WifiClient>);
method public default void onInfoChanged(@NonNull android.net.wifi.SoftApInfo);
@@ -8061,7 +8111,6 @@
field @NonNull public static final String ENABLE_CMAS_PRESIDENTIAL_PREF = "enable_cmas_presidential_alerts";
field @NonNull public static final String ENABLE_CMAS_SEVERE_THREAT_PREF = "enable_cmas_severe_threat_alerts";
field @NonNull public static final String ENABLE_EMERGENCY_PERF = "enable_emergency_alerts";
- field @NonNull public static final String ENABLE_FULL_VOLUME_PREF = "use_full_volume";
field @NonNull public static final String ENABLE_PUBLIC_SAFETY_PREF = "enable_public_safety_messages";
field @NonNull public static final String ENABLE_STATE_LOCAL_TEST_PREF = "enable_state_local_test_alerts";
field @NonNull public static final String ENABLE_TEST_ALERT_PREF = "enable_test_alerts";
@@ -10510,7 +10559,6 @@
field public static final String ACTION_EMERGENCY_ASSISTANCE = "android.telephony.action.EMERGENCY_ASSISTANCE";
field public static final String ACTION_EMERGENCY_CALLBACK_MODE_CHANGED = "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED";
field public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED = "android.intent.action.EMERGENCY_CALL_STATE_CHANGED";
- field public static final String ACTION_NETWORK_SET_TIME = "android.telephony.action.NETWORK_SET_TIME";
field public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE = "com.android.omadm.service.CONFIGURATION_UPDATE";
field public static final String ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS = "android.telephony.action.SHOW_NOTICE_ECM_BLOCK_OTHERS";
field public static final String ACTION_SIM_APPLICATION_STATE_CHANGED = "android.telephony.action.SIM_APPLICATION_STATE_CHANGED";
diff --git a/api/test-current.txt b/api/test-current.txt
index a8f7b51..fd692d6 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -34,6 +34,7 @@
public static final class R.string {
field public static final int config_defaultAssistant = 17039393; // 0x1040021
field public static final int config_defaultDialer = 17039395; // 0x1040023
+ field public static final int config_systemGallery = 17039402; // 0x104002a
}
}
@@ -1020,7 +1021,7 @@
package android.hardware.camera2 {
public abstract class CameraDevice implements java.lang.AutoCloseable {
- method public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method @Deprecated public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
field public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED = 1; // 0x1
field public static final int SESSION_OPERATION_MODE_NORMAL = 0; // 0x0
field public static final int SESSION_OPERATION_MODE_VENDOR_START = 32768; // 0x8000
diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp
index 229628c..4074789 100644
--- a/cmds/idmap2/libidmap2/ResourceMapping.cpp
+++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp
@@ -33,6 +33,8 @@
namespace {
+#define REWRITE_PACKAGE(resid, package_id) \
+ (((resid)&0x00ffffffU) | (((uint32_t)(package_id)) << 24U))
#define EXTRACT_PACKAGE(resid) ((0xff000000 & (resid)) >> 24)
std::string ConcatPolicies(const std::vector<std::string>& policies) {
@@ -154,6 +156,7 @@
return Error("root element is not <overlay> tag");
}
+ const uint8_t target_package_id = target_package->GetPackageId();
const uint8_t overlay_package_id = overlay_package->GetPackageId();
auto overlay_it_end = root_it.end();
for (auto overlay_it = root_it.begin(); overlay_it != overlay_it_end; ++overlay_it) {
@@ -187,6 +190,9 @@
continue;
}
+ // Retrieve the compile-time resource id of the target resource.
+ target_id = REWRITE_PACKAGE(target_id, target_package_id);
+
if (overlay_resource->dataType == Res_value::TYPE_STRING) {
overlay_resource->data += string_pool_offset;
}
@@ -214,6 +220,7 @@
const AssetManager2* target_am, const AssetManager2* overlay_am,
const LoadedPackage* target_package, const LoadedPackage* overlay_package) {
ResourceMapping resource_mapping;
+ const uint8_t target_package_id = target_package->GetPackageId();
const auto end = overlay_package->end();
for (auto iter = overlay_package->begin(); iter != end; ++iter) {
const ResourceId overlay_resid = *iter;
@@ -225,11 +232,14 @@
// Find the resource with the same type and entry name within the target package.
const std::string full_name =
base::StringPrintf("%s:%s", target_package->GetPackageName().c_str(), name->c_str());
- const ResourceId target_resource = target_am->GetResourceId(full_name);
+ ResourceId target_resource = target_am->GetResourceId(full_name);
if (target_resource == 0U) {
continue;
}
+ // Retrieve the compile-time resource id of the target resource.
+ target_resource = REWRITE_PACKAGE(target_resource, target_package_id);
+
resource_mapping.AddMapping(target_resource, Res_value::TYPE_REFERENCE, overlay_resid,
/* rewrite_overlay_reference */ false);
}
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 2bacfbc..fb43783 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -4725,36 +4725,69 @@
}
message AggStats {
- optional int64 min = 1;
+ // These are all in byte resolution.
+ optional int64 min = 1 [deprecated = true];
+ optional int64 average = 2 [deprecated = true];
+ optional int64 max = 3 [deprecated = true];
- optional int64 average = 2;
-
- optional int64 max = 3;
+ // These are all in kilobyte resolution. Can fit in int32, so smaller on the wire than the above
+ // int64 fields.
+ optional int32 mean_kb = 4;
+ optional int32 max_kb = 5;
}
+// A reduced subset of process states; reducing the number of possible states allows more
+// aggressive device-side aggregation of statistics and hence reduces metric upload size.
+enum ProcessStateAggregated {
+ PROCESS_STATE_UNKNOWN = 0;
+ // Persistent system process.
+ PROCESS_STATE_PERSISTENT = 1;
+ // Top activity; actually any visible activity.
+ PROCESS_STATE_TOP = 2;
+ // Process binding to top or a foreground service.
+ PROCESS_STATE_BOUND_TOP_OR_FGS = 3;
+ // Processing running a foreground service.
+ PROCESS_STATE_FGS = 4;
+ // Important foreground process (ime, wallpaper, etc).
+ PROCESS_STATE_IMPORTANT_FOREGROUND = 5;
+ // Important background process.
+ PROCESS_STATE_BACKGROUND = 6;
+ // Process running a receiver.
+ PROCESS_STATE_RECEIVER = 7;
+ // All kinds of cached processes.
+ PROCESS_STATE_CACHED = 8;
+}
+
+// Next tag: 13
message ProcessStatsStateProto {
optional android.service.procstats.ScreenState screen_state = 1;
- optional android.service.procstats.MemoryState memory_state = 2;
+ optional android.service.procstats.MemoryState memory_state = 2 [deprecated = true];
// this enum list is from frameworks/base/core/java/com/android/internal/app/procstats/ProcessStats.java
// and not frameworks/base/core/java/android/app/ActivityManager.java
- optional android.service.procstats.ProcessState process_state = 3;
+ optional android.service.procstats.ProcessState process_state = 3 [deprecated = true];
+
+ optional ProcessStateAggregated process_state_aggregated = 10;
// Millisecond uptime duration spent in this state
- optional int64 duration_millis = 4;
+ optional int64 duration_millis = 4 [deprecated = true];
+ // Same as above, but with minute resolution so it fits into an int32.
+ optional int32 duration_minutes = 11;
// Millisecond elapsed realtime duration spent in this state
- optional int64 realtime_duration_millis = 9;
+ optional int64 realtime_duration_millis = 9 [deprecated = true];
+ // Same as above, but with minute resolution so it fits into an int32.
+ optional int32 realtime_duration_minutes = 12;
// # of samples taken
optional int32 sample_size = 5;
// PSS is memory reserved for this process
- optional AggStats pss = 6;
+ optional AggStats pss = 6 [deprecated = true];
// USS is memory shared between processes, divided evenly for accounting
- optional AggStats uss = 7;
+ optional AggStats uss = 7 [deprecated = true];
// RSS is memory resident for this process
optional AggStats rss = 8;
@@ -4779,7 +4812,7 @@
// PSS stats during cached kill
optional AggStats cached_pss = 3;
}
- optional Kill kill = 3;
+ optional Kill kill = 3 [deprecated = true];
// Time and memory spent in various states.
repeated ProcessStatsStateProto states = 5;
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index c0fee6e..e46840c 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -1547,6 +1547,34 @@
void onShowModeChanged(@NonNull SoftKeyboardController controller,
@SoftKeyboardShowMode int showMode);
}
+
+ /**
+ * Switches the current IME for the user for whom the service is enabled. The change will
+ * persist until the current IME is explicitly changed again, and may persist beyond the
+ * life cycle of the requesting service.
+ *
+ * @param imeId The ID of the input method to make current. This IME must be installed and
+ * enabled.
+ * @return {@code true} if the current input method was successfully switched to the input
+ * method by {@code imeId},
+ * {@code false} if the input method specified is not installed, not enabled, or
+ * otherwise not available to become the current IME
+ *
+ * @see android.view.inputmethod.InputMethodInfo#getId()
+ */
+ public boolean switchToInputMethod(@NonNull String imeId) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.switchToInputMethod(imeId);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ return false;
+ }
}
/**
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 4841781..656f87f 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -91,6 +91,8 @@
void setSoftKeyboardCallbackEnabled(boolean enabled);
+ boolean switchToInputMethod(String imeId);
+
boolean isAccessibilityButtonAvailable();
void sendGesture(int sequence, in ParceledListSlice gestureSteps);
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index aa11598..d23754e 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -482,19 +482,6 @@
}
}
- if (key.mOverlayDirs != null) {
- for (final String idmapPath : key.mOverlayDirs) {
- try {
- builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/,
- true /*overlay*/));
- } catch (IOException e) {
- Log.w(TAG, "failed to add overlay path " + idmapPath);
-
- // continue.
- }
- }
- }
-
if (key.mLibDirs != null) {
for (final String libDir : key.mLibDirs) {
if (libDir.endsWith(".apk")) {
@@ -513,6 +500,19 @@
}
}
+ if (key.mOverlayDirs != null) {
+ for (final String idmapPath : key.mOverlayDirs) {
+ try {
+ builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/,
+ true /*overlay*/));
+ } catch (IOException e) {
+ Log.w(TAG, "failed to add overlay path " + idmapPath);
+
+ // continue.
+ }
+ }
+ }
+
return builder.build();
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index ca3d0d7..96664eb 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -170,6 +170,7 @@
import android.service.persistentdata.PersistentDataBlockManager;
import android.service.vr.IVrManager;
import android.telecom.TelecomManager;
+import android.telephony.MmsManager;
import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.TelephonyRegistryManager;
import android.util.ArrayMap;
@@ -631,6 +632,13 @@
return new TelecomManager(ctx.getOuterContext());
}});
+ registerService(Context.MMS_SERVICE, MmsManager.class,
+ new CachedServiceFetcher<MmsManager>() {
+ @Override
+ public MmsManager createService(ContextImpl ctx) {
+ return new MmsManager(ctx.getOuterContext());
+ }});
+
registerService(Context.UI_MODE_SERVICE, UiModeManager.class,
new CachedServiceFetcher<UiModeManager>() {
@Override
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 2aac94c..69640b8 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -398,6 +398,42 @@
"android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
/**
+ * Activity action: Starts the provisioning flow which sets up a financed device.
+ *
+ * <p>During financed device provisioning, a device admin app is downloaded and set as the owner
+ * of the device. A device owner has full control over the device. The device owner can not be
+ * modified by the user.
+ *
+ * <p>A typical use case would be a device that is bought from the reseller through financing
+ * program.
+ *
+ * <p>An intent with this action can be sent only on an unprovisioned device.
+ *
+ * <p>Unlike {@link #ACTION_PROVISION_MANAGED_DEVICE}, the provisioning message can only be sent
+ * by a privileged app with the permission
+ * {@link android.Manifest.permission#DISPATCH_PROVISIONING_MESSAGE}.
+ *
+ * <p>The provisioning intent contains the following properties:
+ * <ul>
+ * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}</li>
+ * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_SUPPORT_URL}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_ORGANIZATION_NAME}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
+ * </ul>
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ @SystemApi
+ public static final String ACTION_PROVISION_FINANCED_DEVICE =
+ "android.app.action.PROVISION_FINANCED_DEVICE";
+
+ /**
* Activity action: Starts the provisioning flow which sets up a managed device.
* Must be started with {@link android.app.Activity#startActivityForResult(Intent, int)}.
*
@@ -864,6 +900,7 @@
* The name is displayed only during provisioning.
*
* <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+ * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
*
* @hide
*/
@@ -876,6 +913,7 @@
* during provisioning. If the url is not HTTPS, an error will be shown.
*
* <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+ * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
*
* @hide
*/
@@ -888,6 +926,7 @@
* as the app label of the package.
*
* <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+ * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
*
* @hide
*/
@@ -912,6 +951,7 @@
* {@link android.content.ClipData} of the intent too.
*
* <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+ * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
*
* @hide
*/
@@ -1371,6 +1411,16 @@
= "android.app.action.DEVICE_OWNER_CHANGED";
/**
+ * Broadcast action: sent when the factory reset protection (FRP) policy is changed.
+ *
+ * @see #setFactoryResetProtectionPolicy
+ * @hide
+ */
+ @SystemApi
+ public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED =
+ "android.app.action.RESET_PROTECTION_POLICY_CHANGED";
+
+ /**
* The ComponentName of the administrator component.
*
* @see #ACTION_ADD_DEVICE_ADMIN
@@ -4289,6 +4339,60 @@
}
/**
+ * Callable by device owner or profile owner of an organization-owned device, to set a
+ * factory reset protection (FRP) policy. When a new policy is set, the system
+ * notifies the FRP management agent of a policy change by broadcasting
+ * {@code ACTION_RESET_PROTECTION_POLICY_CHANGED}.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param policy the new FRP policy, or {@code null} to clear the current policy.
+ * @throws SecurityException if {@code admin} is not a device owner or a profile owner of
+ * an organization-owned device.
+ * @throws UnsupportedOperationException if factory reset protection is not
+ * supported on the device.
+ */
+ public void setFactoryResetProtectionPolicy(@NonNull ComponentName admin,
+ @Nullable FactoryResetProtectionPolicy policy) {
+ throwIfParentInstance("setFactoryResetProtectionPolicy");
+ if (mService != null) {
+ try {
+ mService.setFactoryResetProtectionPolicy(admin, policy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Callable by device owner or profile owner of an organization-owned device, to retrieve
+ * the current factory reset protection (FRP) policy set previously by
+ * {@link #setFactoryResetProtectionPolicy}.
+ * <p>
+ * This method can also be called by the FRP management agent on device, in which case,
+ * it can pass {@code null} as the ComponentName.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with or
+ * {@code null} if called by the FRP management agent on device.
+ * @return The current FRP policy object or {@code null} if no policy is set.
+ * @throws SecurityException if {@code admin} is not a device owner, a profile owner of
+ * an organization-owned device or the FRP management agent.
+ * @throws UnsupportedOperationException if factory reset protection is not
+ * supported on the device.
+ */
+ public @Nullable FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(
+ @Nullable ComponentName admin) {
+ throwIfParentInstance("getFactoryResetProtectionPolicy");
+ if (mService != null) {
+ try {
+ return mService.getFactoryResetProtectionPolicy(admin);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
* Called by an application that is administering the device to set the
* global proxy and exclusion list.
* <p>
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/core/java/android/app/admin/FactoryResetProtectionPolicy.aidl
similarity index 81%
copy from media/java/android/media/RouteSessionInfo.aidl
copy to core/java/android/app/admin/FactoryResetProtectionPolicy.aidl
index fb5d836..72e639a 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/core/java/android/app/admin/FactoryResetProtectionPolicy.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.media;
+package android.app.admin;
-parcelable RouteSessionInfo;
+parcelable FactoryResetProtectionPolicy;
diff --git a/core/java/android/app/admin/FactoryResetProtectionPolicy.java b/core/java/android/app/admin/FactoryResetProtectionPolicy.java
new file mode 100644
index 0000000..ed74779
--- /dev/null
+++ b/core/java/android/app/admin/FactoryResetProtectionPolicy.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.END_TAG;
+import static org.xmlpull.v1.XmlPullParser.TEXT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The factory reset protection policy determines which accounts can unlock a device that
+ * has gone through untrusted factory reset.
+ * <p>
+ * Only a device owner or profile owner of an organization-owned device can set a factory
+ * reset protection policy for the device by calling the {@code DevicePolicyManager} method
+ * {@link DevicePolicyManager#setFactoryResetProtectionPolicy(ComponentName,
+ * FactoryResetProtectionPolicy)}}.
+ *
+ * @see DevicePolicyManager#setFactoryResetProtectionPolicy
+ * @see DevicePolicyManager#getFactoryResetProtectionPolicy
+ */
+public final class FactoryResetProtectionPolicy implements Parcelable {
+
+ private static final String LOG_TAG = "FactoryResetProtectionPolicy";
+
+ private static final String KEY_FACTORY_RESET_PROTECTION_ACCOUNT =
+ "factory_reset_protection_account";
+ private static final String KEY_FACTORY_RESET_PROTECTION_DISABLED =
+ "factory_reset_protection_disabled";
+ private static final String ATTR_VALUE = "value";
+
+ private final List<String> mFactoryResetProtectionAccounts;
+ private final boolean mFactoryResetProtectionDisabled;
+
+ private FactoryResetProtectionPolicy(List<String> factoryResetProtectionAccounts,
+ boolean factoryResetProtectionDisabled) {
+ mFactoryResetProtectionAccounts = factoryResetProtectionAccounts;
+ mFactoryResetProtectionDisabled = factoryResetProtectionDisabled;
+ }
+
+ /**
+ * Get the list of accounts that can provision a device which has been factory reset.
+ */
+ public @NonNull List<String> getFactoryResetProtectionAccounts() {
+ return mFactoryResetProtectionAccounts;
+ }
+
+ /**
+ * Return whether factory reset protection for the device is disabled or not.
+ */
+ public boolean isFactoryResetProtectionDisabled() {
+ return mFactoryResetProtectionDisabled;
+ }
+
+ /**
+ * Builder class for {@link FactoryResetProtectionPolicy} objects.
+ */
+ public static class Builder {
+ private List<String> mFactoryResetProtectionAccounts;
+ private boolean mFactoryResetProtectionDisabled;
+
+ /**
+ * Initialize a new Builder to construct a {@link FactoryResetProtectionPolicy}.
+ */
+ public Builder() {
+ };
+
+ /**
+ * Sets which accounts can unlock a device that has been factory reset.
+ * <p>
+ * Once set, the consumer unlock flow will be disabled and only accounts in this list
+ * can unlock factory reset protection after untrusted factory reset.
+ * <p>
+ * It's up to the FRP management agent to interpret the {@code String} as account it
+ * supports. Please consult their relevant documentation for details.
+ *
+ * @param factoryResetProtectionAccounts list of accounts.
+ * @return the same Builder instance.
+ */
+ @NonNull
+ public Builder setFactoryResetProtectionAccounts(
+ @NonNull List<String> factoryResetProtectionAccounts) {
+ mFactoryResetProtectionAccounts = new ArrayList<>(factoryResetProtectionAccounts);
+ return this;
+ }
+
+ /**
+ * Sets whether factory reset protection is disabled or not.
+ * <p>
+ * Once disabled, factory reset protection will not kick in all together when the device
+ * goes through untrusted factory reset. This applies to both the consumer unlock flow and
+ * the admin account overrides via {@link #setFactoryResetProtectionAccounts}
+ *
+ * @param factoryResetProtectionDisabled Whether the policy is disabled or not.
+ * @return the same Builder instance.
+ */
+ @NonNull
+ public Builder setFactoryResetProtectionDisabled(boolean factoryResetProtectionDisabled) {
+ mFactoryResetProtectionDisabled = factoryResetProtectionDisabled;
+ return this;
+ }
+
+ /**
+ * Combines all of the attributes that have been set on this {@code Builder}
+ *
+ * @return a new {@link FactoryResetProtectionPolicy} object.
+ */
+ @NonNull
+ public FactoryResetProtectionPolicy build() {
+ return new FactoryResetProtectionPolicy(mFactoryResetProtectionAccounts,
+ mFactoryResetProtectionDisabled);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "FactoryResetProtectionPolicy{"
+ + "mFactoryResetProtectionAccounts=" + mFactoryResetProtectionAccounts
+ + ", mFactoryResetProtectionDisabled=" + mFactoryResetProtectionDisabled
+ + '}';
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, @Nullable int flags) {
+ int accountsCount = mFactoryResetProtectionAccounts.size();
+ dest.writeInt(accountsCount);
+ for (String account: mFactoryResetProtectionAccounts) {
+ dest.writeString(account);
+ }
+ dest.writeBoolean(mFactoryResetProtectionDisabled);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Creator<FactoryResetProtectionPolicy> CREATOR =
+ new Creator<FactoryResetProtectionPolicy>() {
+
+ @Override
+ public FactoryResetProtectionPolicy createFromParcel(Parcel in) {
+ List<String> factoryResetProtectionAccounts = new ArrayList<>();
+ int accountsCount = in.readInt();
+ for (int i = 0; i < accountsCount; i++) {
+ factoryResetProtectionAccounts.add(in.readString());
+ }
+ boolean factoryResetProtectionDisabled = in.readBoolean();
+
+ return new FactoryResetProtectionPolicy(factoryResetProtectionAccounts,
+ factoryResetProtectionDisabled);
+ }
+
+ @Override
+ public FactoryResetProtectionPolicy[] newArray(int size) {
+ return new FactoryResetProtectionPolicy[size];
+ }
+ };
+
+ /**
+ * Restore a previously saved FactoryResetProtectionPolicy from XML.
+ * <p>
+ * No validation is required on the reconstructed policy since the XML was previously
+ * created by the system server from a validated policy.
+ * @hide
+ */
+ @Nullable
+ public static FactoryResetProtectionPolicy readFromXml(@NonNull XmlPullParser parser) {
+ try {
+ boolean factoryResetProtectionDisabled = Boolean.parseBoolean(
+ parser.getAttributeValue(null, KEY_FACTORY_RESET_PROTECTION_DISABLED));
+
+ List<String> factoryResetProtectionAccounts = new ArrayList<>();
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != END_DOCUMENT
+ && (type != END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == END_TAG || type == TEXT) {
+ continue;
+ }
+ if (!parser.getName().equals(KEY_FACTORY_RESET_PROTECTION_ACCOUNT)) {
+ continue;
+ }
+ factoryResetProtectionAccounts.add(
+ parser.getAttributeValue(null, ATTR_VALUE));
+ }
+
+ return new FactoryResetProtectionPolicy(factoryResetProtectionAccounts,
+ factoryResetProtectionDisabled);
+ } catch (XmlPullParserException | IOException e) {
+ Log.w(LOG_TAG, "Reading from xml failed", e);
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ public void writeToXml(@NonNull XmlSerializer out) throws IOException {
+ out.attribute(null, KEY_FACTORY_RESET_PROTECTION_DISABLED,
+ Boolean.toString(mFactoryResetProtectionDisabled));
+ for (String account : mFactoryResetProtectionAccounts) {
+ out.startTag(null, KEY_FACTORY_RESET_PROTECTION_ACCOUNT);
+ out.attribute(null, ATTR_VALUE, account);
+ out.endTag(null, KEY_FACTORY_RESET_PROTECTION_ACCOUNT);
+ }
+ }
+
+}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 3eec46b..21c9eb5 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -24,6 +24,7 @@
import android.app.admin.SystemUpdateInfo;
import android.app.admin.SystemUpdatePolicy;
import android.app.admin.PasswordMetrics;
+import android.app.admin.FactoryResetProtectionPolicy;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
@@ -104,6 +105,9 @@
void wipeDataWithReason(int flags, String wipeReasonForUser, boolean parent);
+ void setFactoryResetProtectionPolicy(in ComponentName who, in FactoryResetProtectionPolicy policy);
+ FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(in ComponentName who);
+
ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList);
ComponentName getGlobalProxyAdmin(int userHandle);
void setRecommendedGlobalProxy(in ComponentName admin, in ProxyInfo proxyInfo);
diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java
index 4346d97..9c4a8f4 100644
--- a/core/java/android/app/usage/NetworkStatsManager.java
+++ b/core/java/android/app/usage/NetworkStatsManager.java
@@ -16,7 +16,10 @@
package android.app.usage;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.app.usage.NetworkStats.Bucket;
@@ -27,6 +30,9 @@
import android.net.INetworkStatsService;
import android.net.NetworkIdentity;
import android.net.NetworkTemplate;
+import android.net.netstats.provider.AbstractNetworkStatsProvider;
+import android.net.netstats.provider.NetworkStatsProviderCallback;
+import android.net.netstats.provider.NetworkStatsProviderWrapper;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
@@ -519,6 +525,34 @@
private DataUsageRequest request;
}
+ /**
+ * Registers a custom provider of {@link android.net.NetworkStats} to combine the network
+ * statistics that cannot be seen by the kernel to system. To unregister, invoke
+ * {@link NetworkStatsProviderCallback#unregister()}.
+ *
+ * @param tag a human readable identifier of the custom network stats provider.
+ * @param provider a custom implementation of {@link AbstractNetworkStatsProvider} that needs to
+ * be registered to the system.
+ * @return a {@link NetworkStatsProviderCallback}, which can be used to report events to the
+ * system.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ @NonNull public NetworkStatsProviderCallback registerNetworkStatsProvider(
+ @NonNull String tag,
+ @NonNull AbstractNetworkStatsProvider provider) {
+ try {
+ final NetworkStatsProviderWrapper wrapper = new NetworkStatsProviderWrapper(provider);
+ return new NetworkStatsProviderCallback(
+ mService.registerNetworkStatsProvider(tag, wrapper));
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ // Unreachable code, but compiler doesn't know about it.
+ return null;
+ }
+
private static NetworkTemplate createTemplate(int networkType, String subscriberId) {
final NetworkTemplate template;
switch (networkType) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 44b2df6..1b40a18 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3416,6 +3416,7 @@
TELEPHONY_SUBSCRIPTION_SERVICE,
CARRIER_CONFIG_SERVICE,
EUICC_SERVICE,
+ MMS_SERVICE,
TELECOM_SERVICE,
CLIPBOARD_SERVICE,
INPUT_METHOD_SERVICE,
@@ -3612,6 +3613,8 @@
* @see android.telephony.CarrierConfigManager
* @see #EUICC_SERVICE
* @see android.telephony.euicc.EuiccManager
+ * @see #MMS_SERVICE
+ * @see android.telephony.MmsManager
* @see #INPUT_METHOD_SERVICE
* @see android.view.inputmethod.InputMethodManager
* @see #UI_MODE_SERVICE
@@ -4288,6 +4291,15 @@
/**
* Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.telephony.MmsManager} to send/receive MMS messages.
+ *
+ * @see #getSystemService(String)
+ * @see android.telephony.MmsManager
+ */
+ public static final String MMS_SERVICE = "mms";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
* {@link android.content.ClipboardManager} for accessing and modifying
* the contents of the global clipboard.
*
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 32803ab..87acbc1 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -7803,7 +7803,7 @@
ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName);
}
ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state);
- ai.resourceDirs = state.overlayPaths;
+ ai.resourceDirs = state.getAllOverlayPaths();
ai.icon = (sUseRoundIcon && ai.roundIconRes != 0) ? ai.roundIconRes : ai.iconRes;
}
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index f0f6753..da7623a 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -46,6 +46,8 @@
import java.io.IOException;
import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Map;
import java.util.Objects;
/**
@@ -77,7 +79,9 @@
public ArraySet<String> disabledComponents;
public ArraySet<String> enabledComponents;
- public String[] overlayPaths;
+ private String[] overlayPaths;
+ private ArrayMap<String, String[]> sharedLibraryOverlayPaths; // Lib name to overlay paths
+ private String[] cachedOverlayPaths;
@UnsupportedAppUsage
public PackageUserState() {
@@ -112,9 +116,33 @@
enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents);
overlayPaths =
o.overlayPaths == null ? null : Arrays.copyOf(o.overlayPaths, o.overlayPaths.length);
+ if (o.sharedLibraryOverlayPaths != null) {
+ sharedLibraryOverlayPaths = new ArrayMap<>(o.sharedLibraryOverlayPaths);
+ }
harmfulAppWarning = o.harmfulAppWarning;
}
+ public String[] getOverlayPaths() {
+ return overlayPaths;
+ }
+
+ public void setOverlayPaths(String[] paths) {
+ overlayPaths = paths;
+ cachedOverlayPaths = null;
+ }
+
+ public Map<String, String[]> getSharedLibraryOverlayPaths() {
+ return sharedLibraryOverlayPaths;
+ }
+
+ public void setSharedLibraryOverlayPaths(String library, String[] paths) {
+ if (sharedLibraryOverlayPaths == null) {
+ sharedLibraryOverlayPaths = new ArrayMap<>();
+ }
+ sharedLibraryOverlayPaths.put(library, paths);
+ cachedOverlayPaths = null;
+ }
+
/**
* Test if this package is installed.
*/
@@ -235,6 +263,38 @@
return isComponentEnabled;
}
+ public String[] getAllOverlayPaths() {
+ if (overlayPaths == null && sharedLibraryOverlayPaths == null) {
+ return null;
+ }
+
+ if (cachedOverlayPaths != null) {
+ return cachedOverlayPaths;
+ }
+
+ final LinkedHashSet<String> paths = new LinkedHashSet<>();
+ if (overlayPaths != null) {
+ final int N = overlayPaths.length;
+ for (int i = 0; i < N; i++) {
+ paths.add(overlayPaths[i]);
+ }
+ }
+
+ if (sharedLibraryOverlayPaths != null) {
+ for (String[] libOverlayPaths : sharedLibraryOverlayPaths.values()) {
+ if (libOverlayPaths != null) {
+ final int N = libOverlayPaths.length;
+ for (int i = 0; i < N; i++) {
+ paths.add(libOverlayPaths[i]);
+ }
+ }
+ }
+ }
+
+ cachedOverlayPaths = paths.toArray(new String[0]);
+ return cachedOverlayPaths;
+ }
+
@Override
final public boolean equals(Object obj) {
if (!(obj instanceof PackageUserState)) {
diff --git a/core/java/android/content/pm/parsing/PackageInfoUtils.java b/core/java/android/content/pm/parsing/PackageInfoUtils.java
index f2cf9a4..73a8d2a 100644
--- a/core/java/android/content/pm/parsing/PackageInfoUtils.java
+++ b/core/java/android/content/pm/parsing/PackageInfoUtils.java
@@ -41,9 +41,11 @@
import android.content.pm.parsing.ComponentParseUtils.ParsedInstrumentation;
import android.content.pm.parsing.ComponentParseUtils.ParsedPermission;
import android.content.pm.parsing.ComponentParseUtils.ParsedPermissionGroup;
+import android.util.ArraySet;
import com.android.internal.util.ArrayUtils;
+import java.util.LinkedHashSet;
import java.util.Set;
/** @hide */
@@ -545,7 +547,7 @@
ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName);
}
ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state);
- ai.resourceDirs = state.overlayPaths;
+ ai.resourceDirs = state.getAllOverlayPaths();
ai.icon = (PackageParser.sUseRoundIcon && ai.roundIconRes != 0)
? ai.roundIconRes : ai.iconRes;
}
diff --git a/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java b/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java
index 07d2443..6ead64c 100644
--- a/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java
@@ -25,9 +25,12 @@
* A constrained high speed capture session for a {@link CameraDevice}, used for capturing high
* speed images from the {@link CameraDevice} for high speed video recording use case.
* <p>
- * A CameraHighSpeedCaptureSession is created by providing a set of target output surfaces to
- * {@link CameraDevice#createConstrainedHighSpeedCaptureSession}, Once created, the session is
- * active until a new session is created by the camera device, or the camera device is closed.
+ * A CameraConstrainedHighSpeedCaptureSession is created by providing a session configuration to
+ * {@link CameraDevice#createCaptureSession(SessionConfiguration)} with a type of
+ * {@link android.hardware.camera2.params.SessionConfiguration#SESSION_HIGH_SPEED}. The
+ * CameraCaptureSession returned from {@link CameraCaptureSession.StateCallback} can then be cast to
+ * a CameraConstrainedHighSpeedCaptureSession. Once created, the session is active until a new
+ * session is created by the camera device, or the camera device is closed.
* </p>
* <p>
* An active high speed capture session is a specialized capture session that is only targeted at
@@ -37,8 +40,8 @@
* accepts request lists created via {@link #createHighSpeedRequestList}, and the request list can
* only be submitted to this session via {@link CameraCaptureSession#captureBurst captureBurst}, or
* {@link CameraCaptureSession#setRepeatingBurst setRepeatingBurst}. See
- * {@link CameraDevice#createConstrainedHighSpeedCaptureSession} for more details of the
- * limitations.
+ * {@link CameraDevice#createCaptureSession(android.hardware.camera2.params.SessionConfiguration)}
+ * for more details of the limitations.
* </p>
* <p>
* Creating a session is an expensive operation and can take several hundred milliseconds, since it
@@ -50,13 +53,6 @@
* completed, then the {@link CameraCaptureSession.StateCallback#onConfigureFailed} is called, and
* the session will not become active.
* </p>
- * <!--
- * <p>
- * Any capture requests (repeating or non-repeating) submitted before the session is ready will be
- * queued up and will begin capture once the session becomes ready. In case the session cannot be
- * configured and {@link CameraCaptureSession.StateCallback#onConfigureFailed onConfigureFailed} is
- * called, all queued capture requests are discarded. </p>
- * -->
* <p>
* If a new session is created by the camera device, then the previous session is closed, and its
* associated {@link CameraCaptureSession.StateCallback#onClosed onClosed} callback will be
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index fb1ece2..cc06681 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -16,24 +16,22 @@
package android.hardware.camera2;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.IntDef;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import static android.hardware.camera2.ICameraDeviceUser.NORMAL_MODE;
-import static android.hardware.camera2.ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE;
import android.hardware.camera2.params.InputConfiguration;
-import android.hardware.camera2.params.StreamConfigurationMap;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
+import android.hardware.camera2.params.StreamConfigurationMap;
import android.os.Handler;
import android.view.Surface;
-import java.util.List;
-import java.util.Set;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.Set;
/**
* <p>The CameraDevice class is a representation of a single camera connected to an
@@ -220,6 +218,224 @@
* <p>Create a new camera capture session by providing the target output set of Surfaces to the
* camera device.</p>
*
+ * @param outputs The new set of Surfaces that should be made available as
+ * targets for captured image data.
+ * @param callback The callback to notify about the status of the new capture session.
+ * @param handler The handler on which the callback should be invoked, or {@code null} to use
+ * the current thread's {@link android.os.Looper looper}.
+ *
+ * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements,
+ * the callback is null, or the handler is null but the current
+ * thread has no looper.
+ * @throws CameraAccessException if the camera device is no longer connected or has
+ * encountered a fatal error
+ * @throws IllegalStateException if the camera device has been closed
+ *
+ * @see CameraCaptureSession
+ * @see StreamConfigurationMap#getOutputFormats()
+ * @see StreamConfigurationMap#getOutputSizes(int)
+ * @see StreamConfigurationMap#getOutputSizes(Class)
+ * @deprecated Please use @{link
+ * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+ * full set of configuration options available.
+ */
+ @Deprecated
+ public abstract void createCaptureSession(@NonNull List<Surface> outputs,
+ @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
+ throws CameraAccessException;
+
+ /**
+ * <p>Create a new camera capture session by providing the target output set of Surfaces and
+ * its corresponding surface configuration to the camera device.</p>
+ *
+ * @see #createCaptureSession
+ * @see OutputConfiguration
+ * @deprecated Please use @{link
+ * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+ * full set of configuration options available.
+ */
+ @Deprecated
+ public abstract void createCaptureSessionByOutputConfigurations(
+ List<OutputConfiguration> outputConfigurations,
+ CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
+ throws CameraAccessException;
+ /**
+ * Create a new reprocessable camera capture session by providing the desired reprocessing
+ * input Surface configuration and the target output set of Surfaces to the camera device.
+ *
+ * @param inputConfig The configuration for the input {@link Surface}
+ * @param outputs The new set of Surfaces that should be made available as
+ * targets for captured image data.
+ * @param callback The callback to notify about the status of the new capture session.
+ * @param handler The handler on which the callback should be invoked, or {@code null} to use
+ * the current thread's {@link android.os.Looper looper}.
+ *
+ * @throws IllegalArgumentException if the input configuration is null or not supported, the set
+ * of output Surfaces do not meet the requirements, the
+ * callback is null, or the handler is null but the current
+ * thread has no looper.
+ * @throws CameraAccessException if the camera device is no longer connected or has
+ * encountered a fatal error
+ * @throws IllegalStateException if the camera device has been closed
+ *
+ * @see #createCaptureSession
+ * @see CameraCaptureSession
+ * @see StreamConfigurationMap#getInputFormats
+ * @see StreamConfigurationMap#getInputSizes
+ * @see StreamConfigurationMap#getValidOutputFormatsForInput
+ * @see StreamConfigurationMap#getOutputSizes
+ * @see android.media.ImageWriter
+ * @see android.media.ImageReader
+ * @deprecated Please use @{link
+ * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+ * full set of configuration options available.
+ */
+ @Deprecated
+ public abstract void createReprocessableCaptureSession(@NonNull InputConfiguration inputConfig,
+ @NonNull List<Surface> outputs, @NonNull CameraCaptureSession.StateCallback callback,
+ @Nullable Handler handler)
+ throws CameraAccessException;
+
+ /**
+ * Create a new reprocessable camera capture session by providing the desired reprocessing
+ * input configuration and output {@link OutputConfiguration}
+ * to the camera device.
+ *
+ * @see #createReprocessableCaptureSession
+ * @see OutputConfiguration
+ * @deprecated Please use @{link
+ * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+ * full set of configuration options available.
+ */
+ @Deprecated
+ public abstract void createReprocessableCaptureSessionByConfigurations(
+ @NonNull InputConfiguration inputConfig,
+ @NonNull List<OutputConfiguration> outputs,
+ @NonNull CameraCaptureSession.StateCallback callback,
+ @Nullable Handler handler)
+ throws CameraAccessException;
+
+ /**
+ * <p>Create a new constrained high speed capture session.</p>
+ *
+ * @param outputs The new set of Surfaces that should be made available as
+ * targets for captured high speed image data.
+ * @param callback The callback to notify about the status of the new capture session.
+ * @param handler The handler on which the callback should be invoked, or {@code null} to use
+ * the current thread's {@link android.os.Looper looper}.
+ *
+ * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements,
+ * the callback is null, or the handler is null but the current
+ * thread has no looper, or the camera device doesn't support
+ * high speed video capability.
+ * @throws CameraAccessException if the camera device is no longer connected or has
+ * encountered a fatal error
+ * @throws IllegalStateException if the camera device has been closed
+ *
+ * @see #createCaptureSession
+ * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE
+ * @see StreamConfigurationMap#getHighSpeedVideoSizes
+ * @see StreamConfigurationMap#getHighSpeedVideoFpsRangesFor
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ * @see CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO
+ * @see CameraCaptureSession#captureBurst
+ * @see CameraCaptureSession#setRepeatingBurst
+ * @see CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList
+ * @deprecated Please use @{link
+ * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+ * full set of configuration options available.
+ */
+ @Deprecated
+ public abstract void createConstrainedHighSpeedCaptureSession(@NonNull List<Surface> outputs,
+ @NonNull CameraCaptureSession.StateCallback callback,
+ @Nullable Handler handler)
+ throws CameraAccessException;
+
+ /**
+ * Standard camera operation mode.
+ *
+ * @see #createCustomCaptureSession
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final int SESSION_OPERATION_MODE_NORMAL =
+ 0; // ICameraDeviceUser.NORMAL_MODE;
+
+ /**
+ * Constrained high-speed operation mode.
+ *
+ * @see #createCustomCaptureSession
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED =
+ 1; // ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE;
+
+ /**
+ * First vendor-specific operating mode
+ *
+ * @see #createCustomCaptureSession
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final int SESSION_OPERATION_MODE_VENDOR_START =
+ 0x8000; // ICameraDeviceUser.VENDOR_MODE_START;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"SESSION_OPERATION_MODE"}, value =
+ {SESSION_OPERATION_MODE_NORMAL,
+ SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED,
+ SESSION_OPERATION_MODE_VENDOR_START})
+ public @interface SessionOperatingMode {};
+
+ /**
+ * Create a new camera capture session with a custom operating mode.
+ *
+ * @param inputConfig The configuration for the input {@link Surface} if a reprocessing session
+ * is desired, or {@code null} otherwise.
+ * @param outputs The new set of {@link OutputConfiguration OutputConfigurations} that should be
+ * made available as targets for captured image data.
+ * @param operatingMode The custom operating mode to use; a nonnegative value, either a custom
+ * vendor value or one of the SESSION_OPERATION_MODE_* values.
+ * @param callback The callback to notify about the status of the new capture session.
+ * @param handler The handler on which the callback should be invoked, or {@code null} to use
+ * the current thread's {@link android.os.Looper looper}.
+ *
+ * @throws IllegalArgumentException if the input configuration is null or not supported, the set
+ * of output Surfaces do not meet the requirements, the
+ * callback is null, or the handler is null but the current
+ * thread has no looper.
+ * @throws CameraAccessException if the camera device is no longer connected or has
+ * encountered a fatal error
+ * @throws IllegalStateException if the camera device has been closed
+ *
+ * @see #createCaptureSession
+ * @see #createReprocessableCaptureSession
+ * @see CameraCaptureSession
+ * @see OutputConfiguration
+ * @deprecated Please use @{link
+ * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+ * full set of configuration options available.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ @Deprecated
+ public abstract void createCustomCaptureSession(
+ InputConfiguration inputConfig,
+ @NonNull List<OutputConfiguration> outputs,
+ @SessionOperatingMode int operatingMode,
+ @NonNull CameraCaptureSession.StateCallback callback,
+ @Nullable Handler handler)
+ throws CameraAccessException;
+
+ /**
+ * <p>Create a new {@link CameraCaptureSession} using a {@link SessionConfiguration} helper
+ * object that aggregates all supported parameters.</p>
* <p>The active capture session determines the set of potential output Surfaces for
* the camera device for each capture request. A given request may use all
* or only some of the outputs. Once the CameraCaptureSession is created, requests can be
@@ -308,11 +524,15 @@
* <p>Configuring a session with an empty or null list will close the current session, if
* any. This can be used to release the current session's target surfaces for another use.</p>
*
+ * <h3>Regular capture</h3>
+ *
* <p>While any of the sizes from {@link StreamConfigurationMap#getOutputSizes} can be used when
* a single output stream is configured, a given camera device may not be able to support all
* combination of sizes, formats, and targets when multiple outputs are configured at once. The
* tables below list the maximum guaranteed resolutions for combinations of streams and targets,
- * given the capabilities of the camera device.</p>
+ * given the capabilities of the camera device. These are valid for when the
+ * {@link android.hardware.camera2.params.SessionConfiguration#setInputConfiguration
+ * input configuration} is not set and therefore no reprocessing is active.</p>
*
* <p>If an application tries to create a session using a set of targets that exceed the limits
* described in the below tables, one of three possibilities may occur. First, the session may
@@ -488,56 +708,22 @@
* (either width or height) might not be supported, and capture session creation will fail if it
* is not.</p>
*
- * @param outputs The new set of Surfaces that should be made available as
- * targets for captured image data.
- * @param callback The callback to notify about the status of the new capture session.
- * @param handler The handler on which the callback should be invoked, or {@code null} to use
- * the current thread's {@link android.os.Looper looper}.
- *
- * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements,
- * the callback is null, or the handler is null but the current
- * thread has no looper.
- * @throws CameraAccessException if the camera device is no longer connected or has
- * encountered a fatal error
- * @throws IllegalStateException if the camera device has been closed
- *
- * @see CameraCaptureSession
- * @see StreamConfigurationMap#getOutputFormats()
- * @see StreamConfigurationMap#getOutputSizes(int)
- * @see StreamConfigurationMap#getOutputSizes(Class)
- */
- public abstract void createCaptureSession(@NonNull List<Surface> outputs,
- @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
- throws CameraAccessException;
-
- /**
- * <p>Create a new camera capture session by providing the target output set of Surfaces and
- * its corresponding surface configuration to the camera device.</p>
- *
- * @see #createCaptureSession
- * @see OutputConfiguration
- */
- public abstract void createCaptureSessionByOutputConfigurations(
- List<OutputConfiguration> outputConfigurations,
- CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
- throws CameraAccessException;
- /**
- * Create a new reprocessable camera capture session by providing the desired reprocessing
- * input Surface configuration and the target output set of Surfaces to the camera device.
+ * <h3>Reprocessing</h3>
*
* <p>If a camera device supports YUV reprocessing
* ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING}) or PRIVATE
* reprocessing
- * ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING}), besides
- * the capture session created via {@link #createCaptureSession createCaptureSession}, the
+ * ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING}), the
* application can also create a reprocessable capture session to submit reprocess capture
- * requests in addition to regular capture requests. A reprocess capture request takes the next
- * available buffer from the session's input Surface, and sends it through the camera device's
- * processing pipeline again, to produce buffers for the request's target output Surfaces. No
- * new image data is captured for a reprocess request. However the input buffer provided by
- * the application must be captured previously by the same camera device in the same session
- * directly (e.g. for Zero-Shutter-Lag use case) or indirectly (e.g. combining multiple output
- * images).</p>
+ * requests in addition to regular capture requests, by setting an
+ * {@link android.hardware.camera2.params.SessionConfiguration#setInputConfiguration
+ * input configuration} for the session. A reprocess capture request takes the next available
+ * buffer from the
+ * session's input Surface, and sends it through the camera device's processing pipeline again,
+ * to produce buffers for the request's target output Surfaces. No new image data is captured
+ * for a reprocess request. However the input buffer provided by the application must be
+ * captured previously by the same camera device in the same session directly (e.g. for
+ * Zero-Shutter-Lag use case) or indirectly (e.g. combining multiple output images).</p>
*
* <p>The active reprocessable capture session determines an input {@link Surface} and the set
* of potential output Surfaces for the camera devices for each capture request. The application
@@ -570,10 +756,7 @@
* <p>Starting from API level 30, recreating a reprocessable capture session will flush all the
* queued but not yet processed buffers from the input surface.</p>
*
- * <p>The guaranteed stream configurations listed in
- * {@link #createCaptureSession createCaptureSession} are also guaranteed to work for
- * {@link #createReprocessableCaptureSession createReprocessableCaptureSession}. In addition,
- * the configurations in the tables below are also guaranteed for creating a reprocessable
+ * <p>The configurations in the tables below are guaranteed for creating a reprocessable
* capture session if the camera device supports YUV reprocessing or PRIVATE reprocessing.
* However, not all output targets used to create a reprocessable session may be used in a
* {@link CaptureRequest} simultaneously. For devices that support only 1 output target in a
@@ -602,7 +785,7 @@
* <p>LIMITED-level ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL}
* {@code == }{@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED LIMITED}) devices
* support at least the following stream combinations for creating a reprocessable capture
- * session in addition to those listed in {@link #createCaptureSession createCaptureSession} for
+ * session in addition to those listed earlier for regular captures for
* {@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED LIMITED} devices:
*
* <table>
@@ -671,74 +854,30 @@
* </table><br>
* </p>
*
- * <p>Clients can access the above mandatory stream combination tables via
- * {@link android.hardware.camera2.params.MandatoryStreamCombination}.</p>
+ * <h3>Constrained high-speed recording</h3>
*
- * @param inputConfig The configuration for the input {@link Surface}
- * @param outputs The new set of Surfaces that should be made available as
- * targets for captured image data.
- * @param callback The callback to notify about the status of the new capture session.
- * @param handler The handler on which the callback should be invoked, or {@code null} to use
- * the current thread's {@link android.os.Looper looper}.
- *
- * @throws IllegalArgumentException if the input configuration is null or not supported, the set
- * of output Surfaces do not meet the requirements, the
- * callback is null, or the handler is null but the current
- * thread has no looper.
- * @throws CameraAccessException if the camera device is no longer connected or has
- * encountered a fatal error
- * @throws IllegalStateException if the camera device has been closed
- *
- * @see #createCaptureSession
- * @see CameraCaptureSession
- * @see StreamConfigurationMap#getInputFormats
- * @see StreamConfigurationMap#getInputSizes
- * @see StreamConfigurationMap#getValidOutputFormatsForInput
- * @see StreamConfigurationMap#getOutputSizes
- * @see android.media.ImageWriter
- * @see android.media.ImageReader
- */
- public abstract void createReprocessableCaptureSession(@NonNull InputConfiguration inputConfig,
- @NonNull List<Surface> outputs, @NonNull CameraCaptureSession.StateCallback callback,
- @Nullable Handler handler)
- throws CameraAccessException;
-
- /**
- * Create a new reprocessable camera capture session by providing the desired reprocessing
- * input configuration and output {@link OutputConfiguration}
- * to the camera device.
- *
- * @see #createReprocessableCaptureSession
- * @see OutputConfiguration
- *
- */
- public abstract void createReprocessableCaptureSessionByConfigurations(
- @NonNull InputConfiguration inputConfig,
- @NonNull List<OutputConfiguration> outputs,
- @NonNull CameraCaptureSession.StateCallback callback,
- @Nullable Handler handler)
- throws CameraAccessException;
-
- /**
- * <p>Create a new constrained high speed capture session.</p>
- *
- * <p>The application can use normal capture session (created via {@link #createCaptureSession})
+ * <p>The application can use a
+ * {@link android.hardware.camera2.params.SessionConfiguration#SESSION_REGULAR
+ * normal capture session}
* for high speed capture if the desired high speed FPS ranges are advertised by
* {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES}, in which case all API
* semantics associated with normal capture sessions applies.</p>
*
- * <p>The method creates a specialized capture session that is only targeted at high speed
- * video recording (>=120fps) use case if the camera device supports high speed video
- * capability (i.e., {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES} contains
- * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO}).
- * Therefore, it has special characteristics compared with a normal capture session:</p>
+ * <p>A
+ * {@link android.hardware.camera2.params.SessionConfiguration#SESSION_HIGH_SPEED
+ * high-speed capture session}
+ * can be use for high speed video recording (>=120fps) when the camera device supports high
+ * speed video capability (i.e., {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES}
+ * contains {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO}).
+ * A constrained high-speed capture session has special limitations compared with a normal
+ * capture session:</p>
*
* <ul>
*
- * <li>In addition to the output target Surface requirements specified by the
- * {@link #createCaptureSession} method, an active high speed capture session will support up
- * to 2 output Surfaces, though the application might choose to configure just one Surface
- * (e.g., preview only). All Surfaces must be either video encoder surfaces (acquired by
+ * <li>In addition to the output target Surface requirements specified above for regular
+ * captures, a high speed capture session will only support up to 2 output Surfaces, though
+ * the application might choose to configure just one Surface (e.g., preview only). All
+ * Surfaces must be either video encoder surfaces (acquired by
* {@link android.media.MediaRecorder#getSurface} or
* {@link android.media.MediaCodec#createInputSurface}) or preview surfaces (obtained from
* {@link android.view.SurfaceView}, {@link android.graphics.SurfaceTexture} via
@@ -774,116 +913,6 @@
*
* </ul>
*
- * @param outputs The new set of Surfaces that should be made available as
- * targets for captured high speed image data.
- * @param callback The callback to notify about the status of the new capture session.
- * @param handler The handler on which the callback should be invoked, or {@code null} to use
- * the current thread's {@link android.os.Looper looper}.
- *
- * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements,
- * the callback is null, or the handler is null but the current
- * thread has no looper, or the camera device doesn't support
- * high speed video capability.
- * @throws CameraAccessException if the camera device is no longer connected or has
- * encountered a fatal error
- * @throws IllegalStateException if the camera device has been closed
- *
- * @see #createCaptureSession
- * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE
- * @see StreamConfigurationMap#getHighSpeedVideoSizes
- * @see StreamConfigurationMap#getHighSpeedVideoFpsRangesFor
- * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
- * @see CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO
- * @see CameraCaptureSession#captureBurst
- * @see CameraCaptureSession#setRepeatingBurst
- * @see CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList
- */
- public abstract void createConstrainedHighSpeedCaptureSession(@NonNull List<Surface> outputs,
- @NonNull CameraCaptureSession.StateCallback callback,
- @Nullable Handler handler)
- throws CameraAccessException;
-
- /**
- * Standard camera operation mode.
- *
- * @see #createCustomCaptureSession
- * @hide
- */
- @SystemApi
- @TestApi
- public static final int SESSION_OPERATION_MODE_NORMAL =
- 0; // ICameraDeviceUser.NORMAL_MODE;
-
- /**
- * Constrained high-speed operation mode.
- *
- * @see #createCustomCaptureSession
- * @hide
- */
- @SystemApi
- @TestApi
- public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED =
- 1; // ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE;
-
- /**
- * First vendor-specific operating mode
- *
- * @see #createCustomCaptureSession
- * @hide
- */
- @SystemApi
- @TestApi
- public static final int SESSION_OPERATION_MODE_VENDOR_START =
- 0x8000; // ICameraDeviceUser.VENDOR_MODE_START;
-
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = {"SESSION_OPERATION_MODE"}, value =
- {SESSION_OPERATION_MODE_NORMAL,
- SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED,
- SESSION_OPERATION_MODE_VENDOR_START})
- public @interface SessionOperatingMode {};
-
- /**
- * Create a new camera capture session with a custom operating mode.
- *
- * @param inputConfig The configuration for the input {@link Surface} if a reprocessing session
- * is desired, or {@code null} otherwise.
- * @param outputs The new set of {@link OutputConfiguration OutputConfigurations} that should be
- * made available as targets for captured image data.
- * @param operatingMode The custom operating mode to use; a nonnegative value, either a custom
- * vendor value or one of the SESSION_OPERATION_MODE_* values.
- * @param callback The callback to notify about the status of the new capture session.
- * @param handler The handler on which the callback should be invoked, or {@code null} to use
- * the current thread's {@link android.os.Looper looper}.
- *
- * @throws IllegalArgumentException if the input configuration is null or not supported, the set
- * of output Surfaces do not meet the requirements, the
- * callback is null, or the handler is null but the current
- * thread has no looper.
- * @throws CameraAccessException if the camera device is no longer connected or has
- * encountered a fatal error
- * @throws IllegalStateException if the camera device has been closed
- *
- * @see #createCaptureSession
- * @see #createReprocessableCaptureSession
- * @see CameraCaptureSession
- * @see OutputConfiguration
- * @hide
- */
- @SystemApi
- @TestApi
- public abstract void createCustomCaptureSession(
- InputConfiguration inputConfig,
- @NonNull List<OutputConfiguration> outputs,
- @SessionOperatingMode int operatingMode,
- @NonNull CameraCaptureSession.StateCallback callback,
- @Nullable Handler handler)
- throws CameraAccessException;
-
- /**
- * <p>Create a new {@link CameraCaptureSession} using a {@link SessionConfiguration} helper
- * object that aggregates all supported parameters.</p>
*
* @param config A session configuration (see {@link SessionConfiguration}).
*
@@ -997,7 +1026,7 @@
*
* @see CaptureRequest.Builder
* @see TotalCaptureResult
- * @see CameraDevice#createReprocessableCaptureSession
+ * @see CameraDevice#createCaptureSession(android.hardware.camera2.params.SessionConfiguration)
* @see android.media.ImageWriter
*/
@NonNull
@@ -1028,7 +1057,8 @@
* <p>This method performs a runtime check of a given {@link SessionConfiguration}. The result
* confirms whether or not the passed session configuration can be successfully used to
* create a camera capture session using
- * {@link CameraDevice#createCaptureSession(SessionConfiguration)}.
+ * {@link CameraDevice#createCaptureSession(
+ * android.hardware.camera2.params.SessionConfiguration)}.
* </p>
*
* <p>The method can be called at any point before, during and after active capture session.
diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java
index 555ff9a..47a897c 100644
--- a/core/java/android/hardware/camera2/params/SessionConfiguration.java
+++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java
@@ -17,10 +17,11 @@
package android.hardware.camera2.params;
+import static com.android.internal.util.Preconditions.*;
+
import android.annotation.CallbackExecutor;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
@@ -32,14 +33,12 @@
import android.os.Parcelable;
import android.util.Log;
-import java.util.Collections;
-import java.util.List;
-import java.util.ArrayList;
-import java.util.concurrent.Executor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-
-import static com.android.internal.util.Preconditions.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
/**
* A helper class that aggregates all supported arguments for capture session initialization.
@@ -61,6 +60,12 @@
* A high speed session type that can only contain instances of {@link OutputConfiguration}.
* The outputs can run using high speed FPS ranges. Calls to {@link #setInputConfiguration}
* are not supported.
+ * <p>
+ * When using this type, the CameraCaptureSession returned by
+ * {@link android.hardware.camera2.CameraCaptureSession.StateCallback} can be cast to a
+ * {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession} to access the extra
+ * methods for constrained high speed recording.
+ * </p>
*
* @see CameraDevice#createConstrainedHighSpeedCaptureSession
*/
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index 43f3787..d43a619 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -20,6 +20,7 @@
import android.hardware.soundtrigger.ModelParams;
import android.media.AudioFormat;
import android.media.audio.common.AudioConfig;
+import android.media.soundtrigger_middleware.AudioCapabilities;
import android.media.soundtrigger_middleware.ConfidenceLevel;
import android.media.soundtrigger_middleware.ModelParameterRange;
import android.media.soundtrigger_middleware.Phrase;
@@ -56,7 +57,8 @@
properties.maxBufferMs,
properties.concurrentCapture,
properties.powerConsumptionMw,
- properties.triggerInEvent
+ properties.triggerInEvent,
+ aidl2apiAudioCapabilities(properties.audioCapabilities)
);
}
@@ -145,6 +147,7 @@
apiConfig.keyphrases[i]);
}
aidlConfig.data = Arrays.copyOf(apiConfig.data, apiConfig.data.length);
+ aidlConfig.audioCapabilities = api2aidlAudioCapabilities(apiConfig.audioCapabilities);
return aidlConfig;
}
@@ -326,4 +329,26 @@
}
return new SoundTrigger.ModelParamRange(aidlRange.minInclusive, aidlRange.maxInclusive);
}
+
+ public static int aidl2apiAudioCapabilities(int aidlCapabilities) {
+ int result = 0;
+ if ((aidlCapabilities & AudioCapabilities.ECHO_CANCELLATION) != 0) {
+ result |= SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION;
+ }
+ if ((aidlCapabilities & AudioCapabilities.NOISE_SUPPRESSION) != 0) {
+ result |= SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION;
+ }
+ return result;
+ }
+
+ public static int api2aidlAudioCapabilities(int apiCapabilities) {
+ int result = 0;
+ if ((apiCapabilities & SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION) != 0) {
+ result |= AudioCapabilities.ECHO_CANCELLATION;
+ }
+ if ((apiCapabilities & SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION) != 0) {
+ result |= AudioCapabilities.NOISE_SUPPRESSION;
+ }
+ return result;
+ }
}
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index d872009..60e466e 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -24,6 +24,7 @@
import static java.util.Objects.requireNonNull;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -42,6 +43,8 @@
import android.os.ServiceManager;
import android.util.Log;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.UUID;
@@ -83,6 +86,30 @@
*
****************************************************************************/
public static final class ModuleProperties implements Parcelable {
+
+ /**
+ * Bit field values of AudioCapabilities supported by the implemented HAL
+ * driver.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "AUDIO_CAPABILITY_" }, value = {
+ CAPABILITY_ECHO_CANCELLATION,
+ CAPABILITY_NOISE_SUPPRESSION
+ })
+ public @interface AudioCapabilities {}
+
+ /**
+ * If set the underlying module supports AEC.
+ * Describes bit field {@link ModuleProperties#audioCapabilities}
+ */
+ public static final int CAPABILITY_ECHO_CANCELLATION = 0x1;
+ /**
+ * If set, the underlying module supports noise suppression.
+ * Describes bit field {@link ModuleProperties#audioCapabilities}
+ */
+ public static final int CAPABILITY_NOISE_SUPPRESSION = 0x2;
+
/** Unique module ID provided by the native service */
public final int id;
@@ -137,12 +164,19 @@
* recognition callback event */
public final boolean returnsTriggerInEvent;
+ /**
+ * Bit field encoding of the AudioCapabilities
+ * supported by the firmware.
+ */
+ @AudioCapabilities
+ public final int audioCapabilities;
+
ModuleProperties(int id, @NonNull String implementor, @NonNull String description,
@NonNull String uuid, int version, @NonNull String supportedModelArch,
int maxSoundModels, int maxKeyphrases, int maxUsers, int recognitionModes,
boolean supportsCaptureTransition, int maxBufferMs,
boolean supportsConcurrentCapture, int powerConsumptionMw,
- boolean returnsTriggerInEvent) {
+ boolean returnsTriggerInEvent, int audioCapabilities) {
this.id = id;
this.implementor = requireNonNull(implementor);
this.description = requireNonNull(description);
@@ -158,6 +192,7 @@
this.supportsConcurrentCapture = supportsConcurrentCapture;
this.powerConsumptionMw = powerConsumptionMw;
this.returnsTriggerInEvent = returnsTriggerInEvent;
+ this.audioCapabilities = audioCapabilities;
}
public static final @android.annotation.NonNull Parcelable.Creator<ModuleProperties> CREATOR
@@ -187,10 +222,11 @@
boolean supportsConcurrentCapture = in.readByte() == 1;
int powerConsumptionMw = in.readInt();
boolean returnsTriggerInEvent = in.readByte() == 1;
+ int audioCapabilities = in.readInt();
return new ModuleProperties(id, implementor, description, uuid, version,
supportedModelArch, maxSoundModels, maxKeyphrases, maxUsers, recognitionModes,
supportsCaptureTransition, maxBufferMs, supportsConcurrentCapture,
- powerConsumptionMw, returnsTriggerInEvent);
+ powerConsumptionMw, returnsTriggerInEvent, audioCapabilities);
}
@Override
@@ -210,6 +246,7 @@
dest.writeByte((byte) (supportsConcurrentCapture ? 1 : 0));
dest.writeInt(powerConsumptionMw);
dest.writeByte((byte) (returnsTriggerInEvent ? 1 : 0));
+ dest.writeInt(audioCapabilities);
}
@Override
@@ -227,7 +264,8 @@
+ ", supportsCaptureTransition=" + supportsCaptureTransition + ", maxBufferMs="
+ maxBufferMs + ", supportsConcurrentCapture=" + supportsConcurrentCapture
+ ", powerConsumptionMw=" + powerConsumptionMw
- + ", returnsTriggerInEvent=" + returnsTriggerInEvent + "]";
+ + ", returnsTriggerInEvent=" + returnsTriggerInEvent
+ + ", audioCapabilities=" + audioCapabilities + "]";
}
}
@@ -1049,13 +1087,27 @@
@NonNull
public final byte[] data;
- @UnsupportedAppUsage
+ /**
+ * Bit field encoding of the AudioCapabilities
+ * supported by the firmware.
+ */
+ @ModuleProperties.AudioCapabilities
+ public final int audioCapabilities;
+
public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
- @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
+ @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data,
+ int audioCapabilities) {
this.captureRequested = captureRequested;
this.allowMultipleTriggers = allowMultipleTriggers;
this.keyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
this.data = data != null ? data : new byte[0];
+ this.audioCapabilities = audioCapabilities;
+ }
+
+ @UnsupportedAppUsage
+ public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
+ @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
+ this(captureRequested, allowMultipleTriggers, keyphrases, data, 0);
}
public static final @android.annotation.NonNull Parcelable.Creator<RecognitionConfig> CREATOR
@@ -1075,7 +1127,9 @@
KeyphraseRecognitionExtra[] keyphrases =
in.createTypedArray(KeyphraseRecognitionExtra.CREATOR);
byte[] data = in.readBlob();
- return new RecognitionConfig(captureRequested, allowMultipleTriggers, keyphrases, data);
+ int audioCapabilities = in.readInt();
+ return new RecognitionConfig(captureRequested, allowMultipleTriggers, keyphrases, data,
+ audioCapabilities);
}
@Override
@@ -1084,6 +1138,7 @@
dest.writeByte((byte) (allowMultipleTriggers ? 1 : 0));
dest.writeTypedArray(keyphrases, flags);
dest.writeBlob(data);
+ dest.writeInt(audioCapabilities);
}
@Override
@@ -1095,7 +1150,8 @@
public String toString() {
return "RecognitionConfig [captureRequested=" + captureRequested
+ ", allowMultipleTriggers=" + allowMultipleTriggers + ", keyphrases="
- + Arrays.toString(keyphrases) + ", data=" + Arrays.toString(data) + "]";
+ + Arrays.toString(keyphrases) + ", data=" + Arrays.toString(data)
+ + ", audioCapabilities=" + Integer.toHexString(audioCapabilities) + "]";
}
}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 03d4200..4bf3e90 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -3121,8 +3121,6 @@
@RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
public int registerNetworkProvider(@NonNull NetworkProvider provider) {
if (provider.getProviderId() != NetworkProvider.ID_NONE) {
- // TODO: Provide a better method for checking this by moving NetworkFactory.SerialNumber
- // out of NetworkFactory.
throw new IllegalStateException("NetworkProviders can only be registered once");
}
@@ -3175,9 +3173,8 @@
*/
@RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
public int registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
- NetworkCapabilities nc, int score, NetworkMisc misc) {
- return registerNetworkAgent(messenger, ni, lp, nc, score, misc,
- NetworkFactory.SerialNumber.NONE);
+ NetworkCapabilities nc, int score, NetworkAgentConfig config) {
+ return registerNetworkAgent(messenger, ni, lp, nc, score, config, NetworkProvider.ID_NONE);
}
/**
@@ -3187,10 +3184,9 @@
*/
@RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
public int registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
- NetworkCapabilities nc, int score, NetworkMisc misc, int factorySerialNumber) {
+ NetworkCapabilities nc, int score, NetworkAgentConfig config, int providerId) {
try {
- return mService.registerNetworkAgent(messenger, ni, lp, nc, score, misc,
- factorySerialNumber);
+ return mService.registerNetworkAgent(messenger, ni, lp, nc, score, config, providerId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index e6a0379..7691beb 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -20,9 +20,9 @@
import android.net.ConnectionInfo;
import android.net.LinkProperties;
import android.net.Network;
+import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
-import android.net.NetworkMisc;
import android.net.NetworkQuotaInfo;
import android.net.NetworkRequest;
import android.net.NetworkState;
@@ -153,7 +153,8 @@
void declareNetworkRequestUnfulfillable(in NetworkRequest request);
int registerNetworkAgent(in Messenger messenger, in NetworkInfo ni, in LinkProperties lp,
- in NetworkCapabilities nc, int score, in NetworkMisc misc, in int factorySerialNumber);
+ in NetworkCapabilities nc, int score, in NetworkAgentConfig config,
+ in int factorySerialNumber);
NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities,
in Messenger messenger, int timeoutSec, in IBinder binder, int legacy);
diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl
index 9994f9f..5fa515a 100644
--- a/core/java/android/net/INetworkStatsService.aidl
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -23,6 +23,8 @@
import android.net.NetworkStats;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
+import android.net.netstats.provider.INetworkStatsProvider;
+import android.net.netstats.provider.INetworkStatsProviderCallback;
import android.os.IBinder;
import android.os.Messenger;
import com.android.internal.net.VpnInfo;
@@ -89,4 +91,7 @@
/** Get the total network stats information since boot */
long getTotalStats(int type);
+ /** Registers a network stats provider */
+ INetworkStatsProviderCallback registerNetworkStatsProvider(String tag,
+ in INetworkStatsProvider provider);
}
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index 60bd573..fc72eec 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -43,11 +43,12 @@
*
* @hide
*/
-public abstract class NetworkAgent extends Handler {
+public abstract class NetworkAgent {
// Guaranteed to be valid (not NETID_UNSET), otherwise registerNetworkAgent() would have thrown
// an exception.
public final int netId;
+ private final Handler mHandler;
private volatile AsyncChannel mAsyncChannel;
private final String LOG_TAG;
private static final boolean DBG = true;
@@ -58,7 +59,7 @@
private static final long BW_REFRESH_MIN_WIN_MS = 500;
private boolean mPollLceScheduled = false;
private AtomicBoolean mPollLcePending = new AtomicBoolean(false);
- public final int mFactorySerialNumber;
+ public final int mProviderId;
private static final int BASE = Protocol.BASE_NETWORK_AGENT;
@@ -219,25 +220,25 @@
// the entire tree.
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
NetworkCapabilities nc, LinkProperties lp, int score) {
- this(looper, context, logTag, ni, nc, lp, score, null, NetworkFactory.SerialNumber.NONE);
+ this(looper, context, logTag, ni, nc, lp, score, null, NetworkProvider.ID_NONE);
}
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
- NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc) {
- this(looper, context, logTag, ni, nc, lp, score, misc, NetworkFactory.SerialNumber.NONE);
+ NetworkCapabilities nc, LinkProperties lp, int score, NetworkAgentConfig config) {
+ this(looper, context, logTag, ni, nc, lp, score, config, NetworkProvider.ID_NONE);
}
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
- NetworkCapabilities nc, LinkProperties lp, int score, int factorySerialNumber) {
- this(looper, context, logTag, ni, nc, lp, score, null, factorySerialNumber);
+ NetworkCapabilities nc, LinkProperties lp, int score, int providerId) {
+ this(looper, context, logTag, ni, nc, lp, score, null, providerId);
}
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
- NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc,
- int factorySerialNumber) {
- super(looper);
+ NetworkCapabilities nc, LinkProperties lp, int score, NetworkAgentConfig config,
+ int providerId) {
+ mHandler = new NetworkAgentHandler(looper);
LOG_TAG = logTag;
mContext = context;
- mFactorySerialNumber = factorySerialNumber;
+ mProviderId = providerId;
if (ni == null || nc == null || lp == null) {
throw new IllegalArgumentException();
}
@@ -245,117 +246,124 @@
if (VDBG) log("Registering NetworkAgent");
ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(
Context.CONNECTIVITY_SERVICE);
- netId = cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni),
- new LinkProperties(lp), new NetworkCapabilities(nc), score, misc,
- factorySerialNumber);
+ netId = cm.registerNetworkAgent(new Messenger(mHandler), new NetworkInfo(ni),
+ new LinkProperties(lp), new NetworkCapabilities(nc), score, config,
+ providerId);
}
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
- if (mAsyncChannel != null) {
- log("Received new connection while already connected!");
- } else {
- if (VDBG) log("NetworkAgent fully connected");
- AsyncChannel ac = new AsyncChannel();
- ac.connected(null, this, msg.replyTo);
- ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
- AsyncChannel.STATUS_SUCCESSFUL);
- synchronized (mPreConnectedQueue) {
- mAsyncChannel = ac;
- for (Message m : mPreConnectedQueue) {
- ac.sendMessage(m);
- }
- mPreConnectedQueue.clear();
- }
- }
- break;
- }
- case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
- if (VDBG) log("CMD_CHANNEL_DISCONNECT");
- if (mAsyncChannel != null) mAsyncChannel.disconnect();
- break;
- }
- case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
- if (DBG) log("NetworkAgent channel lost");
- // let the client know CS is done with us.
- unwanted();
- synchronized (mPreConnectedQueue) {
- mAsyncChannel = null;
- }
- break;
- }
- case CMD_SUSPECT_BAD: {
- log("Unhandled Message " + msg);
- break;
- }
- case CMD_REQUEST_BANDWIDTH_UPDATE: {
- long currentTimeMs = System.currentTimeMillis();
- if (VDBG) {
- log("CMD_REQUEST_BANDWIDTH_UPDATE request received.");
- }
- if (currentTimeMs >= (mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS)) {
- mPollLceScheduled = false;
- if (mPollLcePending.getAndSet(true) == false) {
- pollLceData();
- }
- } else {
- // deliver the request at a later time rather than discard it completely.
- if (!mPollLceScheduled) {
- long waitTime = mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS -
- currentTimeMs + 1;
- mPollLceScheduled = sendEmptyMessageDelayed(
- CMD_REQUEST_BANDWIDTH_UPDATE, waitTime);
- }
- }
- break;
- }
- case CMD_REPORT_NETWORK_STATUS: {
- String redirectUrl = ((Bundle)msg.obj).getString(REDIRECT_URL_KEY);
- if (VDBG) {
- log("CMD_REPORT_NETWORK_STATUS(" +
- (msg.arg1 == VALID_NETWORK ? "VALID, " : "INVALID, ") + redirectUrl);
- }
- networkStatus(msg.arg1, redirectUrl);
- break;
- }
- case CMD_SAVE_ACCEPT_UNVALIDATED: {
- saveAcceptUnvalidated(msg.arg1 != 0);
- break;
- }
- case CMD_START_SOCKET_KEEPALIVE: {
- startSocketKeepalive(msg);
- break;
- }
- case CMD_STOP_SOCKET_KEEPALIVE: {
- stopSocketKeepalive(msg);
- break;
- }
+ private class NetworkAgentHandler extends Handler {
+ NetworkAgentHandler(Looper looper) {
+ super(looper);
+ }
- case CMD_SET_SIGNAL_STRENGTH_THRESHOLDS: {
- ArrayList<Integer> thresholds =
- ((Bundle) msg.obj).getIntegerArrayList("thresholds");
- // TODO: Change signal strength thresholds API to use an ArrayList<Integer>
- // rather than convert to int[].
- int[] intThresholds = new int[(thresholds != null) ? thresholds.size() : 0];
- for (int i = 0; i < intThresholds.length; i++) {
- intThresholds[i] = thresholds.get(i);
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
+ if (mAsyncChannel != null) {
+ log("Received new connection while already connected!");
+ } else {
+ if (VDBG) log("NetworkAgent fully connected");
+ AsyncChannel ac = new AsyncChannel();
+ ac.connected(null, this, msg.replyTo);
+ ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
+ AsyncChannel.STATUS_SUCCESSFUL);
+ synchronized (mPreConnectedQueue) {
+ mAsyncChannel = ac;
+ for (Message m : mPreConnectedQueue) {
+ ac.sendMessage(m);
+ }
+ mPreConnectedQueue.clear();
+ }
+ }
+ break;
}
- setSignalStrengthThresholds(intThresholds);
- break;
- }
- case CMD_PREVENT_AUTOMATIC_RECONNECT: {
- preventAutomaticReconnect();
- break;
- }
- case CMD_ADD_KEEPALIVE_PACKET_FILTER: {
- addKeepalivePacketFilter(msg);
- break;
- }
- case CMD_REMOVE_KEEPALIVE_PACKET_FILTER: {
- removeKeepalivePacketFilter(msg);
- break;
+ case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
+ if (VDBG) log("CMD_CHANNEL_DISCONNECT");
+ if (mAsyncChannel != null) mAsyncChannel.disconnect();
+ break;
+ }
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
+ if (DBG) log("NetworkAgent channel lost");
+ // let the client know CS is done with us.
+ unwanted();
+ synchronized (mPreConnectedQueue) {
+ mAsyncChannel = null;
+ }
+ break;
+ }
+ case CMD_SUSPECT_BAD: {
+ log("Unhandled Message " + msg);
+ break;
+ }
+ case CMD_REQUEST_BANDWIDTH_UPDATE: {
+ long currentTimeMs = System.currentTimeMillis();
+ if (VDBG) {
+ log("CMD_REQUEST_BANDWIDTH_UPDATE request received.");
+ }
+ if (currentTimeMs >= (mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS)) {
+ mPollLceScheduled = false;
+ if (!mPollLcePending.getAndSet(true)) {
+ pollLceData();
+ }
+ } else {
+ // deliver the request at a later time rather than discard it completely.
+ if (!mPollLceScheduled) {
+ long waitTime = mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS
+ - currentTimeMs + 1;
+ mPollLceScheduled = sendEmptyMessageDelayed(
+ CMD_REQUEST_BANDWIDTH_UPDATE, waitTime);
+ }
+ }
+ break;
+ }
+ case CMD_REPORT_NETWORK_STATUS: {
+ String redirectUrl = ((Bundle) msg.obj).getString(REDIRECT_URL_KEY);
+ if (VDBG) {
+ log("CMD_REPORT_NETWORK_STATUS("
+ + (msg.arg1 == VALID_NETWORK ? "VALID, " : "INVALID, ")
+ + redirectUrl);
+ }
+ networkStatus(msg.arg1, redirectUrl);
+ break;
+ }
+ case CMD_SAVE_ACCEPT_UNVALIDATED: {
+ saveAcceptUnvalidated(msg.arg1 != 0);
+ break;
+ }
+ case CMD_START_SOCKET_KEEPALIVE: {
+ startSocketKeepalive(msg);
+ break;
+ }
+ case CMD_STOP_SOCKET_KEEPALIVE: {
+ stopSocketKeepalive(msg);
+ break;
+ }
+
+ case CMD_SET_SIGNAL_STRENGTH_THRESHOLDS: {
+ ArrayList<Integer> thresholds =
+ ((Bundle) msg.obj).getIntegerArrayList("thresholds");
+ // TODO: Change signal strength thresholds API to use an ArrayList<Integer>
+ // rather than convert to int[].
+ int[] intThresholds = new int[(thresholds != null) ? thresholds.size() : 0];
+ for (int i = 0; i < intThresholds.length; i++) {
+ intThresholds[i] = thresholds.get(i);
+ }
+ setSignalStrengthThresholds(intThresholds);
+ break;
+ }
+ case CMD_PREVENT_AUTOMATIC_RECONNECT: {
+ preventAutomaticReconnect();
+ break;
+ }
+ case CMD_ADD_KEEPALIVE_PACKET_FILTER: {
+ addKeepalivePacketFilter(msg);
+ break;
+ }
+ case CMD_REMOVE_KEEPALIVE_PACKET_FILTER: {
+ removeKeepalivePacketFilter(msg);
+ break;
+ }
}
}
}
diff --git a/core/java/android/net/NetworkMisc.aidl b/core/java/android/net/NetworkAgentConfig.aidl
similarity index 95%
rename from core/java/android/net/NetworkMisc.aidl
rename to core/java/android/net/NetworkAgentConfig.aidl
index c65583f..cb70bdd 100644
--- a/core/java/android/net/NetworkMisc.aidl
+++ b/core/java/android/net/NetworkAgentConfig.aidl
@@ -16,4 +16,4 @@
package android.net;
-parcelable NetworkMisc;
+parcelable NetworkAgentConfig;
diff --git a/core/java/android/net/NetworkMisc.java b/core/java/android/net/NetworkAgentConfig.java
similarity index 72%
rename from core/java/android/net/NetworkMisc.java
rename to core/java/android/net/NetworkAgentConfig.java
index 4ad52d5..3a383a4 100644
--- a/core/java/android/net/NetworkMisc.java
+++ b/core/java/android/net/NetworkAgentConfig.java
@@ -16,6 +16,8 @@
package android.net;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
@@ -26,7 +28,7 @@
*
* @hide
*/
-public class NetworkMisc implements Parcelable {
+public class NetworkAgentConfig implements Parcelable {
/**
* If the {@link Network} is a VPN, whether apps are allowed to bypass the
@@ -83,17 +85,17 @@
*/
public boolean hasShownBroken;
- public NetworkMisc() {
+ public NetworkAgentConfig() {
}
- public NetworkMisc(NetworkMisc nm) {
- if (nm != null) {
- allowBypass = nm.allowBypass;
- explicitlySelected = nm.explicitlySelected;
- acceptUnvalidated = nm.acceptUnvalidated;
- subscriberId = nm.subscriberId;
- provisioningNotificationDisabled = nm.provisioningNotificationDisabled;
- skip464xlat = nm.skip464xlat;
+ public NetworkAgentConfig(@Nullable NetworkAgentConfig nac) {
+ if (nac != null) {
+ allowBypass = nac.allowBypass;
+ explicitlySelected = nac.explicitlySelected;
+ acceptUnvalidated = nac.acceptUnvalidated;
+ subscriberId = nac.subscriberId;
+ provisioningNotificationDisabled = nac.provisioningNotificationDisabled;
+ skip464xlat = nac.skip464xlat;
}
}
@@ -112,22 +114,23 @@
out.writeInt(skip464xlat ? 1 : 0);
}
- public static final @android.annotation.NonNull Creator<NetworkMisc> CREATOR = new Creator<NetworkMisc>() {
+ public static final @NonNull Creator<NetworkAgentConfig> CREATOR =
+ new Creator<NetworkAgentConfig>() {
@Override
- public NetworkMisc createFromParcel(Parcel in) {
- NetworkMisc networkMisc = new NetworkMisc();
- networkMisc.allowBypass = in.readInt() != 0;
- networkMisc.explicitlySelected = in.readInt() != 0;
- networkMisc.acceptUnvalidated = in.readInt() != 0;
- networkMisc.subscriberId = in.readString();
- networkMisc.provisioningNotificationDisabled = in.readInt() != 0;
- networkMisc.skip464xlat = in.readInt() != 0;
- return networkMisc;
+ public NetworkAgentConfig createFromParcel(Parcel in) {
+ NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig();
+ networkAgentConfig.allowBypass = in.readInt() != 0;
+ networkAgentConfig.explicitlySelected = in.readInt() != 0;
+ networkAgentConfig.acceptUnvalidated = in.readInt() != 0;
+ networkAgentConfig.subscriberId = in.readString();
+ networkAgentConfig.provisioningNotificationDisabled = in.readInt() != 0;
+ networkAgentConfig.skip464xlat = in.readInt() != 0;
+ return networkAgentConfig;
}
@Override
- public NetworkMisc[] newArray(int size) {
- return new NetworkMisc[size];
+ public NetworkAgentConfig[] newArray(int size) {
+ return new NetworkAgentConfig[size];
}
};
}
diff --git a/core/java/android/net/NetworkFactory.java b/core/java/android/net/NetworkFactory.java
index 42ab925..824ddb8 100644
--- a/core/java/android/net/NetworkFactory.java
+++ b/core/java/android/net/NetworkFactory.java
@@ -16,6 +16,7 @@
package android.net;
+import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Build;
@@ -27,7 +28,6 @@
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.AsyncChannel;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Protocol;
@@ -52,7 +52,7 @@
* @hide
**/
public class NetworkFactory extends Handler {
- /** @hide */
+ /* TODO: delete when all callers have migrated to NetworkProvider IDs. */
public static class SerialNumber {
// Guard used by no network factory.
public static final int NONE = -1;
@@ -91,8 +91,8 @@
* with the same NetworkRequest but an updated score.
* Also, network conditions may change for this bearer
* allowing for a better score in the future.
- * msg.arg2 = the serial number of the factory currently responsible for the
- * NetworkAgent handling this request, or SerialNumber.NONE if none.
+ * msg.arg2 = the ID of the NetworkProvider currently responsible for the
+ * NetworkAgent handling this request, or NetworkProvider.ID_NONE if none.
*/
public static final int CMD_REQUEST_NETWORK = BASE;
@@ -124,7 +124,6 @@
private final Context mContext;
private final ArrayList<Message> mPreConnectedQueue = new ArrayList<Message>();
- private AsyncChannel mAsyncChannel;
private final String LOG_TAG;
private final SparseArray<NetworkRequestInfo> mNetworkRequests =
@@ -135,7 +134,8 @@
private int mRefCount = 0;
private Messenger mMessenger = null;
- private int mSerialNumber;
+ private NetworkProvider mProvider = null;
+ private int mProviderId;
@UnsupportedAppUsage
public NetworkFactory(Looper looper, Context context, String logTag,
@@ -147,55 +147,43 @@
}
public void register() {
- if (DBG) log("Registering NetworkFactory");
- if (mMessenger == null) {
- mMessenger = new Messenger(this);
- mSerialNumber = ConnectivityManager.from(mContext).registerNetworkFactory(mMessenger,
- LOG_TAG);
+ if (mProvider != null) {
+ Log.e(LOG_TAG, "Ignoring attempt to register already-registered NetworkFactory");
+ return;
}
+ if (DBG) log("Registering NetworkFactory");
+
+ mProvider = new NetworkProvider(mContext, NetworkFactory.this.getLooper(), LOG_TAG) {
+ @Override
+ public void onNetworkRequested(@NonNull NetworkRequest request, int score,
+ int servingProviderId) {
+ handleAddRequest((NetworkRequest) request, score, servingProviderId);
+ }
+
+ @Override
+ public void onRequestWithdrawn(@NonNull NetworkRequest request) {
+ handleRemoveRequest(request);
+ }
+ };
+
+ mMessenger = new Messenger(this);
+ mProviderId = ConnectivityManager.from(mContext).registerNetworkProvider(mProvider);
}
public void unregister() {
- if (DBG) log("Unregistering NetworkFactory");
- if (mMessenger != null) {
- ConnectivityManager.from(mContext).unregisterNetworkFactory(mMessenger);
- mMessenger = null;
+ if (mProvider == null) {
+ Log.e(LOG_TAG, "Ignoring attempt to unregister unregistered NetworkFactory");
+ return;
}
+ if (DBG) log("Unregistering NetworkFactory");
+
+ ConnectivityManager.from(mContext).unregisterNetworkProvider(mProvider);
+ mProvider = null;
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
- if (mAsyncChannel != null) {
- log("Received new connection while already connected!");
- break;
- }
- if (VDBG) log("NetworkFactory fully connected");
- AsyncChannel ac = new AsyncChannel();
- ac.connected(null, this, msg.replyTo);
- ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
- AsyncChannel.STATUS_SUCCESSFUL);
- mAsyncChannel = ac;
- for (Message m : mPreConnectedQueue) {
- ac.sendMessage(m);
- }
- mPreConnectedQueue.clear();
- break;
- }
- case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
- if (VDBG) log("CMD_CHANNEL_DISCONNECT");
- if (mAsyncChannel != null) {
- mAsyncChannel.disconnect();
- mAsyncChannel = null;
- }
- break;
- }
- case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
- if (DBG) log("NetworkFactory channel lost");
- mAsyncChannel = null;
- break;
- }
case CMD_REQUEST_NETWORK: {
handleAddRequest((NetworkRequest) msg.obj, msg.arg1, msg.arg2);
break;
@@ -219,13 +207,13 @@
public final NetworkRequest request;
public int score;
public boolean requested; // do we have a request outstanding, limited by score
- public int factorySerialNumber;
+ public int providerId;
- NetworkRequestInfo(NetworkRequest request, int score, int factorySerialNumber) {
+ NetworkRequestInfo(NetworkRequest request, int score, int providerId) {
this.request = request;
this.score = score;
this.requested = false;
- this.factorySerialNumber = factorySerialNumber;
+ this.providerId = providerId;
}
@Override
@@ -240,8 +228,6 @@
*
* @param request the request to handle.
* @param score the score of the NetworkAgent currently satisfying this request.
- * @param servingFactorySerialNumber the serial number of the NetworkFactory that
- * created the NetworkAgent currently satisfying this request.
*/
// TODO : remove this method. It is a stopgap measure to help sheperding a number
// of dependent changes that would conflict throughout the automerger graph. Having this
@@ -249,7 +235,7 @@
// the entire tree.
@VisibleForTesting
protected void handleAddRequest(NetworkRequest request, int score) {
- handleAddRequest(request, score, SerialNumber.NONE);
+ handleAddRequest(request, score, NetworkProvider.ID_NONE);
}
/**
@@ -258,27 +244,26 @@
*
* @param request the request to handle.
* @param score the score of the NetworkAgent currently satisfying this request.
- * @param servingFactorySerialNumber the serial number of the NetworkFactory that
- * created the NetworkAgent currently satisfying this request.
+ * @param servingProviderId the ID of the NetworkProvider that created the NetworkAgent
+ * currently satisfying this request.
*/
@VisibleForTesting
- protected void handleAddRequest(NetworkRequest request, int score,
- int servingFactorySerialNumber) {
+ protected void handleAddRequest(NetworkRequest request, int score, int servingProviderId) {
NetworkRequestInfo n = mNetworkRequests.get(request.requestId);
if (n == null) {
if (DBG) {
log("got request " + request + " with score " + score
- + " and serial " + servingFactorySerialNumber);
+ + " and providerId " + servingProviderId);
}
- n = new NetworkRequestInfo(request, score, servingFactorySerialNumber);
+ n = new NetworkRequestInfo(request, score, servingProviderId);
mNetworkRequests.put(n.request.requestId, n);
} else {
if (VDBG) {
log("new score " + score + " for exisiting request " + request
- + " with serial " + servingFactorySerialNumber);
+ + " and providerId " + servingProviderId);
}
n.score = score;
- n.factorySerialNumber = servingFactorySerialNumber;
+ n.providerId = servingProviderId;
}
if (VDBG) log(" my score=" + mScore + ", my filter=" + mCapabilityFilter);
@@ -333,8 +318,8 @@
log(" n.requests = " + n.requested);
log(" n.score = " + n.score);
log(" mScore = " + mScore);
- log(" n.factorySerialNumber = " + n.factorySerialNumber);
- log(" mSerialNumber = " + mSerialNumber);
+ log(" n.providerId = " + n.providerId);
+ log(" mProviderId = " + mProviderId);
}
if (shouldNeedNetworkFor(n)) {
if (VDBG) log(" needNetworkFor");
@@ -355,7 +340,7 @@
// If the score of this request is higher or equal to that of this factory and some
// other factory is responsible for it, then this factory should not track the request
// because it has no hope of satisfying it.
- && (n.score < mScore || n.factorySerialNumber == mSerialNumber)
+ && (n.score < mScore || n.providerId == mProviderId)
// If this factory can't satisfy the capability needs of this request, then it
// should not be tracked.
&& n.request.networkCapabilities.satisfiedByNetworkCapabilities(mCapabilityFilter)
@@ -373,7 +358,7 @@
// assigned to the factory
// - This factory can't satisfy the capability needs of the request
// - The concrete implementation of the factory rejects the request
- && ((n.score > mScore && n.factorySerialNumber != mSerialNumber)
+ && ((n.score > mScore && n.providerId != mProviderId)
|| !n.request.networkCapabilities.satisfiedByNetworkCapabilities(
mCapabilityFilter)
|| !acceptRequest(n.request, n.score));
@@ -408,12 +393,7 @@
protected void releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest r) {
post(() -> {
if (DBG) log("releaseRequestAsUnfulfillableByAnyFactory: " + r);
- Message msg = obtainMessage(EVENT_UNFULFILLABLE_REQUEST, r);
- if (mAsyncChannel != null) {
- mAsyncChannel.sendMessage(msg);
- } else {
- mPreConnectedQueue.add(msg);
- }
+ ConnectivityManager.from(mContext).declareNetworkRequestUnfulfillable(r);
});
}
@@ -444,8 +424,13 @@
return mNetworkRequests.size();
}
+ /* TODO: delete when all callers have migrated to NetworkProvider IDs. */
public int getSerialNumber() {
- return mSerialNumber;
+ return mProviderId;
+ }
+
+ public int getProviderId() {
+ return mProviderId;
}
protected void log(String s) {
@@ -465,8 +450,8 @@
@Override
public String toString() {
- StringBuilder sb = new StringBuilder("{").append(LOG_TAG).append(" - mSerialNumber=")
- .append(mSerialNumber).append(", ScoreFilter=")
+ StringBuilder sb = new StringBuilder("{").append(LOG_TAG).append(" - mProviderId=")
+ .append(mProviderId).append(", ScoreFilter=")
.append(mScore).append(", Filter=").append(mCapabilityFilter).append(", requests=")
.append(mNetworkRequests.size()).append(", refCount=").append(mRefCount)
.append("}");
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 2e7ac3f5..f369064 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4142,7 +4142,6 @@
* <li>{@link #ENABLE_CMAS_PRESIDENTIAL_PREF}</li>
* <li>{@link #ENABLE_ALERT_VIBRATION_PREF}</li>
* <li>{@link #ENABLE_EMERGENCY_PERF}</li>
- * <li>{@link #ENABLE_FULL_VOLUME_PREF}</li>
* <li>{@link #ENABLE_CMAS_IN_SECOND_LANGUAGE_PREF}</li>
* </ul>
* @hide
@@ -4205,10 +4204,6 @@
public static final @NonNull String ENABLE_EMERGENCY_PERF =
"enable_emergency_alerts";
- /** Preference to enable volume for alerts */
- public static final @NonNull String ENABLE_FULL_VOLUME_PREF =
- "use_full_volume";
-
/** Preference to enable receive alerts in second language */
public static final @NonNull String ENABLE_CMAS_IN_SECOND_LANGUAGE_PREF =
"receive_cmas_in_second_language";
diff --git a/core/java/android/se/omapi/SEService.java b/core/java/android/se/omapi/SEService.java
index d646e23..00060ab 100644
--- a/core/java/android/se/omapi/SEService.java
+++ b/core/java/android/se/omapi/SEService.java
@@ -22,14 +22,11 @@
package android.se.omapi;
-import android.app.ActivityThread;
import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
@@ -143,10 +140,6 @@
throw new NullPointerException("Arguments must not be null");
}
- if (!hasOMAPIReaders()) {
- throw new UnsupportedOperationException("Device does not support any OMAPI reader");
- }
-
mContext = context;
mSEListener.mListener = listener;
mSEListener.mExecutor = executor;
@@ -277,23 +270,4 @@
throw new IllegalStateException(e.getMessage());
}
}
-
- /**
- * Helper to check if this device support any OMAPI readers
- */
- private static boolean hasOMAPIReaders() {
- IPackageManager pm = ActivityThread.getPackageManager();
- if (pm == null) {
- Log.e(TAG, "Cannot get package manager, assuming OMAPI readers supported");
- return true;
- }
- try {
- return pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_UICC, 0)
- || pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_ESE, 0)
- || pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_SD, 0);
- } catch (RemoteException e) {
- Log.e(TAG, "Package manager query failed, assuming OMAPI readers supported", e);
- return true;
- }
- }
}
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index d7c6d0f..0f33998 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -119,7 +119,9 @@
@IntDef(flag = true, prefix = { "RECOGNITION_FLAG_" }, value = {
RECOGNITION_FLAG_NONE,
RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO,
- RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS
+ RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS,
+ RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION,
+ RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION,
})
public @interface RecognitionFlags {}
@@ -144,6 +146,26 @@
*/
public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 0x2;
+ /**
+ * Audio capabilities flag for {@link #startRecognition(int)} that indicates
+ * if the underlying recognition should use AEC.
+ * This capability may or may not be supported by the system, and support can be queried
+ * by calling {@link #getSupportedAudioCapabilities()}. The corresponding capabilities field for
+ * this flag is {@link #AUDIO_CAPABILITY_ECHO_CANCELLATION}. If this flag is passed without the
+ * audio capability supported, there will be no audio effect applied.
+ */
+ public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 0x4;
+
+ /**
+ * Audio capabilities flag for {@link #startRecognition(int)} that indicates
+ * if the underlying recognition should use noise suppression.
+ * This capability may or may not be supported by the system, and support can be queried
+ * by calling {@link #getSupportedAudioCapabilities()}. The corresponding capabilities field for
+ * this flag is {@link #AUDIO_CAPABILITY_NOISE_SUPPRESSION}. If this flag is passed without the
+ * audio capability supported, there will be no audio effect applied.
+ */
+ public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 0x8;
+
//---- Recognition mode flags. Return codes for getSupportedRecognitionModes() ----//
// Must be kept in sync with the related attribute defined as searchKeyphraseRecognitionFlags.
@@ -168,6 +190,30 @@
public static final int RECOGNITION_MODE_USER_IDENTIFICATION
= SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION;
+ //-- Audio capabilities. Values in returned bit field for getSupportedAudioCapabilities() --//
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "AUDIO_CAPABILITY_" }, value = {
+ AUDIO_CAPABILITY_ECHO_CANCELLATION,
+ AUDIO_CAPABILITY_NOISE_SUPPRESSION,
+ })
+ public @interface AudioCapabilities {}
+
+ /**
+ * If set the underlying module supports AEC.
+ * Returned by {@link #getSupportedAudioCapabilities()}
+ */
+ public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION =
+ SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION;
+
+ /**
+ * If set, the underlying module supports noise suppression.
+ * Returned by {@link #getSupportedAudioCapabilities()}
+ */
+ public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION =
+ SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = { "MODEL_PARAM_" }, value = {
@@ -448,6 +494,37 @@
}
/**
+ * Get the audio capabilities supported by the platform which can be enabled when
+ * starting a recognition.
+ *
+ * @see #AUDIO_CAPABILITY_ECHO_CANCELLATION
+ * @see #AUDIO_CAPABILITY_NOISE_SUPPRESSION
+ *
+ * @return Bit field encoding of the AudioCapabilities supported.
+ */
+ @AudioCapabilities
+ public int getSupportedAudioCapabilities() {
+ if (DBG) Slog.d(TAG, "getSupportedAudioCapabilities()");
+ synchronized (mLock) {
+ return getSupportedAudioCapabilitiesLocked();
+ }
+ }
+
+ private int getSupportedAudioCapabilitiesLocked() {
+ try {
+ ModuleProperties properties =
+ mModelManagementService.getDspModuleProperties(mVoiceInteractionService);
+ if (properties != null) {
+ return properties.audioCapabilities;
+ }
+
+ return 0;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Starts recognition for the associated keyphrase.
*
* @see #RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
@@ -711,12 +788,21 @@
(recognitionFlags&RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO) != 0;
boolean allowMultipleTriggers =
(recognitionFlags&RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS) != 0;
+
+ int audioCapabilities = 0;
+ if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION) != 0) {
+ audioCapabilities |= AUDIO_CAPABILITY_ECHO_CANCELLATION;
+ }
+ if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION) != 0) {
+ audioCapabilities |= AUDIO_CAPABILITY_NOISE_SUPPRESSION;
+ }
+
int code = STATUS_ERROR;
try {
code = mModelManagementService.startRecognition(mVoiceInteractionService,
mKeyphraseMetadata.id, mLocale.toLanguageTag(), mInternalCallback,
new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers,
- recognitionExtra, null /* additional data */));
+ recognitionExtra, null /* additional data */, audioCapabilities));
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in startRecognition!", e);
}
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
index fa994ba..0892c94 100644
--- a/core/java/android/util/NtpTrustedTime.java
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -16,6 +16,8 @@
package android.util;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.content.Context;
@@ -25,172 +27,270 @@
import android.net.NetworkInfo;
import android.net.SntpClient;
import android.os.SystemClock;
-import android.os.TimestampedValue;
import android.provider.Settings;
import android.text.TextUtils;
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Objects;
+import java.util.function.Supplier;
+
/**
- * {@link TrustedTime} that connects with a remote NTP server as its trusted
- * time source.
+ * A singleton that connects with a remote NTP server as its trusted time source. This class
+ * is thread-safe. The {@link #forceRefresh()} method is synchronous, i.e. it may occupy the
+ * current thread while performing an NTP request. All other threads calling {@link #forceRefresh()}
+ * will block during that request.
*
* @hide
*/
public class NtpTrustedTime implements TrustedTime {
+
+ /**
+ * The result of a successful NTP query.
+ *
+ * @hide
+ */
+ public static class TimeResult {
+ private final long mTimeMillis;
+ private final long mElapsedRealtimeMillis;
+ private final long mCertaintyMillis;
+
+ public TimeResult(long timeMillis, long elapsedRealtimeMillis, long certaintyMillis) {
+ mTimeMillis = timeMillis;
+ mElapsedRealtimeMillis = elapsedRealtimeMillis;
+ mCertaintyMillis = certaintyMillis;
+ }
+
+ public long getTimeMillis() {
+ return mTimeMillis;
+ }
+
+ public long getElapsedRealtimeMillis() {
+ return mElapsedRealtimeMillis;
+ }
+
+ public long getCertaintyMillis() {
+ return mCertaintyMillis;
+ }
+
+ /** Calculates and returns the current time accounting for the age of this result. */
+ public long currentTimeMillis() {
+ return mTimeMillis + getAgeMillis();
+ }
+
+ /** Calculates and returns the age of this result. */
+ public long getAgeMillis() {
+ return SystemClock.elapsedRealtime() - mElapsedRealtimeMillis;
+ }
+
+ @Override
+ public String toString() {
+ return "TimeResult{"
+ + "mTimeMillis=" + mTimeMillis
+ + ", mElapsedRealtimeMillis=" + mElapsedRealtimeMillis
+ + ", mCertaintyMillis=" + mCertaintyMillis
+ + '}';
+ }
+ }
+
private static final String TAG = "NtpTrustedTime";
private static final boolean LOGD = false;
private static NtpTrustedTime sSingleton;
- private static Context sContext;
- private final String mServer;
- private final long mTimeout;
+ @NonNull
+ private final Context mContext;
- private ConnectivityManager mCM;
+ /**
+ * A supplier that returns the ConnectivityManager. The Supplier can return null if
+ * ConnectivityService isn't running yet.
+ */
+ private final Supplier<ConnectivityManager> mConnectivityManagerSupplier =
+ new Supplier<ConnectivityManager>() {
+ private ConnectivityManager mConnectivityManager;
- private boolean mHasCache;
- private long mCachedNtpTime;
- private long mCachedNtpElapsedRealtime;
- private long mCachedNtpCertainty;
+ @Nullable
+ @Override
+ public synchronized ConnectivityManager get() {
+ // We can't do this at initialization time: ConnectivityService might not be running
+ // yet.
+ if (mConnectivityManager == null) {
+ mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
+ }
+ return mConnectivityManager;
+ }
+ };
- private NtpTrustedTime(String server, long timeout) {
- if (LOGD) Log.d(TAG, "creating NtpTrustedTime using " + server);
- mServer = server;
- mTimeout = timeout;
+ // Declared volatile and accessed outside of synchronized blocks to avoid blocking reads during
+ // forceRefresh().
+ private volatile TimeResult mTimeResult;
+
+ private NtpTrustedTime(Context context) {
+ mContext = Objects.requireNonNull(context);
}
@UnsupportedAppUsage
public static synchronized NtpTrustedTime getInstance(Context context) {
if (sSingleton == null) {
- final Resources res = context.getResources();
- final ContentResolver resolver = context.getContentResolver();
-
- final String defaultServer = res.getString(
- com.android.internal.R.string.config_ntpServer);
- final long defaultTimeout = res.getInteger(
- com.android.internal.R.integer.config_ntpTimeout);
-
- final String secureServer = Settings.Global.getString(
- resolver, Settings.Global.NTP_SERVER);
- final long timeout = Settings.Global.getLong(
- resolver, Settings.Global.NTP_TIMEOUT, defaultTimeout);
-
- final String server = secureServer != null ? secureServer : defaultServer;
- sSingleton = new NtpTrustedTime(server, timeout);
- sContext = context;
+ Context appContext = context.getApplicationContext();
+ sSingleton = new NtpTrustedTime(appContext);
}
-
return sSingleton;
}
- @Override
@UnsupportedAppUsage
public boolean forceRefresh() {
- // We can't do this at initialization time: ConnectivityService might not be running yet.
synchronized (this) {
- if (mCM == null) {
- mCM = sContext.getSystemService(ConnectivityManager.class);
+ NtpConnectionInfo connectionInfo = getNtpConnectionInfo();
+ if (connectionInfo == null) {
+ // missing server config, so no trusted time available
+ if (LOGD) Log.d(TAG, "forceRefresh: invalid server config");
+ return false;
}
- }
- final Network network = mCM == null ? null : mCM.getActiveNetwork();
- return forceRefresh(network);
- }
-
- public boolean forceRefresh(Network network) {
- if (TextUtils.isEmpty(mServer)) {
- // missing server, so no trusted time available
- return false;
- }
-
- // We can't do this at initialization time: ConnectivityService might not be running yet.
- synchronized (this) {
- if (mCM == null) {
- mCM = sContext.getSystemService(ConnectivityManager.class);
+ ConnectivityManager connectivityManager = mConnectivityManagerSupplier.get();
+ if (connectivityManager == null) {
+ if (LOGD) Log.d(TAG, "forceRefresh: no ConnectivityManager");
+ return false;
}
- }
+ final Network network = connectivityManager.getActiveNetwork();
+ final NetworkInfo ni = connectivityManager.getNetworkInfo(network);
+ if (ni == null || !ni.isConnected()) {
+ if (LOGD) Log.d(TAG, "forceRefresh: no connectivity");
+ return false;
+ }
- final NetworkInfo ni = mCM == null ? null : mCM.getNetworkInfo(network);
- if (ni == null || !ni.isConnected()) {
- if (LOGD) Log.d(TAG, "forceRefresh: no connectivity");
- return false;
- }
-
-
- if (LOGD) Log.d(TAG, "forceRefresh() from cache miss");
- final SntpClient client = new SntpClient();
- if (client.requestTime(mServer, (int) mTimeout, network)) {
- mHasCache = true;
- mCachedNtpTime = client.getNtpTime();
- mCachedNtpElapsedRealtime = client.getNtpTimeReference();
- mCachedNtpCertainty = client.getRoundTripTime() / 2;
- return true;
- } else {
- return false;
+ if (LOGD) Log.d(TAG, "forceRefresh() from cache miss");
+ final SntpClient client = new SntpClient();
+ final String serverName = connectionInfo.getServer();
+ final int timeoutMillis = connectionInfo.getTimeoutMillis();
+ if (client.requestTime(serverName, timeoutMillis, network)) {
+ long ntpCertainty = client.getRoundTripTime() / 2;
+ mTimeResult = new TimeResult(
+ client.getNtpTime(), client.getNtpTimeReference(), ntpCertainty);
+ return true;
+ } else {
+ return false;
+ }
}
}
- @Override
+ /**
+ * Only kept for UnsupportedAppUsage.
+ *
+ * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
+ */
+ @Deprecated
@UnsupportedAppUsage
public boolean hasCache() {
- return mHasCache;
+ return mTimeResult != null;
}
+ /**
+ * Only kept for UnsupportedAppUsage.
+ *
+ * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
+ */
+ @Deprecated
@Override
public long getCacheAge() {
- if (mHasCache) {
- return SystemClock.elapsedRealtime() - mCachedNtpElapsedRealtime;
+ TimeResult timeResult = mTimeResult;
+ if (timeResult != null) {
+ return SystemClock.elapsedRealtime() - timeResult.getElapsedRealtimeMillis();
} else {
return Long.MAX_VALUE;
}
}
- @Override
- public long getCacheCertainty() {
- if (mHasCache) {
- return mCachedNtpCertainty;
- } else {
- return Long.MAX_VALUE;
- }
- }
-
- @Override
+ /**
+ * Only kept for UnsupportedAppUsage.
+ *
+ * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
+ */
+ @Deprecated
@UnsupportedAppUsage
public long currentTimeMillis() {
- if (!mHasCache) {
+ TimeResult timeResult = mTimeResult;
+ if (timeResult == null) {
throw new IllegalStateException("Missing authoritative time source");
}
if (LOGD) Log.d(TAG, "currentTimeMillis() cache hit");
// current time is age after the last ntp cache; callers who
- // want fresh values will hit makeAuthoritative() first.
- return mCachedNtpTime + getCacheAge();
- }
-
- @UnsupportedAppUsage
- public long getCachedNtpTime() {
- if (LOGD) Log.d(TAG, "getCachedNtpTime() cache hit");
- return mCachedNtpTime;
- }
-
- @UnsupportedAppUsage
- public long getCachedNtpTimeReference() {
- return mCachedNtpElapsedRealtime;
+ // want fresh values will hit forceRefresh() first.
+ return timeResult.currentTimeMillis();
}
/**
- * Returns the combination of {@link #getCachedNtpTime()} and {@link
- * #getCachedNtpTimeReference()} as a {@link TimestampedValue}. This method is useful when
- * passing the time to another component that will adjust for elapsed time.
+ * Only kept for UnsupportedAppUsage.
*
- * @throws IllegalStateException if there is no cached value
+ * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
*/
- public TimestampedValue<Long> getCachedNtpTimeSignal() {
- if (!mHasCache) {
- throw new IllegalStateException("Missing authoritative time source");
- }
- if (LOGD) Log.d(TAG, "getCachedNtpTimeSignal() cache hit");
-
- return new TimestampedValue<>(mCachedNtpElapsedRealtime, mCachedNtpTime);
+ @Deprecated
+ @UnsupportedAppUsage
+ public long getCachedNtpTime() {
+ if (LOGD) Log.d(TAG, "getCachedNtpTime() cache hit");
+ TimeResult timeResult = mTimeResult;
+ return timeResult == null ? 0 : timeResult.getTimeMillis();
}
+ /**
+ * Only kept for UnsupportedAppUsage.
+ *
+ * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public long getCachedNtpTimeReference() {
+ TimeResult timeResult = mTimeResult;
+ return timeResult == null ? 0 : timeResult.getElapsedRealtimeMillis();
+ }
+
+ /**
+ * Returns an object containing the latest NTP information available. Can return {@code null} if
+ * no information is available.
+ */
+ @Nullable
+ public TimeResult getCachedTimeResult() {
+ return mTimeResult;
+ }
+
+ private static class NtpConnectionInfo {
+
+ @NonNull private final String mServer;
+ private final int mTimeoutMillis;
+
+ NtpConnectionInfo(@NonNull String server, int timeoutMillis) {
+ mServer = Objects.requireNonNull(server);
+ mTimeoutMillis = timeoutMillis;
+ }
+
+ @NonNull
+ public String getServer() {
+ return mServer;
+ }
+
+ int getTimeoutMillis() {
+ return mTimeoutMillis;
+ }
+ }
+
+ @GuardedBy("this")
+ private NtpConnectionInfo getNtpConnectionInfo() {
+ final ContentResolver resolver = mContext.getContentResolver();
+
+ final Resources res = mContext.getResources();
+ final String defaultServer = res.getString(
+ com.android.internal.R.string.config_ntpServer);
+ final int defaultTimeoutMillis = res.getInteger(
+ com.android.internal.R.integer.config_ntpTimeout);
+
+ final String secureServer = Settings.Global.getString(
+ resolver, Settings.Global.NTP_SERVER);
+ final int timeoutMillis = Settings.Global.getInt(
+ resolver, Settings.Global.NTP_TIMEOUT, defaultTimeoutMillis);
+
+ final String server = secureServer != null ? secureServer : defaultServer;
+ return TextUtils.isEmpty(server) ? null : new NtpConnectionInfo(server, timeoutMillis);
+ }
}
diff --git a/core/java/android/util/TrustedTime.java b/core/java/android/util/TrustedTime.java
index 1360f87..f41fe85 100644
--- a/core/java/android/util/TrustedTime.java
+++ b/core/java/android/util/TrustedTime.java
@@ -20,42 +20,48 @@
/**
* Interface that provides trusted time information, possibly coming from an NTP
- * server. Implementations may cache answers until {@link #forceRefresh()}.
+ * server.
*
* @hide
+ * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
*/
public interface TrustedTime {
/**
* Force update with an external trusted time source, returning {@code true}
* when successful.
+ *
+ * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
*/
+ @Deprecated
@UnsupportedAppUsage
public boolean forceRefresh();
/**
* Check if this instance has cached a response from a trusted time source.
+ *
+ * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
*/
+ @Deprecated
@UnsupportedAppUsage
- public boolean hasCache();
+ boolean hasCache();
/**
* Return time since last trusted time source contact, or
* {@link Long#MAX_VALUE} if never contacted.
+ *
+ * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
*/
+ @Deprecated
@UnsupportedAppUsage
public long getCacheAge();
/**
- * Return certainty of cached trusted time in milliseconds, or
- * {@link Long#MAX_VALUE} if never contacted. Smaller values are more
- * precise.
- */
- public long getCacheCertainty();
-
- /**
* Return current time similar to {@link System#currentTimeMillis()},
* possibly using a cached authoritative time source.
+ *
+ * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
*/
+ @Deprecated
@UnsupportedAppUsage
- public long currentTimeMillis();
+ long currentTimeMillis();
}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 9e23c28..9fbc1b7 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -350,7 +350,12 @@
* (boolean) Whether screenshot flow going to the corner (instead of shown in a notification)
* is enabled.
*/
- public static final String SCREENSHOT_CORNER_FLOW = "screenshot_corner_flow";
+ public static final String SCREENSHOT_CORNER_FLOW = "enable_screenshot_corner_flow";
+
+ /**
+ * (boolean) Whether scrolling screenshots are enabled.
+ */
+ public static final String SCREENSHOT_SCROLLING_ENABLED = "enable_screenshot_scrolling";
private SystemUiDeviceConfigFlags() {
}
diff --git a/core/java/com/android/internal/widget/RecyclerView.java b/core/java/com/android/internal/widget/RecyclerView.java
index 43a227a..d7a01c4 100644
--- a/core/java/com/android/internal/widget/RecyclerView.java
+++ b/core/java/com/android/internal/widget/RecyclerView.java
@@ -2011,13 +2011,27 @@
}
if (!dispatchNestedPreFling(velocityX, velocityY)) {
- final boolean canScroll = canScrollHorizontal || canScrollVertical;
- dispatchNestedFling(velocityX, velocityY, canScroll);
+ final View firstChild = mLayout.getChildAt(0);
+ final View lastChild = mLayout.getChildAt(mLayout.getChildCount() - 1);
+ boolean consumed = false;
+ if (velocityY < 0) {
+ consumed = getChildAdapterPosition(firstChild) > 0
+ || firstChild.getTop() < getPaddingTop();
+ }
+
+ if (velocityY > 0) {
+ consumed = getChildAdapterPosition(lastChild) < mAdapter.getItemCount() - 1
+ || lastChild.getBottom() > getHeight() - getPaddingBottom();
+ }
+
+ dispatchNestedFling(velocityX, velocityY, consumed);
if (mOnFlingListener != null && mOnFlingListener.onFling(velocityX, velocityY)) {
return true;
}
+ final boolean canScroll = canScrollHorizontal || canScrollVertical;
+
if (canScroll) {
velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index fa64fd1..adedffd 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -52,42 +52,34 @@
using namespace android;
-jstring encodedFormatToString(JNIEnv* env, SkEncodedImageFormat format) {
- const char* mimeType;
+const char* getMimeType(SkEncodedImageFormat format) {
switch (format) {
case SkEncodedImageFormat::kBMP:
- mimeType = "image/bmp";
- break;
+ return "image/bmp";
case SkEncodedImageFormat::kGIF:
- mimeType = "image/gif";
- break;
+ return "image/gif";
case SkEncodedImageFormat::kICO:
- mimeType = "image/x-ico";
- break;
+ return "image/x-ico";
case SkEncodedImageFormat::kJPEG:
- mimeType = "image/jpeg";
- break;
+ return "image/jpeg";
case SkEncodedImageFormat::kPNG:
- mimeType = "image/png";
- break;
+ return "image/png";
case SkEncodedImageFormat::kWEBP:
- mimeType = "image/webp";
- break;
+ return "image/webp";
case SkEncodedImageFormat::kHEIF:
- mimeType = "image/heif";
- break;
+ return "image/heif";
case SkEncodedImageFormat::kWBMP:
- mimeType = "image/vnd.wap.wbmp";
- break;
+ return "image/vnd.wap.wbmp";
case SkEncodedImageFormat::kDNG:
- mimeType = "image/x-adobe-dng";
- break;
+ return "image/x-adobe-dng";
default:
- mimeType = nullptr;
- break;
+ return nullptr;
}
+}
+jstring getMimeTypeAsJavaString(JNIEnv* env, SkEncodedImageFormat format) {
jstring jstr = nullptr;
+ const char* mimeType = getMimeType(format);
if (mimeType) {
// NOTE: Caller should env->ExceptionCheck() for OOM
// (can't check for nullptr as it's a valid return value)
@@ -289,10 +281,9 @@
// Set the options and return if the client only wants the size.
if (options != NULL) {
- jstring mimeType = encodedFormatToString(
- env, (SkEncodedImageFormat)codec->getEncodedFormat());
+ jstring mimeType = getMimeTypeAsJavaString(env, codec->getEncodedFormat());
if (env->ExceptionCheck()) {
- return nullObjectReturn("OOM in encodedFormatToString()");
+ return nullObjectReturn("OOM in getMimeTypeAsJavaString()");
}
env->SetIntField(options, gOptions_widthFieldID, scaledWidth);
env->SetIntField(options, gOptions_heightFieldID, scaledHeight);
diff --git a/core/jni/android/graphics/BitmapFactory.h b/core/jni/android/graphics/BitmapFactory.h
index e37c98d..45bffc4 100644
--- a/core/jni/android/graphics/BitmapFactory.h
+++ b/core/jni/android/graphics/BitmapFactory.h
@@ -26,6 +26,6 @@
extern jclass gBitmapConfig_class;
extern jmethodID gBitmapConfig_nativeToConfigMethodID;
-jstring encodedFormatToString(JNIEnv* env, SkEncodedImageFormat format);
+jstring getMimeTypeAsJavaString(JNIEnv*, SkEncodedImageFormat);
#endif // _ANDROID_GRAPHICS_BITMAP_FACTORY_H_
diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp
index f18632d..06b4ff8 100644
--- a/core/jni/android/graphics/BitmapRegionDecoder.cpp
+++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp
@@ -197,7 +197,7 @@
env->SetIntField(options, gOptions_heightFieldID, bitmap.height());
env->SetObjectField(options, gOptions_mimeFieldID,
- encodedFormatToString(env, (SkEncodedImageFormat)brd->getEncodedFormat()));
+ getMimeTypeAsJavaString(env, brd->getEncodedFormat()));
if (env->ExceptionCheck()) {
return nullObjectReturn("OOM in encodedFormatToString()");
}
diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp
index 627f8f5..a900286 100644
--- a/core/jni/android/graphics/ImageDecoder.cpp
+++ b/core/jni/android/graphics/ImageDecoder.cpp
@@ -475,7 +475,7 @@
static jstring ImageDecoder_nGetMimeType(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
- return encodedFormatToString(env, decoder->mCodec->getEncodedFormat());
+ return getMimeTypeAsJavaString(env, decoder->mCodec->getEncodedFormat());
}
static jobject ImageDecoder_nGetColorSpace(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/core/jni/android/graphics/MimeType.h
similarity index 85%
copy from media/java/android/media/RouteSessionInfo.aidl
copy to core/jni/android/graphics/MimeType.h
index fb5d836..38a579c 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/core/jni/android/graphics/MimeType.h
@@ -14,6 +14,8 @@
* limitations under the License.
*/
-package android.media;
+#pragma once
-parcelable RouteSessionInfo;
+#include "SkEncodedImageFormat.h"
+
+const char* getMimeType(SkEncodedImageFormat);
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index ff596b4..062b886 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -220,8 +220,12 @@
// ----------------------------------------------------------------------------
+static std::unique_ptr<DynamicLibManager> sDynamicLibManager =
+ std::make_unique<DynamicLibManager>();
+
// Let the opaque type AAssetManager refer to a guarded AssetManager2 instance.
struct GuardedAssetManager : public ::AAssetManager {
+ GuardedAssetManager() : guarded_assetmanager(sDynamicLibManager.get()) {}
Guarded<AssetManager2> guarded_assetmanager;
};
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 5039213..466544c 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -746,9 +746,6 @@
const userid_t user_id = multiuser_get_user_id(uid);
const std::string user_source = StringPrintf("/mnt/user/%d", user_id);
- const std::string pass_through_source = StringPrintf("/mnt/pass_through/%d", user_id);
- bool isFuse = GetBoolProperty(kPropFuse, false);
-
// Shell is neither AID_ROOT nor AID_EVERYBODY. Since it equally needs 'execute' access to
// /mnt/user/0 to 'adb shell ls /sdcard' for instance, we set the uid bit of /mnt/user/0 to
// AID_SHELL. This gives shell access along with apps running as group everybody (user 0 apps)
@@ -757,9 +754,15 @@
PrepareDir(user_source, 0710, user_id ? AID_ROOT : AID_SHELL,
multiuser_get_uid(user_id, AID_EVERYBODY), fail_fn);
+ bool isFuse = GetBoolProperty(kPropFuse, false);
+
if (isFuse) {
if (mount_mode == MOUNT_EXTERNAL_PASS_THROUGH) {
+ const std::string pass_through_source = StringPrintf("/mnt/pass_through/%d", user_id);
BindMount(pass_through_source, "/storage", fail_fn);
+ } else if (mount_mode == MOUNT_EXTERNAL_INSTALLER) {
+ const std::string installer_source = StringPrintf("/mnt/installer/%d", user_id);
+ BindMount(installer_source, "/storage", fail_fn);
} else {
BindMount(user_source, "/storage", fail_fn);
}
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index b83b31c..cd3887e 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2553,4 +2553,8 @@
// OS: R
MANAGE_EXTERNAL_STORAGE = 1822;
+ // Open: Settings > DND > People
+ // OS: R
+ DND_PEOPLE = 1823;
+
}
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index 9054d54..0fca1d1 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -154,4 +154,5 @@
SET_AUTO_TIME = 127;
SET_AUTO_TIME_ZONE = 128;
SET_PACKAGES_PROTECTED = 129;
+ SET_FACTORY_RESET_PROTECTION = 130;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c050146..d4768c0 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -445,6 +445,7 @@
<protected-broadcast android:name="android.internal.policy.action.BURN_IN_PROTECTION" />
<protected-broadcast android:name="android.app.action.SYSTEM_UPDATE_POLICY_CHANGED" />
+ <protected-broadcast android:name="android.app.action.RESET_PROTECTION_POLICY_CHANGED" />
<protected-broadcast android:name="android.app.action.DEVICE_OWNER_CHANGED" />
<protected-broadcast android:name="android.app.action.MANAGED_USER_CREATED" />
@@ -640,10 +641,6 @@
<protected-broadcast android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY" />
- <!-- NETWORK_SET_TIME moved from com.android.phone to system server. It should ultimately be
- removed. -->
- <protected-broadcast android:name="android.telephony.action.NETWORK_SET_TIME" />
-
<!-- For tether entitlement recheck-->
<protected-broadcast
android:name="com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM" />
@@ -1791,6 +1788,11 @@
android:label="@string/permlab_preferredPaymentInfo"
android:protectionLevel="normal" />
+ <!-- @SystemApi Allows an internal user to use privileged SecureElement APIs.
+ @hide -->
+ <permission android:name="android.permission.SECURE_ELEMENT_PRIVILEGED"
+ android:protectionLevel="signature|privileged" />
+
<!-- @deprecated This permission used to allow too broad access to sensitive methods and all its
uses have been replaced by a more appropriate permission. Most uses have been replaced with
a NETWORK_STACK or NETWORK_SETTINGS check. Please look up the documentation of the
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 6691d4c..6f554f02 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1877,6 +1877,8 @@
<string name="config_defaultCallRedirection" translatable="false"></string>
<!-- The name of the package that will hold the call screening role by default. -->
<string name="config_defaultCallScreening" translatable="false"></string>
+ <!-- The name of the package that will hold the system gallery role. -->
+ <string name="config_systemGallery" translatable="false">com.android.gallery</string>
<!-- Enable/disable default bluetooth profiles:
HSP_AG, ObexObjectPush, Audio, NAP -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 37c9710..6cf6a68 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3030,6 +3030,8 @@
<public name="config_defaultCallRedirection" />
<!-- @hide @SystemApi -->
<public name="config_defaultCallScreening" />
+ <!-- @hide @SystemApi @TestApi -->
+ <public name="config_systemGallery" />
</public-group>
<public-group type="bool" first-id="0x01110005">
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index 3586216a..93f4b51 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -124,6 +124,10 @@
public void setSoftKeyboardCallbackEnabled(boolean enabled) {}
+ public boolean switchToInputMethod(String imeId) {
+ return false;
+ }
+
public boolean isAccessibilityButtonAvailable() {
return false;
}
diff --git a/core/tests/overlaytests/remount/TEST_MAPPING b/core/tests/overlaytests/remount/TEST_MAPPING
new file mode 100644
index 0000000..54dd431
--- /dev/null
+++ b/core/tests/overlaytests/remount/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name" : "OverlayRemountedTest"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/core/tests/overlaytests/remount/host/Android.bp b/core/tests/overlaytests/remount/host/Android.bp
new file mode 100644
index 0000000..3825c55
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/Android.bp
@@ -0,0 +1,28 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_test_host {
+ name: "OverlayRemountedTest",
+ srcs: ["src/**/*.java"],
+ libs: [
+ "tradefed",
+ "junit",
+ ],
+ test_suites: ["general-tests"],
+ java_resources: [
+ ":OverlayRemountedTest_SharedLibrary",
+ ":OverlayRemountedTest_SharedLibraryOverlay",
+ ":OverlayRemountedTest_Target",
+ ],
+}
diff --git a/core/tests/overlaytests/remount/host/AndroidTest.xml b/core/tests/overlaytests/remount/host/AndroidTest.xml
new file mode 100644
index 0000000..11eadf1a
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<configuration description="Test module config for OverlayRemountedTest">
+ <option name="test-tag" value="OverlayRemountedTest" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="remount" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.HostTest">
+ <option name="jar" value="OverlayRemountedTest.jar" />
+ </test>
+</configuration>
diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlayHostTest.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlayHostTest.java
new file mode 100644
index 0000000..84af187
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlayHostTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.overlaytest.remounted;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TemporaryFolder;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class OverlayHostTest extends BaseHostJUnit4Test {
+ private static final long TIME_OUT_MS = 30000;
+ private static final String RES_INSTRUMENTATION_ARG = "res";
+ private static final String OVERLAY_INSTRUMENTATION_ARG = "overlays";
+ private static final String RESOURCES_TYPE_SUFFIX = "_type";
+ private static final String RESOURCES_DATA_SUFFIX = "_data";
+
+ public final TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+ public final SystemPreparer mPreparer = new SystemPreparer(mTemporaryFolder, this::getDevice);
+
+ @Rule
+ public final RuleChain ruleChain = RuleChain.outerRule(mTemporaryFolder).around(mPreparer);
+ private Map<String, String> mLastResults;
+
+ /**
+ * Retrieves the values of the resources in the test package. The test package must use the
+ * {@link com.android.overlaytest.remounted.target.ResourceRetrievalRunner} instrumentation.
+ **/
+ void retrieveResource(String testPackageName, List<String> requiredOverlayPaths,
+ String... resourceNames) throws DeviceNotAvailableException {
+ final HashMap<String, String> args = new HashMap<>();
+ if (!requiredOverlayPaths.isEmpty()) {
+ // Enclose the require overlay paths in quotes so the arguments will be string arguments
+ // rather than file arguments.
+ args.put(OVERLAY_INSTRUMENTATION_ARG,
+ String.format("\"%s\"", String.join(" ", requiredOverlayPaths)));
+ }
+
+ if (resourceNames.length == 0) {
+ throw new IllegalArgumentException("Must specify at least one resource to retrieve.");
+ }
+
+ // Pass the names of the resources to retrieve into the test as one string.
+ args.put(RES_INSTRUMENTATION_ARG,
+ String.format("\"%s\"", String.join(" ", resourceNames)));
+
+ runDeviceTests(getDevice(), null, testPackageName, null, null, null, TIME_OUT_MS,
+ TIME_OUT_MS, TIME_OUT_MS, false, false, args);
+
+ // Retrieve the results of the most recently run test.
+ mLastResults = (getLastDeviceRunResults().getRunMetrics() == mLastResults) ? null :
+ getLastDeviceRunResults().getRunMetrics();
+ }
+
+ /** Returns the base resource directories of the specified packages. */
+ List<String> getPackagePaths(String... packageNames)
+ throws DeviceNotAvailableException {
+ final ArrayList<String> paths = new ArrayList<>();
+ for (String packageName : packageNames) {
+ // Use the package manager shell command to find the path of the package.
+ final String result = getDevice().executeShellCommand(
+ String.format("pm dump %s | grep \"resourcePath=\"", packageName));
+ assertNotNull("Failed to find path for package " + packageName, result);
+ int splitIndex = result.indexOf('=');
+ assertTrue(splitIndex >= 0);
+ paths.add(result.substring(splitIndex + 1).trim());
+ }
+ return paths;
+ }
+
+ /** Builds the full name of a resource in the form package:type/entry. */
+ String resourceName(String pkg, String type, String entry) {
+ return String.format("%s:%s/%s", pkg, type, entry);
+ }
+
+ /**
+ * Asserts that the type and data of a a previously retrieved is the same as expected.
+ * @param resourceName the full name of the resource in the form package:type/entry
+ * @param type the expected {@link android.util.TypedValue} type of the resource
+ * @param data the expected value of the resource when coerced to a string using
+ * {@link android.util.TypedValue#coerceToString()}
+ **/
+ void assertResource(String resourceName, int type, String data) {
+ assertNotNull("Failed to get test results", mLastResults);
+ assertNotEquals("No resource values were retrieved", mLastResults.size(), 0);
+ assertEquals("" + type, mLastResults.get(resourceName + RESOURCES_TYPE_SUFFIX));
+ assertEquals("" + data, mLastResults.get(resourceName + RESOURCES_DATA_SUFFIX));
+ }
+}
diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java
new file mode 100644
index 0000000..4939e16
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.overlaytest.remounted;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class OverlaySharedLibraryTest extends OverlayHostTest {
+ private static final String TARGET_APK = "OverlayRemountedTest_Target.apk";
+ private static final String TARGET_PACKAGE = "com.android.overlaytest.remounted.target";
+ private static final String SHARED_LIBRARY_APK =
+ "OverlayRemountedTest_SharedLibrary.apk";
+ private static final String SHARED_LIBRARY_PACKAGE =
+ "com.android.overlaytest.remounted.shared_library";
+ private static final String SHARED_LIBRARY_OVERLAY_APK =
+ "OverlayRemountedTest_SharedLibraryOverlay.apk";
+ private static final String SHARED_LIBRARY_OVERLAY_PACKAGE =
+ "com.android.overlaytest.remounted.shared_library.overlay";
+
+ @Test
+ public void testSharedLibrary() throws Exception {
+ final String targetResource = resourceName(TARGET_PACKAGE, "bool",
+ "uses_shared_library_overlaid");
+ final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool",
+ "shared_library_overlaid");
+
+ mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk")
+ .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE)
+ .reboot()
+ .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, false)
+ .installResourceApk(TARGET_APK, TARGET_PACKAGE);
+
+ // The shared library resource is not currently overlaid.
+ retrieveResource(Collections.emptyList(), targetResource, libraryResource);
+ assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "false");
+ assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "false");
+
+ // Overlay the shared library resource.
+ mPreparer.setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true);
+ retrieveResource(getPackagePaths(SHARED_LIBRARY_OVERLAY_PACKAGE), targetResource,
+ libraryResource);
+ assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "true");
+ assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "true");
+ }
+
+ @Test
+ public void testSharedLibraryPreEnabled() throws Exception {
+ final String targetResource = resourceName(TARGET_PACKAGE, "bool",
+ "uses_shared_library_overlaid");
+ final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool",
+ "shared_library_overlaid");
+
+ mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk")
+ .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE)
+ .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true)
+ .reboot()
+ .installResourceApk(TARGET_APK, TARGET_PACKAGE);
+
+ retrieveResource(getPackagePaths(SHARED_LIBRARY_OVERLAY_PACKAGE), targetResource,
+ libraryResource);
+ assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "true");
+ assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "true");
+ }
+
+ private void retrieveResource(List<String> requiredOverlayPaths, String... resourceNames)
+ throws DeviceNotAvailableException {
+ retrieveResource(TARGET_PACKAGE, requiredOverlayPaths, resourceNames);
+ }
+}
diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java
new file mode 100644
index 0000000..7028f2f
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.overlaytest.remounted;
+
+import static org.junit.Assert.assertTrue;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+
+import org.junit.Assert;
+import org.junit.rules.ExternalResource;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeoutException;
+
+class SystemPreparer extends ExternalResource {
+ private static final long REBOOT_SLEEP_MS = 30000;
+ private static final long OVERLAY_ENABLE_TIMEOUT_MS = 20000;
+
+ // The paths of the files pushed onto the device through this rule.
+ private ArrayList<String> mPushedFiles = new ArrayList<>();
+
+ // The package names of packages installed through this rule.
+ private ArrayList<String> mInstalledPackages = new ArrayList<>();
+
+ private final TemporaryFolder mHostTempFolder;
+ private final DeviceProvider mDeviceProvider;
+
+ SystemPreparer(TemporaryFolder hostTempFolder, DeviceProvider deviceProvider) {
+ mHostTempFolder = hostTempFolder;
+ mDeviceProvider = deviceProvider;
+ }
+
+ /** Copies a file within the host test jar to a path on device. */
+ SystemPreparer pushResourceFile(String resourcePath,
+ String outputPath) throws DeviceNotAvailableException, IOException {
+ final ITestDevice device = mDeviceProvider.getDevice();
+ assertTrue(device.pushFile(copyResourceToTemp(resourcePath), outputPath));
+ mPushedFiles.add(outputPath);
+ return this;
+ }
+
+ /** Installs an APK within the host test jar onto the device. */
+ SystemPreparer installResourceApk(String resourcePath, String packageName)
+ throws DeviceNotAvailableException, IOException {
+ final ITestDevice device = mDeviceProvider.getDevice();
+ final File tmpFile = copyResourceToTemp(resourcePath);
+ final String result = device.installPackage(tmpFile, true);
+ Assert.assertNull(result);
+ mInstalledPackages.add(packageName);
+ return this;
+ }
+
+ /** Sets the enable state of an overlay pacakage. */
+ SystemPreparer setOverlayEnabled(String packageName, boolean enabled)
+ throws ExecutionException, TimeoutException {
+ final ITestDevice device = mDeviceProvider.getDevice();
+
+ // Wait for the overlay to change its enabled state.
+ final FutureTask<Boolean> enabledListener = new FutureTask<>(() -> {
+ while (true) {
+ device.executeShellCommand(String.format("cmd overlay %s %s",
+ enabled ? "enable" : "disable", packageName));
+
+ final String pattern = (enabled ? "[x]" : "[ ]") + " " + packageName;
+ if (device.executeShellCommand("cmd overlay list").contains(pattern)) {
+ return true;
+ }
+ }
+ });
+
+ final Executor executor = (cmd) -> new Thread(cmd).start();
+ executor.execute(enabledListener);
+ try {
+ enabledListener.get(OVERLAY_ENABLE_TIMEOUT_MS, MILLISECONDS);
+ } catch (InterruptedException ignored) {
+ }
+
+ return this;
+ }
+
+ /** Restarts the device and waits until after boot is completed. */
+ SystemPreparer reboot() throws DeviceNotAvailableException {
+ final ITestDevice device = mDeviceProvider.getDevice();
+ device.executeShellCommand("stop");
+ device.executeShellCommand("start");
+ try {
+ // Sleep until the device is ready for test execution.
+ Thread.sleep(REBOOT_SLEEP_MS);
+ } catch (InterruptedException ignored) {
+ }
+
+ return this;
+ }
+
+ /** Copies a file within the host test jar to a temporary file on the host machine. */
+ private File copyResourceToTemp(String resourcePath) throws IOException {
+ final File tempFile = mHostTempFolder.newFile(resourcePath);
+ final ClassLoader classLoader = getClass().getClassLoader();
+ try (InputStream assetIs = classLoader.getResource(resourcePath).openStream();
+ FileOutputStream assetOs = new FileOutputStream(tempFile)) {
+ if (assetIs == null) {
+ throw new IllegalStateException("Failed to find resource " + resourcePath);
+ }
+
+ int b;
+ while ((b = assetIs.read()) >= 0) {
+ assetOs.write(b);
+ }
+ }
+
+ return tempFile;
+ }
+
+ /** Removes installed packages and files that were pushed to the device. */
+ @Override
+ protected void after() {
+ final ITestDevice device = mDeviceProvider.getDevice();
+ try {
+ for (final String file : mPushedFiles) {
+ device.deleteFile(file);
+ }
+ for (final String packageName : mInstalledPackages) {
+ device.uninstallPackage(packageName);
+ }
+ } catch (DeviceNotAvailableException e) {
+ Assert.fail(e.toString());
+ }
+ }
+
+ interface DeviceProvider {
+ ITestDevice getDevice();
+ }
+}
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp
new file mode 100644
index 0000000..ffb0572
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp
@@ -0,0 +1,19 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+ name: "OverlayRemountedTest_SharedLibrary",
+ sdk_version: "current",
+ aaptflags: ["--shared-lib"],
+}
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml
new file mode 100644
index 0000000..06e3f6a
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.overlaytest.remounted.shared_library">
+ <application>
+ <library android:name="com.android.overlaytest.remounted.shared_library" />
+ </application>
+</manifest>
\ No newline at end of file
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml
new file mode 100644
index 0000000..1b06f6d
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <overlayable name="TestResources">
+ <policy type="public">
+ <item type="bool" name="shared_library_overlaid" />
+ </policy>
+ </overlayable>
+</resources>
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml
new file mode 100644
index 0000000..5b9db16
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <public type="bool" name="shared_library_overlaid" id="0x00050001"/>
+</resources>
\ No newline at end of file
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml
new file mode 100644
index 0000000..2dc47a7
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <bool name="shared_library_overlaid">false</bool>
+</resources>
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp
new file mode 100644
index 0000000..0d29aec
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp
@@ -0,0 +1,18 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+ name: "OverlayRemountedTest_SharedLibraryOverlay",
+ sdk_version: "current",
+}
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..53a4e61
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.overlaytest.remounted.shared_library.overlay">
+ <application android:hasCode="false" />
+ <overlay android:targetPackage="com.android.overlaytest.remounted.shared_library"
+ android:targetName="TestResources" />
+</manifest>
\ No newline at end of file
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml
new file mode 100644
index 0000000..f66448a
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <bool name="shared_library_overlaid">true</bool>
+</resources>
diff --git a/core/tests/overlaytests/remount/target/Android.bp b/core/tests/overlaytests/remount/target/Android.bp
new file mode 100644
index 0000000..83f9f28
--- /dev/null
+++ b/core/tests/overlaytests/remount/target/Android.bp
@@ -0,0 +1,20 @@
+// 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.
+
+android_test_helper_app {
+ name: "OverlayRemountedTest_Target",
+ srcs: ["src/**/*.java"],
+ sdk_version: "test_current",
+ libs: ["OverlayRemountedTest_SharedLibrary"],
+}
diff --git a/core/tests/overlaytests/remount/target/AndroidManifest.xml b/core/tests/overlaytests/remount/target/AndroidManifest.xml
new file mode 100644
index 0000000..32fec43
--- /dev/null
+++ b/core/tests/overlaytests/remount/target/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.overlaytest.remounted.target">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <uses-library android:name="com.android.overlaytest.remounted.shared_library"
+ android:required="true" />
+ </application>
+
+ <instrumentation android:name="com.android.overlaytest.remounted.target.ResourceRetrievalRunner"
+ android:targetPackage="com.android.overlaytest.remounted.target"
+ android:label="Remounted system RRO tests" />
+</manifest>
diff --git a/core/tests/overlaytests/remount/target/res/values/values.xml b/core/tests/overlaytests/remount/target/res/values/values.xml
new file mode 100644
index 0000000..b5f444a
--- /dev/null
+++ b/core/tests/overlaytests/remount/target/res/values/values.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:sharedlib="http://schemas.android.com/apk/res/com.android.overlaytest.remounted.shared_library">
+ <bool name="uses_shared_library_overlaid">@sharedlib:bool/shared_library_overlaid</bool>
+</resources>
diff --git a/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java b/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java
new file mode 100644
index 0000000..2e4c211
--- /dev/null
+++ b/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.overlaytest.remounted.target;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.TypedValue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.Executor;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An {@link Instrumentation} that retrieves the value of specified resources within the
+ * application.
+ **/
+public class ResourceRetrievalRunner extends Instrumentation {
+ private static final String TAG = ResourceRetrievalRunner.class.getSimpleName();
+
+ // A list of whitespace separated resource names of which to retrieve the resource values.
+ private static final String RESOURCE_LIST_TAG = "res";
+
+ // A list of whitespace separated overlay package paths that must be present before retrieving
+ // resource values.
+ private static final String REQUIRED_OVERLAYS_LIST_TAG = "overlays";
+
+ // The suffixes of the keys returned from the instrumentation. To retrieve the type of a
+ // resource looked up with the instrumentation, append the {@link #RESOURCES_TYPE_SUFFIX} suffix
+ // to the end of the name of the resource. For the value of a resource, use
+ // {@link #RESOURCES_DATA_SUFFIX} instead.
+ private static final String RESOURCES_TYPE_SUFFIX = "_type";
+ private static final String RESOURCES_DATA_SUFFIX = "_data";
+
+ // The amount of time in seconds to wait for the overlays to be present in the AssetManager.
+ private static final int OVERLAY_PATH_TIMEOUT = 60;
+
+ private final ArrayList<String> mResourceNames = new ArrayList<>();
+ private final ArrayList<String> mOverlayPaths = new ArrayList<>();
+ private final Bundle mResult = new Bundle();
+
+ /**
+ * Receives the instrumentation arguments and runs the resource retrieval.
+ * The entry with key {@link #RESOURCE_LIST_TAG} in the {@link Bundle} arguments is a
+ * whitespace separated string of resource names of which to retrieve the resource values.
+ * The entry with key {@link #REQUIRED_OVERLAYS_LIST_TAG} in the {@link Bundle} arguments is a
+ * whitespace separated string of overlay package paths prefixes that must be present before
+ * retrieving the resource values.
+ */
+ @Override
+ public void onCreate(Bundle arguments) {
+ super.onCreate(arguments);
+ mResourceNames.addAll(Arrays.asList(arguments.getString(RESOURCE_LIST_TAG).split(" ")));
+ if (arguments.containsKey(REQUIRED_OVERLAYS_LIST_TAG)) {
+ mOverlayPaths.addAll(Arrays.asList(
+ arguments.getString(REQUIRED_OVERLAYS_LIST_TAG).split(" ")));
+ }
+ start();
+ }
+
+ @Override
+ public void onStart() {
+ final Resources res = getContext().getResources();
+ res.getAssets().setResourceResolutionLoggingEnabled(true);
+
+ if (!mOverlayPaths.isEmpty()) {
+ Log.d(TAG, String.format("Waiting for overlay paths [%s]",
+ String.join(",", mOverlayPaths)));
+
+ // Wait for all required overlays to be present in the AssetManager.
+ final FutureTask<Boolean> overlayListener = new FutureTask<>(() -> {
+ while (!mOverlayPaths.isEmpty()) {
+ final String[] apkPaths = res.getAssets().getApkPaths();
+ for (String path : apkPaths) {
+ for (String overlayPath : mOverlayPaths) {
+ if (path.startsWith(overlayPath)) {
+ mOverlayPaths.remove(overlayPath);
+ break;
+ }
+ }
+ }
+ }
+ return true;
+ });
+
+ try {
+ final Executor executor = (t) -> new Thread(t).start();
+ executor.execute(overlayListener);
+ overlayListener.get(OVERLAY_PATH_TIMEOUT, TimeUnit.SECONDS);
+ } catch (Exception e) {
+ Log.e(TAG, String.format("Failed to wait for required overlays [%s]",
+ String.join(",", mOverlayPaths)), e);
+ finish(Activity.RESULT_CANCELED, mResult);
+ }
+ }
+
+ // Retrieve the values for each resource passed in.
+ final TypedValue typedValue = new TypedValue();
+ for (final String resourceName : mResourceNames) {
+ try {
+ final int resId = res.getIdentifier(resourceName, null, null);
+ res.getValue(resId, typedValue, true);
+ Log.d(TAG, String.format("Resolution for 0x%s: %s", Integer.toHexString(resId),
+ res.getAssets().getLastResourceResolution()));
+ } catch (Resources.NotFoundException e) {
+ Log.e(TAG, "Failed to retrieve value for resource " + resourceName, e);
+ finish(Activity.RESULT_CANCELED, mResult);
+ }
+
+ putValue(resourceName, typedValue);
+ }
+
+ finish(Activity.RESULT_OK, mResult);
+ }
+
+ private void putValue(String resourceName, TypedValue value) {
+ mResult.putInt(resourceName + RESOURCES_TYPE_SUFFIX, value.type);
+ final CharSequence textValue = value.coerceToString();
+ mResult.putString(resourceName + RESOURCES_DATA_SUFFIX,
+ textValue == null ? "null" : textValue.toString());
+ }
+}
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 8765719..3f2f349 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -44,6 +44,7 @@
"AttributeResolution.cpp",
"ChunkIterator.cpp",
"ConfigDescription.cpp",
+ "DynamicLibManager.cpp",
"Idmap.cpp",
"LoadedArsc.cpp",
"Locale.cpp",
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index ca4143f..8cfd2d8 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -25,6 +25,7 @@
#include "android-base/logging.h"
#include "android-base/stringprintf.h"
+#include "androidfw/DynamicLibManager.h"
#include "androidfw/ResourceUtils.h"
#include "androidfw/Util.h"
#include "utils/ByteOrder.h"
@@ -66,7 +67,12 @@
StringPoolRef entry_string_ref;
};
-AssetManager2::AssetManager2() {
+AssetManager2::AssetManager2() : dynamic_lib_manager_(std::make_unique<DynamicLibManager>()) {
+ memset(&configuration_, 0, sizeof(configuration_));
+}
+
+AssetManager2::AssetManager2(DynamicLibManager* dynamic_lib_manager)
+ : dynamic_lib_manager_(dynamic_lib_manager) {
memset(&configuration_, 0, sizeof(configuration_));
}
@@ -85,25 +91,45 @@
package_groups_.clear();
package_ids_.fill(0xff);
- // A mapping from apk assets path to the runtime package id of its first loaded package.
+ // Overlay resources are not directly referenced by an application so their resource ids
+ // can change throughout the application's lifetime. Assign overlay package ids last.
+ std::vector<const ApkAssets*> sorted_apk_assets(apk_assets_);
+ std::stable_partition(sorted_apk_assets.begin(), sorted_apk_assets.end(), [](const ApkAssets* a) {
+ return !a->IsOverlay();
+ });
+
std::unordered_map<std::string, uint8_t> apk_assets_package_ids;
+ std::unordered_map<std::string, uint8_t> package_name_package_ids;
- // 0x01 is reserved for the android package.
- int next_package_id = 0x02;
- const size_t apk_assets_count = apk_assets_.size();
- for (size_t i = 0; i < apk_assets_count; i++) {
- const ApkAssets* apk_assets = apk_assets_[i];
- const LoadedArsc* loaded_arsc = apk_assets->GetLoadedArsc();
-
- for (const std::unique_ptr<const LoadedPackage>& package : loaded_arsc->GetPackages()) {
- // Get the package ID or assign one if a shared library.
- int package_id;
- if (package->IsDynamic()) {
- package_id = next_package_id++;
- } else {
- package_id = package->GetPackageId();
+ // Assign stable package ids to application packages.
+ uint8_t next_available_package_id = 0U;
+ for (const auto& apk_assets : sorted_apk_assets) {
+ for (const auto& package : apk_assets->GetLoadedArsc()->GetPackages()) {
+ uint8_t package_id = package->GetPackageId();
+ if (package->IsOverlay()) {
+ package_id = GetDynamicLibManager()->FindUnassignedId(next_available_package_id);
+ next_available_package_id = package_id + 1;
+ } else if (package->IsDynamic()) {
+ package_id = GetDynamicLibManager()->GetAssignedId(package->GetPackageName());
}
+ // Map the path of the apk assets to the package id of its first loaded package.
+ apk_assets_package_ids[apk_assets->GetPath()] = package_id;
+
+ // Map the package name of the package to the first loaded package with that package id.
+ package_name_package_ids[package->GetPackageName()] = package_id;
+ }
+ }
+
+ const int apk_assets_count = apk_assets_.size();
+ for (int i = 0; i < apk_assets_count; i++) {
+ const auto& apk_assets = apk_assets_[i];
+ for (const auto& package : apk_assets->GetLoadedArsc()->GetPackages()) {
+ const auto package_id_entry = package_name_package_ids.find(package->GetPackageName());
+ CHECK(package_id_entry != package_name_package_ids.end())
+ << "no package id assgined to package " << package->GetPackageName();
+ const uint8_t package_id = package_id_entry->second;
+
// Add the mapping for package ID to index if not present.
uint8_t idx = package_ids_[package_id];
if (idx == 0xff) {
@@ -115,7 +141,10 @@
// to take effect.
const auto& loaded_idmap = apk_assets->GetLoadedIdmap();
auto target_package_iter = apk_assets_package_ids.find(loaded_idmap->TargetApkPath());
- if (target_package_iter != apk_assets_package_ids.end()) {
+ if (target_package_iter == apk_assets_package_ids.end()) {
+ LOG(INFO) << "failed to find target package for overlay "
+ << loaded_idmap->OverlayApkPath();
+ } else {
const uint8_t target_package_id = target_package_iter->second;
const uint8_t target_idx = package_ids_[target_package_id];
CHECK(target_idx != 0xff) << "overlay added to apk_assets_package_ids but does not"
@@ -123,7 +152,7 @@
PackageGroup& target_package_group = package_groups_[target_idx];
- // Create a special dynamic reference table for the overlay to rewite references to
+ // Create a special dynamic reference table for the overlay to rewrite references to
// overlay resources as references to the target resources they overlay.
auto overlay_table = std::make_shared<OverlayDynamicRefTable>(
loaded_idmap->GetOverlayDynamicRefTable(target_package_id));
@@ -153,8 +182,6 @@
package_group->dynamic_ref_table->mEntries.replaceValueFor(
package_name, static_cast<uint8_t>(entry.package_id));
}
-
- apk_assets_package_ids.insert(std::make_pair(apk_assets->GetPath(), package_id));
}
}
@@ -567,7 +594,7 @@
if (resource_resolution_logging_enabled_) {
last_resolution_.steps.push_back(
Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result.config.toString(),
- &package_group.packages_[0].loaded_package_->GetPackageName()});
+ overlay_result.package_name});
}
}
}
@@ -1279,6 +1306,16 @@
return 0;
}
+DynamicLibManager* AssetManager2::GetDynamicLibManager() const {
+ auto dynamic_lib_manager =
+ std::get_if<std::unique_ptr<DynamicLibManager>>(&dynamic_lib_manager_);
+ if (dynamic_lib_manager) {
+ return (*dynamic_lib_manager).get();
+ } else {
+ return *std::get_if<DynamicLibManager*>(&dynamic_lib_manager_);
+ }
+}
+
std::unique_ptr<Theme> AssetManager2::NewTheme() {
return std::unique_ptr<Theme>(new Theme(this));
}
diff --git a/libs/androidfw/DynamicLibManager.cpp b/libs/androidfw/DynamicLibManager.cpp
new file mode 100644
index 0000000..895b769
--- /dev/null
+++ b/libs/androidfw/DynamicLibManager.cpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "androidfw/DynamicLibManager.h"
+
+namespace android {
+
+uint8_t DynamicLibManager::GetAssignedId(const std::string& library_package_name) {
+ auto lib_entry = shared_lib_package_ids_.find(library_package_name);
+ if (lib_entry != shared_lib_package_ids_.end()) {
+ return lib_entry->second;
+ }
+
+ return shared_lib_package_ids_[library_package_name] = next_package_id_++;
+}
+
+uint8_t DynamicLibManager::FindUnassignedId(uint8_t start_package_id) {
+ return (start_package_id < next_package_id_) ? next_package_id_ : start_package_id;
+}
+
+} // namespace android
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index 00cbbca..b2cec2a 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -27,6 +27,7 @@
#include "androidfw/ApkAssets.h"
#include "androidfw/Asset.h"
#include "androidfw/AssetManager.h"
+#include "androidfw/DynamicLibManager.h"
#include "androidfw/ResourceTypes.h"
#include "androidfw/Util.h"
@@ -94,6 +95,7 @@
};
AssetManager2();
+ explicit AssetManager2(DynamicLibManager* dynamic_lib_manager);
// Sets/resets the underlying ApkAssets for this AssetManager. The ApkAssets
// are not owned by the AssetManager, and must have a longer lifetime.
@@ -371,6 +373,8 @@
// Retrieve the assigned package id of the package if loaded into this AssetManager
uint8_t GetAssignedPackageId(const LoadedPackage* package) const;
+ DynamicLibManager* GetDynamicLibManager() const;
+
// The ordered list of ApkAssets to search. These are not owned by the AssetManager, and must
// have a longer lifetime.
std::vector<const ApkAssets*> apk_assets_;
@@ -389,6 +393,9 @@
// may need to be purged.
ResTable_config configuration_;
+ // Component responsible for assigning package ids to shared libraries.
+ std::variant<std::unique_ptr<DynamicLibManager>, DynamicLibManager*> dynamic_lib_manager_;
+
// Cached set of bags. These are cached because they can inherit keys from parent bags,
// which involves some calculation.
std::unordered_map<uint32_t, util::unique_cptr<ResolvedBag>> cached_bags_;
diff --git a/libs/androidfw/include/androidfw/DynamicLibManager.h b/libs/androidfw/include/androidfw/DynamicLibManager.h
new file mode 100644
index 0000000..1ff7079
--- /dev/null
+++ b/libs/androidfw/include/androidfw/DynamicLibManager.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROIDFW_DYNAMICLIBMANAGER_H
+#define ANDROIDFW_DYNAMICLIBMANAGER_H
+
+#include <string>
+#include <unordered_map>
+
+#include "android-base/macros.h"
+
+namespace android {
+
+// Manages assigning resource ids for dynamic resources.
+class DynamicLibManager {
+ public:
+ DynamicLibManager() = default;
+
+ // Retrieves the assigned package id for the library.
+ uint8_t GetAssignedId(const std::string& library_package_name);
+
+ // Queries in ascending order for the first available package id that is not currently assigned to
+ // a library.
+ uint8_t FindUnassignedId(uint8_t start_package_id);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DynamicLibManager);
+
+ uint8_t next_package_id_ = 0x02;
+ std::unordered_map<std::string, uint8_t> shared_lib_package_ids_;
+};
+
+} // namespace android
+
+#endif //ANDROIDFW_DYNAMICLIBMANAGER_H
diff --git a/libs/androidfw/include/androidfw/MutexGuard.h b/libs/androidfw/include/androidfw/MutexGuard.h
index 64924f4..8891512 100644
--- a/libs/androidfw/include/androidfw/MutexGuard.h
+++ b/libs/androidfw/include/androidfw/MutexGuard.h
@@ -47,7 +47,8 @@
static_assert(!std::is_pointer<T>::value, "T must not be a raw pointer");
public:
- explicit Guarded() : guarded_() {
+ template <typename ...Args>
+ explicit Guarded(Args&& ...args) : guarded_(std::forward<Args>(args)...) {
}
template <typename U = T>
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index b3190be..2f6f3df 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -214,6 +214,25 @@
EXPECT_EQ(fix_package_id(appaslib::R::array::integerArray1, 0x02), value.data);
}
+TEST_F(AssetManager2Test, AssignsUnchangingPackageIdToSharedLibrary) {
+ DynamicLibManager lib_manager;
+ AssetManager2 assetmanager(&lib_manager);
+ assetmanager.SetApkAssets(
+ {lib_one_assets_.get(), lib_two_assets_.get(), libclient_assets_.get()});
+
+ AssetManager2 assetmanager2(&lib_manager);
+ assetmanager2.SetApkAssets(
+ {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()});
+
+ uint32_t res_id = assetmanager.GetResourceId("com.android.lib_one:string/foo");
+ ASSERT_NE(0U, res_id);
+
+ uint32_t res_id_2 = assetmanager2.GetResourceId("com.android.lib_one:string/foo");
+ ASSERT_NE(0U, res_id_2);
+
+ ASSERT_EQ(res_id, res_id_2);
+}
+
TEST_F(AssetManager2Test, GetSharedLibraryResourceName) {
AssetManager2 assetmanager;
assetmanager.SetApkAssets({lib_one_assets_.get()});
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 2314072..12681ae 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -746,7 +746,10 @@
glyphFunc(buffer.glyphs, buffer.pos);
sk_sp<SkTextBlob> textBlob(builder.make());
- mCanvas->drawTextBlob(textBlob, 0, 0, paintCopy);
+
+ apply_looper(&paintCopy, [&](const SkPaint& p) {
+ mCanvas->drawTextBlob(textBlob, 0, 0, p);
+ });
drawTextDecorations(x, y, totalAdvance, paintCopy);
}
@@ -783,8 +786,10 @@
xform[i - start].fTx = pos.x() - tan.y() * y - halfWidth * tan.x();
xform[i - start].fTy = pos.y() + tan.x() * y - halfWidth * tan.y();
}
-
- this->asSkCanvas()->drawTextBlob(builder.make(), 0, 0, paintCopy);
+ auto* finalCanvas = this->asSkCanvas();
+ apply_looper(&paintCopy, [&](const SkPaint& p) {
+ finalCanvas->drawTextBlob(builder.make(), 0, 0, paintCopy);
+ });
}
// ----------------------------------------------------------------------------
diff --git a/media/java/android/media/IMediaRoute2Provider.aidl b/media/java/android/media/IMediaRoute2Provider.aidl
index 28bf84d..5dd0b1c 100644
--- a/media/java/android/media/IMediaRoute2Provider.aidl
+++ b/media/java/android/media/IMediaRoute2Provider.aidl
@@ -24,7 +24,8 @@
*/
oneway interface IMediaRoute2Provider {
void setClient(IMediaRoute2ProviderClient client);
- void requestCreateSession(String packageName, String routeId, String routeType, long requestId);
+ void requestCreateSession(String packageName, String routeId,
+ String routeFeature, long requestId);
void releaseSession(String sessionId);
void selectRoute(String sessionId, String routeId);
diff --git a/media/java/android/media/IMediaRoute2ProviderClient.aidl b/media/java/android/media/IMediaRoute2ProviderClient.aidl
index bcb2336..e8abfdc 100644
--- a/media/java/android/media/IMediaRoute2ProviderClient.aidl
+++ b/media/java/android/media/IMediaRoute2ProviderClient.aidl
@@ -18,7 +18,7 @@
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2Info;
-import android.media.RouteSessionInfo;
+import android.media.RoutingSessionInfo;
import android.os.Bundle;
/**
@@ -26,7 +26,7 @@
*/
oneway interface IMediaRoute2ProviderClient {
void updateState(in MediaRoute2ProviderInfo providerInfo,
- in List<RouteSessionInfo> sessionInfos);
- void notifySessionCreated(in @nullable RouteSessionInfo sessionInfo, long requestId);
- void notifySessionInfoChanged(in RouteSessionInfo sessionInfo);
+ in List<RoutingSessionInfo> sessionInfos);
+ void notifySessionCreated(in @nullable RoutingSessionInfo sessionInfo, long requestId);
+ void notifySessionInfoChanged(in RoutingSessionInfo sessionInfo);
}
diff --git a/media/java/android/media/IMediaRouter2Client.aidl b/media/java/android/media/IMediaRouter2Client.aidl
index f90c7c5..bc7ebea 100644
--- a/media/java/android/media/IMediaRouter2Client.aidl
+++ b/media/java/android/media/IMediaRouter2Client.aidl
@@ -17,7 +17,7 @@
package android.media;
import android.media.MediaRoute2Info;
-import android.media.RouteSessionInfo;
+import android.media.RoutingSessionInfo;
import android.os.Bundle;
/**
@@ -28,7 +28,7 @@
void notifyRoutesAdded(in List<MediaRoute2Info> routes);
void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
void notifyRoutesChanged(in List<MediaRoute2Info> routes);
- void notifySessionCreated(in @nullable RouteSessionInfo sessionInfo, int requestId);
- void notifySessionInfoChanged(in RouteSessionInfo sessionInfo);
- void notifySessionReleased(in RouteSessionInfo sessionInfo);
+ void notifySessionCreated(in @nullable RoutingSessionInfo sessionInfo, int requestId);
+ void notifySessionInfoChanged(in RoutingSessionInfo sessionInfo);
+ void notifySessionReleased(in RoutingSessionInfo sessionInfo);
}
diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl
index e8af21e..e9add17 100644
--- a/media/java/android/media/IMediaRouter2Manager.aidl
+++ b/media/java/android/media/IMediaRouter2Manager.aidl
@@ -24,7 +24,7 @@
*/
oneway interface IMediaRouter2Manager {
void notifyRouteSelected(String packageName, in MediaRoute2Info route);
- void notifyRouteTypesChanged(String packageName, in List<String> routeTypes);
+ void notifyPreferredFeaturesChanged(String packageName, in List<String> preferredFeatures);
void notifyRoutesAdded(in List<MediaRoute2Info> routes);
void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
void notifyRoutesChanged(in List<MediaRoute2Info> routes);
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index b573f64..3cdaa07 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -22,8 +22,8 @@
import android.media.IMediaRouterClient;
import android.media.MediaRoute2Info;
import android.media.MediaRouterClientState;
-import android.media.RouteDiscoveryRequest;
-import android.media.RouteSessionInfo;
+import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
/**
* {@hide}
@@ -52,8 +52,8 @@
void requestUpdateVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int direction);
void requestCreateSession(IMediaRouter2Client client, in MediaRoute2Info route,
- String routeType, int requestId);
- void setDiscoveryRequest2(IMediaRouter2Client client, in RouteDiscoveryRequest request);
+ String routeFeature, int requestId);
+ void setDiscoveryRequest2(IMediaRouter2Client client, in RouteDiscoveryPreference preference);
void selectRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route);
void deselectRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route);
void transferToRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route);
@@ -70,5 +70,5 @@
void requestUpdateVolume2Manager(IMediaRouter2Manager manager,
in MediaRoute2Info route, int direction);
- List<RouteSessionInfo> getActiveSessions(IMediaRouter2Manager manager);
+ List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager);
}
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 1ed53d9..021e23e 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -36,7 +36,6 @@
/**
* Describes the properties of a route.
- * @hide
*/
public final class MediaRoute2Info implements Parcelable {
@NonNull
@@ -56,7 +55,7 @@
@IntDef({CONNECTION_STATE_DISCONNECTED, CONNECTION_STATE_CONNECTING,
CONNECTION_STATE_CONNECTED})
@Retention(RetentionPolicy.SOURCE)
- private @interface ConnectionState {}
+ public @interface ConnectionState {}
/**
* The default connection state indicating the route is disconnected.
@@ -99,16 +98,15 @@
/** @hide */
@IntDef({
- DEVICE_TYPE_UNKNOWN, DEVICE_TYPE_TV,
- DEVICE_TYPE_SPEAKER, DEVICE_TYPE_BLUETOOTH})
+ DEVICE_TYPE_UNKNOWN, DEVICE_TYPE_REMOTE_TV,
+ DEVICE_TYPE_REMOTE_SPEAKER, DEVICE_TYPE_BLUETOOTH})
@Retention(RetentionPolicy.SOURCE)
- private @interface DeviceType {}
+ public @interface DeviceType {}
/**
* The default receiver device type of the route indicating the type is unknown.
*
* @see #getDeviceType
- * @hide
*/
public static final int DEVICE_TYPE_UNKNOWN = 0;
@@ -118,7 +116,7 @@
*
* @see #getDeviceType
*/
- public static final int DEVICE_TYPE_TV = 1;
+ public static final int DEVICE_TYPE_REMOTE_TV = 1;
/**
* A receiver device type of the route indicating the presentation of the media is happening
@@ -126,14 +124,13 @@
*
* @see #getDeviceType
*/
- public static final int DEVICE_TYPE_SPEAKER = 2;
+ public static final int DEVICE_TYPE_REMOTE_SPEAKER = 2;
/**
* A receiver device type of the route indicating the presentation of the media is happening
* on a bluetooth device such as a bluetooth speaker.
*
* @see #getDeviceType
- * @hide
*/
public static final int DEVICE_TYPE_BLUETOOTH = 3;
@@ -152,7 +149,7 @@
@Nullable
final String mClientPackageName;
@NonNull
- final List<String> mRouteTypes;
+ final List<String> mFeatures;
final int mVolume;
final int mVolumeMax;
final int mVolumeHandling;
@@ -168,7 +165,7 @@
mConnectionState = builder.mConnectionState;
mIconUri = builder.mIconUri;
mClientPackageName = builder.mClientPackageName;
- mRouteTypes = builder.mRouteTypes;
+ mFeatures = builder.mFeatures;
mVolume = builder.mVolume;
mVolumeMax = builder.mVolumeMax;
mVolumeHandling = builder.mVolumeHandling;
@@ -184,7 +181,7 @@
mConnectionState = in.readInt();
mIconUri = in.readParcelable(null);
mClientPackageName = in.readString();
- mRouteTypes = in.createStringArrayList();
+ mFeatures = in.createStringArrayList();
mVolume = in.readInt();
mVolumeMax = in.readInt();
mVolumeHandling = in.readInt();
@@ -223,7 +220,7 @@
&& (mConnectionState == other.mConnectionState)
&& Objects.equals(mIconUri, other.mIconUri)
&& Objects.equals(mClientPackageName, other.mClientPackageName)
- && Objects.equals(mRouteTypes, other.mRouteTypes)
+ && Objects.equals(mFeatures, other.mFeatures)
&& (mVolume == other.mVolume)
&& (mVolumeMax == other.mVolumeMax)
&& (mVolumeHandling == other.mVolumeHandling)
@@ -235,7 +232,7 @@
@Override
public int hashCode() {
return Objects.hash(mId, mName, mDescription, mConnectionState, mIconUri,
- mRouteTypes, mVolume, mVolumeMax, mVolumeHandling, mDeviceType);
+ mFeatures, mVolume, mVolumeMax, mVolumeHandling, mDeviceType);
}
/**
@@ -245,7 +242,7 @@
* In order to ensure uniqueness in {@link MediaRouter2} side, the value of this method
* can be different from what was set in {@link MediaRoute2ProviderService}.
*
- * @see Builder#setId(String)
+ * @see Builder#Builder(String, CharSequence)
*/
@NonNull
public String getId() {
@@ -257,7 +254,7 @@
}
/**
- * Gets the original id set by {@link Builder#setId(String)}.
+ * Gets the original id set by {@link Builder#Builder(String, CharSequence)}.
* @hide
*/
@NonNull
@@ -324,16 +321,16 @@
* Gets the supported categories of the route.
*/
@NonNull
- public List<String> getRouteTypes() {
- return mRouteTypes;
+ public List<String> getFeatures() {
+ return mFeatures;
}
- //TODO: once device types are confirmed, reflect those into the comment.
/**
* Gets the type of the receiver device associated with this route.
*
* @return The type of the receiver device associated with this route:
- * {@link #DEVICE_TYPE_TV} or {@link #DEVICE_TYPE_SPEAKER}.
+ * {@link #DEVICE_TYPE_REMOTE_TV}, {@link #DEVICE_TYPE_REMOTE_SPEAKER},
+ * {@link #DEVICE_TYPE_BLUETOOTH}.
*/
@DeviceType
public int getDeviceType() {
@@ -369,15 +366,15 @@
}
/**
- * Returns if the route contains at least one of the specified route types.
+ * Returns if the route has at least one of the specified route features.
*
- * @param routeTypes the list of route types to consider
- * @return true if the route contains at least one type in the list
+ * @param features the list of route features to consider
+ * @return true if the route has at least one feature in the list
*/
- public boolean containsRouteTypes(@NonNull Collection<String> routeTypes) {
- Objects.requireNonNull(routeTypes, "routeTypes must not be null");
- for (String routeType : routeTypes) {
- if (getRouteTypes().contains(routeType)) {
+ public boolean hasAnyFeatures(@NonNull Collection<String> features) {
+ Objects.requireNonNull(features, "features must not be null");
+ for (String feature : features) {
+ if (getFeatures().contains(feature)) {
return true;
}
}
@@ -398,7 +395,7 @@
dest.writeInt(mConnectionState);
dest.writeParcelable(mIconUri, flags);
dest.writeString(mClientPackageName);
- dest.writeStringList(mRouteTypes);
+ dest.writeStringList(mFeatures);
dest.writeInt(mVolume);
dest.writeInt(mVolumeMax);
dest.writeInt(mVolumeHandling);
@@ -428,15 +425,15 @@
* Builder for {@link MediaRoute2Info media route info}.
*/
public static final class Builder {
- String mId;
+ final String mId;
String mProviderId;
- CharSequence mName;
+ final CharSequence mName;
CharSequence mDescription;
@ConnectionState
int mConnectionState;
Uri mIconUri;
String mClientPackageName;
- List<String> mRouteTypes;
+ List<String> mFeatures;
int mVolume;
int mVolumeMax;
int mVolumeHandling = PLAYBACK_VOLUME_FIXED;
@@ -444,27 +441,43 @@
int mDeviceType = DEVICE_TYPE_UNKNOWN;
Bundle mExtras;
+ /**
+ * Constructor for builder to create {@link MediaRoute2Info}.
+ * <p>
+ * In order to ensure ID uniqueness, the {@link MediaRoute2Info#getId() ID} of a route info
+ * obtained from {@link MediaRouter2} can be different from what was set in
+ * {@link MediaRoute2ProviderService}.
+ * </p>
+ * @param id
+ * @param name
+ */
public Builder(@NonNull String id, @NonNull CharSequence name) {
- setId(id);
- setName(name);
- mRouteTypes = new ArrayList<>();
+ if (TextUtils.isEmpty(id)) {
+ throw new IllegalArgumentException("id must not be empty");
+ }
+ if (TextUtils.isEmpty(name)) {
+ throw new IllegalArgumentException("name must not be empty");
+ }
+ mId = id;
+ mName = name;
+ mFeatures = new ArrayList<>();
}
public Builder(@NonNull MediaRoute2Info routeInfo) {
if (routeInfo == null) {
throw new IllegalArgumentException("route info must not be null");
}
+ mId = routeInfo.mId;
+ mName = routeInfo.mName;
- setId(routeInfo.mId);
if (!TextUtils.isEmpty(routeInfo.mProviderId)) {
setProviderId(routeInfo.mProviderId);
}
- setName(routeInfo.mName);
mDescription = routeInfo.mDescription;
mConnectionState = routeInfo.mConnectionState;
mIconUri = routeInfo.mIconUri;
setClientPackageName(routeInfo.mClientPackageName);
- setRouteTypes(routeInfo.mRouteTypes);
+ mFeatures = new ArrayList<>(routeInfo.mFeatures);
setVolume(routeInfo.mVolume);
setVolumeMax(routeInfo.mVolumeMax);
setVolumeHandling(routeInfo.mVolumeHandling);
@@ -475,26 +488,6 @@
}
/**
- * Sets the unique id of the route. The value given here must be unique for each of your
- * route.
- * <p>
- * In order to ensure uniqueness in {@link MediaRouter2} side, the value of
- * {@link MediaRoute2Info#getId()} can be different from what was set in
- * {@link MediaRoute2ProviderService}.
- * </p>
- *
- * @see MediaRoute2Info#getId()
- */
- @NonNull
- public Builder setId(@NonNull String id) {
- if (TextUtils.isEmpty(id)) {
- throw new IllegalArgumentException("id must not be null or empty");
- }
- mId = id;
- return this;
- }
-
- /**
* Sets the provider id of the route.
* @hide
*/
@@ -508,20 +501,10 @@
}
/**
- * Sets the user-visible name of the route.
- */
- @NonNull
- public Builder setName(@NonNull CharSequence name) {
- Objects.requireNonNull(name, "name must not be null");
- mName = name;
- return this;
- }
-
- /**
* Sets the user-visible description of the route.
*/
@NonNull
- public Builder setDescription(@Nullable String description) {
+ public Builder setDescription(@Nullable CharSequence description) {
mDescription = description;
return this;
}
@@ -569,35 +552,35 @@
}
/**
- * Sets the types of the route.
+ * Clears the features of the route.
*/
@NonNull
- public Builder setRouteTypes(@NonNull Collection<String> routeTypes) {
- mRouteTypes = new ArrayList<>();
- return addRouteTypes(routeTypes);
+ public Builder clearFeatures() {
+ mFeatures = new ArrayList<>();
+ return this;
}
/**
- * Adds types for the route.
+ * Adds features for the route.
*/
@NonNull
- public Builder addRouteTypes(@NonNull Collection<String> routeTypes) {
- Objects.requireNonNull(routeTypes, "routeTypes must not be null");
- for (String routeType: routeTypes) {
- addRouteType(routeType);
+ public Builder addFeatures(@NonNull Collection<String> features) {
+ Objects.requireNonNull(features, "features must not be null");
+ for (String feature : features) {
+ addFeature(feature);
}
return this;
}
/**
- * Add a type for the route.
+ * Adds a feature for the route.
*/
@NonNull
- public Builder addRouteType(@NonNull String routeType) {
- if (TextUtils.isEmpty(routeType)) {
- throw new IllegalArgumentException("routeType must not be null or empty");
+ public Builder addFeature(@NonNull String feature) {
+ if (TextUtils.isEmpty(feature)) {
+ throw new IllegalArgumentException("feature must not be null or empty");
}
- mRouteTypes.add(routeType);
+ mFeatures.add(feature);
return this;
}
@@ -642,7 +625,7 @@
*/
@NonNull
public Builder setExtras(@Nullable Bundle extras) {
- mExtras = extras;
+ mExtras = new Bundle(extras);
return this;
}
diff --git a/media/java/android/media/MediaRoute2ProviderInfo.java b/media/java/android/media/MediaRoute2ProviderInfo.java
index e2f246c..c9a2ec7 100644
--- a/media/java/android/media/MediaRoute2ProviderInfo.java
+++ b/media/java/android/media/MediaRoute2ProviderInfo.java
@@ -93,6 +93,9 @@
/**
* Gets the route for the given route id or null if no matching route exists.
+ * Please note that id should be original id.
+ *
+ * @see MediaRoute2Info#getOriginalId()
*/
@Nullable
public MediaRoute2Info getRoute(@NonNull String routeId) {
@@ -168,7 +171,7 @@
MediaRoute2Info routeWithProviderId = new MediaRoute2Info.Builder(entry.getValue())
.setProviderId(mUniqueId)
.build();
- newRoutes.put(routeWithProviderId.getId(), routeWithProviderId);
+ newRoutes.put(routeWithProviderId.getOriginalId(), routeWithProviderId);
}
mRoutes.clear();
@@ -183,14 +186,14 @@
public Builder addRoute(@NonNull MediaRoute2Info route) {
Objects.requireNonNull(route, "route must not be null");
- if (mRoutes.containsValue(route)) {
- throw new IllegalArgumentException("route descriptor already added");
+ if (mRoutes.containsKey(route.getOriginalId())) {
+ throw new IllegalArgumentException("A route with the same id is already added");
}
if (mUniqueId != null) {
- mRoutes.put(route.getId(),
+ mRoutes.put(route.getOriginalId(),
new MediaRoute2Info.Builder(route).setProviderId(mUniqueId).build());
} else {
- mRoutes.put(route.getId(), route);
+ mRoutes.put(route.getOriginalId(), route);
}
return this;
}
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 24b65ba..1cd5dfa 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -36,12 +36,22 @@
import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
/**
- * @hide
+ * Base class for media route provider services.
+ * <p>
+ * The system media router service will bind to media route provider services when a
+ * {@link RouteDiscoveryPreference discovery preference} is registered via
+ * a {@link MediaRouter2 media router} by an application.
+ * </p><p>
+ * To implement your own media route provider service, extend this class and
+ * override {@link #onDiscoveryPreferenceChanged(RouteDiscoveryPreference)} to publish
+ * {@link MediaRoute2Info routes}.
+ * </p>
*/
public abstract class MediaRoute2ProviderService extends Service {
private static final String TAG = "MR2ProviderService";
@@ -56,13 +66,14 @@
private MediaRoute2ProviderInfo mProviderInfo;
@GuardedBy("mSessionLock")
- private ArrayMap<String, RouteSessionInfo> mSessionInfo = new ArrayMap<>();
+ private ArrayMap<String, RoutingSessionInfo> mSessionInfo = new ArrayMap<>();
public MediaRoute2ProviderService() {
mHandler = new Handler(Looper.getMainLooper());
}
@Override
+ @NonNull
public IBinder onBind(@NonNull Intent intent) {
//TODO: Allow binding from media router service only?
if (SERVICE_INTERFACE.equals(intent.getAction())) {
@@ -79,6 +90,7 @@
*
* @param routeId the id of the target route
* @param request the media control request intent
+ * @hide
*/
//TODO: Discuss what to use for request (e.g., Intent? Request class?)
public abstract void onControlRequest(@NonNull String routeId, @NonNull Intent request);
@@ -88,6 +100,7 @@
*
* @param routeId the id of the route
* @param volume the target volume
+ * @hide
*/
public abstract void onSetVolume(@NonNull String routeId, int volume);
@@ -96,6 +109,7 @@
*
* @param routeId id of the route
* @param delta the delta to add to the current volume
+ * @hide
*/
public abstract void onUpdateVolume(@NonNull String routeId, int delta);
@@ -105,9 +119,10 @@
* @param sessionId id of the session
* @return information of the session with the given id.
* null if the session is destroyed or id is not valid.
+ * @hide
*/
@Nullable
- public final RouteSessionInfo getSessionInfo(@NonNull String sessionId) {
+ public final RoutingSessionInfo getSessionInfo(@NonNull String sessionId) {
if (TextUtils.isEmpty(sessionId)) {
throw new IllegalArgumentException("sessionId must not be empty");
}
@@ -117,10 +132,11 @@
}
/**
- * Gets the list of {@link RouteSessionInfo session info} that the provider service maintains.
+ * Gets the list of {@link RoutingSessionInfo session info} that the provider service maintains.
+ * @hide
*/
@NonNull
- public final List<RouteSessionInfo> getAllSessionInfo() {
+ public final List<RoutingSessionInfo> getAllSessionInfo() {
synchronized (mSessionLock) {
return new ArrayList<>(mSessionInfo.values());
}
@@ -129,20 +145,16 @@
/**
* Updates the information of a session.
* If the session is destroyed or not created before, it will be ignored.
- * A session will be destroyed if it has no selected route.
* Call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify clients of
* session info changes.
*
* @param sessionInfo new session information
- * @see #notifySessionCreated(RouteSessionInfo, long)
+ * @see #notifySessionCreated(RoutingSessionInfo, long)
+ * @hide
*/
- public final void updateSessionInfo(@NonNull RouteSessionInfo sessionInfo) {
+ public final void updateSessionInfo(@NonNull RoutingSessionInfo sessionInfo) {
Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
String sessionId = sessionInfo.getId();
- if (sessionInfo.getSelectedRoutes().isEmpty()) {
- releaseSession(sessionId);
- return;
- }
synchronized (mSessionLock) {
if (mSessionInfo.containsKey(sessionId)) {
@@ -161,7 +173,7 @@
* TODO: This method is temporary, only created for tests. Remove when the alternative is ready.
* @hide
*/
- public final void notifySessionInfoChanged(@NonNull RouteSessionInfo sessionInfo) {
+ public final void notifySessionInfoChanged(@NonNull RoutingSessionInfo sessionInfo) {
Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
String sessionId = sessionInfo.getId();
@@ -189,14 +201,16 @@
* controlled, pass a {@link Bundle} that contains how to control it.
*
* @param sessionInfo information of the new session.
- * The {@link RouteSessionInfo#getId() id} of the session must be
+ * The {@link RoutingSessionInfo#getId() id} of the session must be
* unique. Pass {@code null} to reject the request or inform clients that
* session creation is failed.
* @param requestId id of the previous request to create this session
+ * @hide
*/
// TODO: fail reason?
// TODO: Maybe better to create notifySessionCreationFailed?
- public final void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) {
+ public final void notifySessionCreated(@Nullable RoutingSessionInfo sessionInfo,
+ long requestId) {
if (sessionInfo != null) {
String sessionId = sessionInfo.getId();
synchronized (mSessionLock) {
@@ -224,14 +238,15 @@
* {@link #onDestroySession} is called if the session is released.
*
* @param sessionId id of the session to be released
- * @see #onDestroySession(String, RouteSessionInfo)
+ * @see #onDestroySession(String, RoutingSessionInfo)
+ * @hide
*/
public final void releaseSession(@NonNull String sessionId) {
if (TextUtils.isEmpty(sessionId)) {
throw new IllegalArgumentException("sessionId must not be empty");
}
//TODO: notify media router service of release.
- RouteSessionInfo sessionInfo;
+ RoutingSessionInfo sessionInfo;
synchronized (mSessionLock) {
sessionInfo = mSessionInfo.remove(sessionId);
}
@@ -245,19 +260,20 @@
/**
* Called when a session should be created.
* You should create and maintain your own session and notifies the client of
- * session info. Call {@link #notifySessionCreated(RouteSessionInfo, long)}
+ * session info. Call {@link #notifySessionCreated(RoutingSessionInfo, long)}
* with the given {@code requestId} to notify the information of a new session.
* If you can't create the session or want to reject the request, pass {@code null}
- * as session info in {@link #notifySessionCreated(RouteSessionInfo, long)}
+ * as session info in {@link #notifySessionCreated(RoutingSessionInfo, long)}
* with the given {@code requestId}.
*
* @param packageName the package name of the application that selected the route
* @param routeId the id of the route initially being connected
- * @param routeType the route type of the new session
+ * @param routeFeature the route feature of the new session
* @param requestId the id of this session creation request
+ * @hide
*/
public abstract void onCreateSession(@NonNull String packageName, @NonNull String routeId,
- @NonNull String routeType, long requestId);
+ @NonNull String routeFeature, long requestId);
/**
* Called when a session is about to be destroyed.
@@ -267,71 +283,78 @@
* @param sessionId id of the session being destroyed.
* @param lastSessionInfo information of the session being destroyed.
* @see #releaseSession(String)
+ * @hide
*/
public abstract void onDestroySession(@NonNull String sessionId,
- @NonNull RouteSessionInfo lastSessionInfo);
+ @NonNull RoutingSessionInfo lastSessionInfo);
//TODO: make a way to reject the request
/**
* Called when a client requests selecting a route for the session.
- * After the route is selected, call {@link #updateSessionInfo(RouteSessionInfo)} to update
+ * After the route is selected, call {@link #updateSessionInfo(RoutingSessionInfo)} to update
* session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify
* clients of updated session info.
*
* @param sessionId id of the session
* @param routeId id of the route
- * @see #updateSessionInfo(RouteSessionInfo)
+ * @see #updateSessionInfo(RoutingSessionInfo)
+ * @hide
*/
public abstract void onSelectRoute(@NonNull String sessionId, @NonNull String routeId);
//TODO: make a way to reject the request
/**
* Called when a client requests deselecting a route from the session.
- * After the route is deselected, call {@link #updateSessionInfo(RouteSessionInfo)} to update
+ * After the route is deselected, call {@link #updateSessionInfo(RoutingSessionInfo)} to update
* session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify
* clients of updated session info.
*
* @param sessionId id of the session
* @param routeId id of the route
+ * @hide
*/
public abstract void onDeselectRoute(@NonNull String sessionId, @NonNull String routeId);
//TODO: make a way to reject the request
/**
* Called when a client requests transferring a session to a route.
- * After the transfer is finished, call {@link #updateSessionInfo(RouteSessionInfo)} to update
+ * After the transfer is finished, call {@link #updateSessionInfo(RoutingSessionInfo)} to update
* session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify
* clients of updated session info.
*
* @param sessionId id of the session
* @param routeId id of the route
+ * @hide
*/
public abstract void onTransferToRoute(@NonNull String sessionId, @NonNull String routeId);
/**
- * Called when the {@link RouteDiscoveryRequest discovery request} has changed.
+ * Called when the {@link RouteDiscoveryPreference discovery preference} has changed.
* <p>
* Whenever an application registers a {@link MediaRouter2.RouteCallback callback},
- * it also provides a discovery request to specify types of routes that it is interested in.
- * The media router combines all of these discovery request into a single discovery request
- * and notifies each provider.
+ * it also provides a discovery preference to specify features of routes that it is interested
+ * in. The media router combines all of these discovery request into a single discovery
+ * preference and notifies each provider.
* </p><p>
- * The provider should examine {@link RouteDiscoveryRequest#getRouteTypes() route types}
- * in the discovery request to determine what kind of routes it should try to discover
- * and whether it should perform active or passive scans. In many cases, the provider may be
- * able to save power by not performing any scans when the request doesn't have any matching
- * route types.
+ * The provider should examine {@link RouteDiscoveryPreference#getPreferredFeatures()
+ * preferred features} in the discovery preference to determine what kind of routes it should
+ * try to discover and whether it should perform active or passive scans. In many cases,
+ * the provider may be able to save power by not performing any scans when the request doesn't
+ * have any matching route features.
* </p>
*
- * @param request the new discovery request
+ * @param preference the new discovery preference
*/
- public void onDiscoveryRequestChanged(@NonNull RouteDiscoveryRequest request) {}
+ public void onDiscoveryPreferenceChanged(@NonNull RouteDiscoveryPreference preference) {}
/**
- * Updates provider info and publishes routes and session info.
+ * Updates routes of the provider and notifies the system media router service.
*/
- public final void updateProviderInfo(@NonNull MediaRoute2ProviderInfo providerInfo) {
- mProviderInfo = Objects.requireNonNull(providerInfo, "providerInfo must not be null");
+ public final void notifyRoutes(@NonNull Collection<MediaRoute2Info> routes) {
+ Objects.requireNonNull(routes, "routes must not be null");
+ mProviderInfo = new MediaRoute2ProviderInfo.Builder()
+ .addRoutes(routes)
+ .build();
schedulePublishState();
}
@@ -355,7 +378,7 @@
return;
}
- List<RouteSessionInfo> sessionInfos;
+ List<RoutingSessionInfo> sessionInfos;
synchronized (mSessionLock) {
sessionInfos = new ArrayList<>(mSessionInfo.values());
}
@@ -384,12 +407,12 @@
@Override
public void requestCreateSession(String packageName, String routeId,
- String routeType, long requestId) {
+ String routeFeature, long requestId) {
if (!checkCallerisSystem()) {
return;
}
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onCreateSession,
- MediaRoute2ProviderService.this, packageName, routeId, routeType,
+ MediaRoute2ProviderService.this, packageName, routeId, routeFeature,
requestId));
}
@Override
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 8ebf617..6d37c2d 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -18,10 +18,7 @@
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
import android.annotation.CallbackExecutor;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -37,7 +34,6 @@
import com.android.internal.annotations.GuardedBy;
-import java.lang.annotation.Retention;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -50,50 +46,13 @@
import java.util.stream.Collectors;
/**
- * A new Media Router
- * @hide
+ * Media Router 2 allows applications to control the routing of media channels
+ * and streams from the current device to remote speakers and devices.
*
* TODO: Add method names at the beginning of log messages. (e.g. changeSessionInfoOnHandler)
* Not only MediaRouter2, but also to service / manager / provider.
*/
public class MediaRouter2 {
-
- /** @hide */
- @Retention(SOURCE)
- @IntDef(value = {
- SELECT_REASON_UNKNOWN,
- SELECT_REASON_USER_SELECTED,
- SELECT_REASON_FALLBACK,
- SELECT_REASON_SYSTEM_SELECTED})
- public @interface SelectReason {}
-
- /**
- * Passed to {@link Callback#onRouteSelected(MediaRoute2Info, int, Bundle)} when the reason
- * the route was selected is unknown.
- */
- public static final int SELECT_REASON_UNKNOWN = 0;
-
- /**
- * Passed to {@link Callback#onRouteSelected(MediaRoute2Info, int, Bundle)} when the route
- * is selected in response to a user's request. For example, when a user has selected
- * a different device to play media to.
- */
- public static final int SELECT_REASON_USER_SELECTED = 1;
-
- /**
- * Passed to {@link Callback#onRouteSelected(MediaRoute2Info, int, Bundle)} when the route
- * is selected as a fallback route. For example, when Wi-Fi is disconnected, the device speaker
- * may be selected as a fallback route.
- */
- public static final int SELECT_REASON_FALLBACK = 2;
-
- /**
- * This is passed from {@link com.android.server.media.MediaRouterService} when the route
- * is selected in response to a request from other apps (e.g. System UI).
- * @hide
- */
- public static final int SELECT_REASON_SYSTEM_SELECTED = 3;
-
private static final String TAG = "MR2";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final Object sRouterLock = new Object();
@@ -118,14 +77,14 @@
final Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
@GuardedBy("sLock")
- private RouteDiscoveryRequest mDiscoveryRequest = RouteDiscoveryRequest.EMPTY;
+ private RouteDiscoveryPreference mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
// TODO: Make MediaRouter2 is always connected to the MediaRouterService.
@GuardedBy("sLock")
Client2 mClient;
@GuardedBy("sLock")
- private Map<String, RouteSessionController> mSessionControllers = new ArrayMap<>();
+ private Map<String, RoutingController> mRoutingControllers = new ArrayMap<>();
private AtomicInteger mSessionCreationRequestCnt = new AtomicInteger(1);
@@ -137,6 +96,7 @@
/**
* Gets an instance of the media router associated with the context.
*/
+ @NonNull
public static MediaRouter2 getInstance(@NonNull Context context) {
Objects.requireNonNull(context, "context must not be null");
synchronized (sRouterLock) {
@@ -193,12 +153,12 @@
*/
public void registerRouteCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull RouteCallback routeCallback,
- @NonNull RouteDiscoveryRequest request) {
+ @NonNull RouteDiscoveryPreference preference) {
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(routeCallback, "callback must not be null");
- Objects.requireNonNull(request, "request must not be null");
+ Objects.requireNonNull(preference, "preference must not be null");
- RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, request);
+ RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, preference);
if (!mRouteCallbackRecords.addIfAbsent(record)) {
Log.w(TAG, "Ignoring the same callback");
return;
@@ -210,15 +170,13 @@
try {
mMediaRouterService.registerClient2(client, mPackageName);
updateDiscoveryRequestLocked();
- mMediaRouterService.setDiscoveryRequest2(client, mDiscoveryRequest);
+ mMediaRouterService.setDiscoveryRequest2(client, mDiscoveryPreference);
mClient = client;
} catch (RemoteException ex) {
Log.e(TAG, "Unable to register media router.", ex);
}
}
}
-
- //TODO: Update discovery request here.
}
/**
@@ -251,8 +209,8 @@
}
private void updateDiscoveryRequestLocked() {
- mDiscoveryRequest = new RouteDiscoveryRequest.Builder(
- mRouteCallbackRecords.stream().map(record -> record.mRequest).collect(
+ mDiscoveryPreference = new RouteDiscoveryPreference.Builder(
+ mRouteCallbackRecords.stream().map(record -> record.mPreference).collect(
Collectors.toList())).build();
}
@@ -261,8 +219,8 @@
* known to the media router.
* Please note that the list can be changed before callbacks are invoked.
*
- * @return the list of routes that contains at least one of the route types in discovery
- * requests registered by the application
+ * @return the list of routes that contains at least one of the route features in discovery
+ * preferences registered by the application
*/
@NonNull
public List<MediaRoute2Info> getRoutes() {
@@ -272,7 +230,7 @@
List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
for (MediaRoute2Info route : mRoutes.values()) {
- if (route.containsRouteTypes(mDiscoveryRequest.getRouteTypes())) {
+ if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
filteredRoutes.add(route);
}
}
@@ -283,12 +241,13 @@
}
/**
- * Registers a callback to get updates on creations and changes of route sessions.
+ * Registers a callback to get updates on creations and changes of routing sessions.
* If you register the same callback twice or more, it will be ignored.
*
* @param executor the executor to execute the callback on
* @param callback the callback to register
* @see #unregisterSessionCallback
+ * @hide
*/
@NonNull
public void registerSessionCallback(@CallbackExecutor Executor executor,
@@ -309,6 +268,7 @@
*
* @param callback the callback to unregister
* @see #registerSessionCallback
+ * @hide
*/
@NonNull
public void unregisterSessionCallback(@NonNull SessionCallback callback) {
@@ -324,25 +284,26 @@
* Requests the media route provider service to create a session with the given route.
*
* @param route the route you want to create a session with.
- * @param routeType the route type of the session. Should not be empty
+ * @param routeFeature the route feature of the session. Should not be empty.
*
* @see SessionCallback#onSessionCreated
* @see SessionCallback#onSessionCreationFailed
+ * @hide
*/
@NonNull
public void requestCreateSession(@NonNull MediaRoute2Info route,
- @NonNull String routeType) {
+ @NonNull String routeFeature) {
Objects.requireNonNull(route, "route must not be null");
- if (TextUtils.isEmpty(routeType)) {
- throw new IllegalArgumentException("routeType must not be empty");
+ if (TextUtils.isEmpty(routeFeature)) {
+ throw new IllegalArgumentException("routeFeature must not be empty");
}
// TODO: Check the given route exists
- // TODO: Check the route supports the given routeType
+ // TODO: Check the route supports the given routeFeature
final int requestId;
requestId = mSessionCreationRequestCnt.getAndIncrement();
- SessionCreationRequest request = new SessionCreationRequest(requestId, route, routeType);
+ SessionCreationRequest request = new SessionCreationRequest(requestId, route, routeFeature);
mSessionCreationRequests.add(request);
Client2 client;
@@ -351,7 +312,7 @@
}
if (client != null) {
try {
- mMediaRouterService.requestCreateSession(client, route, routeType, requestId);
+ mMediaRouterService.requestCreateSession(client, route, routeFeature, requestId);
} catch (RemoteException ex) {
Log.e(TAG, "Unable to request to create session.", ex);
mHandler.sendMessage(obtainMessage(MediaRouter2::createControllerOnHandler,
@@ -365,6 +326,7 @@
*
* @param route the route that will receive the control request
* @param request the media control request
+ * @hide
*/
//TODO: Discuss what to use for request (e.g., Intent? Request class?)
//TODO: Provide a way to obtain the result
@@ -392,6 +354,7 @@
* </p>
*
* @param volume The new volume value between 0 and {@link MediaRoute2Info#getVolumeMax}.
+ * @hide
*/
public void requestSetVolume(@NonNull MediaRoute2Info route, int volume) {
Objects.requireNonNull(route, "route must not be null");
@@ -416,6 +379,7 @@
* </p>
*
* @param delta The delta to add to the current volume.
+ * @hide
*/
public void requestUpdateVolume(@NonNull MediaRoute2Info route, int delta) {
Objects.requireNonNull(route, "route must not be null");
@@ -442,7 +406,7 @@
synchronized (sRouterLock) {
for (MediaRoute2Info route : routes) {
mRoutes.put(route.getId(), route);
- if (route.containsRouteTypes(mDiscoveryRequest.getRouteTypes())) {
+ if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
addedRoutes.add(route);
}
}
@@ -458,7 +422,7 @@
synchronized (sRouterLock) {
for (MediaRoute2Info route : routes) {
mRoutes.remove(route.getId());
- if (route.containsRouteTypes(mDiscoveryRequest.getRouteTypes())) {
+ if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
removedRoutes.add(route);
}
}
@@ -474,7 +438,7 @@
synchronized (sRouterLock) {
for (MediaRoute2Info route : routes) {
mRoutes.put(route.getId(), route);
- if (route.containsRouteTypes(mDiscoveryRequest.getRouteTypes())) {
+ if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
changedRoutes.add(route);
}
}
@@ -491,7 +455,7 @@
* <p>
* Pass {@code null} to sessionInfo for the failure case.
*/
- void createControllerOnHandler(@Nullable RouteSessionInfo sessionInfo, int requestId) {
+ void createControllerOnHandler(@Nullable RoutingSessionInfo sessionInfo, int requestId) {
SessionCreationRequest matchingRequest = null;
for (SessionCreationRequest request : mSessionCreationRequests) {
if (request.mRequestId == requestId) {
@@ -504,27 +468,27 @@
mSessionCreationRequests.remove(matchingRequest);
MediaRoute2Info requestedRoute = matchingRequest.mRoute;
- String requestedRouteType = matchingRequest.mRouteType;
+ String requestedRouteFeature = matchingRequest.mRouteFeature;
if (sessionInfo == null) {
// TODO: We may need to distinguish between failure and rejection.
// One way can be introducing 'reason'.
- notifySessionCreationFailed(requestedRoute, requestedRouteType);
+ notifySessionCreationFailed(requestedRoute, requestedRouteFeature);
return;
- } else if (!TextUtils.equals(requestedRouteType,
- sessionInfo.getRouteType())) {
- Log.w(TAG, "The session has different route type from what we requested. "
- + "(requested=" + requestedRouteType
- + ", actual=" + sessionInfo.getRouteType()
+ } else if (!TextUtils.equals(requestedRouteFeature,
+ sessionInfo.getRouteFeature())) {
+ Log.w(TAG, "The session has different route feature from what we requested. "
+ + "(requested=" + requestedRouteFeature
+ + ", actual=" + sessionInfo.getRouteFeature()
+ ")");
- notifySessionCreationFailed(requestedRoute, requestedRouteType);
+ notifySessionCreationFailed(requestedRoute, requestedRouteFeature);
return;
} else if (!sessionInfo.getSelectedRoutes().contains(requestedRoute.getId())) {
Log.w(TAG, "The session does not contain the requested route. "
+ "(requestedRouteId=" + requestedRoute.getId()
+ ", actualRoutes=" + sessionInfo.getSelectedRoutes()
+ ")");
- notifySessionCreationFailed(requestedRoute, requestedRouteType);
+ notifySessionCreationFailed(requestedRoute, requestedRouteFeature);
return;
} else if (!TextUtils.equals(requestedRoute.getProviderId(),
sessionInfo.getProviderId())) {
@@ -532,29 +496,29 @@
+ "(requested route's providerId=" + requestedRoute.getProviderId()
+ ", actual providerId=" + sessionInfo.getProviderId()
+ ")");
- notifySessionCreationFailed(requestedRoute, requestedRouteType);
+ notifySessionCreationFailed(requestedRoute, requestedRouteFeature);
return;
}
}
if (sessionInfo != null) {
- RouteSessionController controller = new RouteSessionController(sessionInfo);
+ RoutingController controller = new RoutingController(sessionInfo);
synchronized (sRouterLock) {
- mSessionControllers.put(controller.getSessionId(), controller);
+ mRoutingControllers.put(controller.getSessionId(), controller);
}
notifySessionCreated(controller);
}
}
- void changeSessionInfoOnHandler(RouteSessionInfo sessionInfo) {
+ void changeSessionInfoOnHandler(RoutingSessionInfo sessionInfo) {
if (sessionInfo == null) {
Log.w(TAG, "changeSessionInfoOnHandler: Ignoring null sessionInfo.");
return;
}
- RouteSessionController matchingController;
+ RoutingController matchingController;
synchronized (sRouterLock) {
- matchingController = mSessionControllers.get(sessionInfo.getId());
+ matchingController = mRoutingControllers.get(sessionInfo.getId());
}
if (matchingController == null) {
@@ -563,27 +527,27 @@
return;
}
- RouteSessionInfo oldInfo = matchingController.getRouteSessionInfo();
+ RoutingSessionInfo oldInfo = matchingController.getRoutingSessionInfo();
if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) {
Log.w(TAG, "changeSessionInfoOnHandler: Provider IDs are not matched. old="
+ oldInfo.getProviderId() + ", new=" + sessionInfo.getProviderId());
return;
}
- matchingController.setRouteSessionInfo(sessionInfo);
+ matchingController.setRoutingSessionInfo(sessionInfo);
notifySessionInfoChanged(matchingController, oldInfo, sessionInfo);
}
- void releaseControllerOnHandler(RouteSessionInfo sessionInfo) {
+ void releaseControllerOnHandler(RoutingSessionInfo sessionInfo) {
if (sessionInfo == null) {
Log.w(TAG, "releaseControllerOnHandler: Ignoring null sessionInfo.");
return;
}
final String uniqueSessionId = sessionInfo.getId();
- RouteSessionController matchingController;
+ RoutingController matchingController;
synchronized (sRouterLock) {
- matchingController = mSessionControllers.get(uniqueSessionId);
+ matchingController = mRoutingControllers.get(uniqueSessionId);
}
if (matchingController == null) {
@@ -594,7 +558,7 @@
return;
}
- RouteSessionInfo oldInfo = matchingController.getRouteSessionInfo();
+ RoutingSessionInfo oldInfo = matchingController.getRoutingSessionInfo();
if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) {
Log.w(TAG, "releaseControllerOnHandler: Provider IDs are not matched. old="
+ oldInfo.getProviderId() + ", new=" + sessionInfo.getProviderId());
@@ -602,23 +566,23 @@
}
synchronized (sRouterLock) {
- mSessionControllers.remove(uniqueSessionId, matchingController);
+ mRoutingControllers.remove(uniqueSessionId, matchingController);
}
matchingController.release();
notifyControllerReleased(matchingController);
}
private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes,
- RouteDiscoveryRequest discoveryRequest) {
+ RouteDiscoveryPreference discoveryRequest) {
return routes.stream()
.filter(
- route -> route.containsRouteTypes(discoveryRequest.getRouteTypes()))
+ route -> route.hasAnyFeatures(discoveryRequest.getPreferredFeatures()))
.collect(Collectors.toList());
}
private void notifyRoutesAdded(List<MediaRoute2Info> routes) {
for (RouteCallbackRecord record: mRouteCallbackRecords) {
- List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mRequest);
+ List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference);
if (!filteredRoutes.isEmpty()) {
record.mExecutor.execute(
() -> record.mRouteCallback.onRoutesAdded(filteredRoutes));
@@ -628,7 +592,7 @@
private void notifyRoutesRemoved(List<MediaRoute2Info> routes) {
for (RouteCallbackRecord record: mRouteCallbackRecords) {
- List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mRequest);
+ List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference);
if (!filteredRoutes.isEmpty()) {
record.mExecutor.execute(
() -> record.mRouteCallback.onRoutesRemoved(filteredRoutes));
@@ -638,7 +602,7 @@
private void notifyRoutesChanged(List<MediaRoute2Info> routes) {
for (RouteCallbackRecord record: mRouteCallbackRecords) {
- List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mRequest);
+ List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference);
if (!filteredRoutes.isEmpty()) {
record.mExecutor.execute(
() -> record.mRouteCallback.onRoutesChanged(filteredRoutes));
@@ -646,22 +610,22 @@
}
}
- private void notifySessionCreated(RouteSessionController controller) {
+ private void notifySessionCreated(RoutingController controller) {
for (SessionCallbackRecord record: mSessionCallbackRecords) {
record.mExecutor.execute(
() -> record.mSessionCallback.onSessionCreated(controller));
}
}
- private void notifySessionCreationFailed(MediaRoute2Info route, String routeType) {
+ private void notifySessionCreationFailed(MediaRoute2Info route, String routeFeature) {
for (SessionCallbackRecord record: mSessionCallbackRecords) {
record.mExecutor.execute(
- () -> record.mSessionCallback.onSessionCreationFailed(route, routeType));
+ () -> record.mSessionCallback.onSessionCreationFailed(route, routeFeature));
}
}
- private void notifySessionInfoChanged(RouteSessionController controller,
- RouteSessionInfo oldInfo, RouteSessionInfo newInfo) {
+ private void notifySessionInfoChanged(RoutingController controller,
+ RoutingSessionInfo oldInfo, RoutingSessionInfo newInfo) {
for (SessionCallbackRecord record: mSessionCallbackRecords) {
record.mExecutor.execute(
() -> record.mSessionCallback.onSessionInfoChanged(
@@ -669,7 +633,7 @@
}
}
- private void notifyControllerReleased(RouteSessionController controller) {
+ private void notifyControllerReleased(RoutingController controller) {
for (SessionCallbackRecord record: mSessionCallbackRecords) {
record.mExecutor.execute(
() -> record.mSessionCallback.onSessionReleased(controller));
@@ -710,23 +674,24 @@
/**
* Callback for receiving a result of session creation and session updates.
+ * @hide
*/
public static class SessionCallback {
/**
- * Called when the route session is created by the route provider.
+ * Called when the routing session is created by the route provider.
*
* @param controller the controller to control the created session
*/
- public void onSessionCreated(@NonNull RouteSessionController controller) {}
+ public void onSessionCreated(@NonNull RoutingController controller) {}
/**
* Called when the session creation request failed.
*
* @param requestedRoute the route info which was used for the request
- * @param requestedRouteType the route type which was used for the request
+ * @param requestedRouteFeature the route feature which was used for the request
*/
public void onSessionCreationFailed(@NonNull MediaRoute2Info requestedRoute,
- @NonNull String requestedRouteType) {}
+ @NonNull String requestedRouteFeature) {}
/**
* Called when the session info has changed.
@@ -737,44 +702,45 @@
* TODO: (Discussion) Do we really need newInfo? The controller has the newInfo.
* However. there can be timing issue if there is no newInfo.
*/
- public void onSessionInfoChanged(@NonNull RouteSessionController controller,
- @NonNull RouteSessionInfo oldInfo,
- @NonNull RouteSessionInfo newInfo) {}
+ public void onSessionInfoChanged(@NonNull RoutingController controller,
+ @NonNull RoutingSessionInfo oldInfo,
+ @NonNull RoutingSessionInfo newInfo) {}
/**
* Called when the session is released by {@link MediaRoute2ProviderService}.
* Before this method is called, the controller would be released by the system,
- * which means the {@link RouteSessionController#isReleased()} will always return true
+ * which means the {@link RoutingController#isReleased()} will always return true
* for the {@code controller} here.
* <p>
- * Note: Calling {@link RouteSessionController#release()} will <em>NOT</em> trigger
+ * Note: Calling {@link RoutingController#release()} will <em>NOT</em> trigger
* this method to be called.
*
* TODO: Add tests for checking whether this method is called.
* TODO: When service process dies, this should be called.
*
- * @see RouteSessionController#isReleased()
+ * @see RoutingController#isReleased()
*/
- public void onSessionReleased(@NonNull RouteSessionController controller) {}
+ public void onSessionReleased(@NonNull RoutingController controller) {}
}
/**
- * A class to control media route session in media route provider.
+ * A class to control media routing session in media route provider.
* For example, selecting/deselcting/transferring routes to session can be done through this
* class. Instances are created by {@link MediaRouter2}.
*
* TODO: Need to add toString()
+ * @hide
*/
- public final class RouteSessionController {
+ public final class RoutingController {
private final Object mControllerLock = new Object();
@GuardedBy("mLock")
- private RouteSessionInfo mSessionInfo;
+ private RoutingSessionInfo mSessionInfo;
@GuardedBy("mLock")
private volatile boolean mIsReleased;
- RouteSessionController(@NonNull RouteSessionInfo sessionInfo) {
+ RoutingController(@NonNull RoutingSessionInfo sessionInfo) {
mSessionInfo = sessionInfo;
}
@@ -788,17 +754,17 @@
}
/**
- * @return the type of routes that the session includes.
+ * @return the feature which is used by the session mainly.
*/
@NonNull
- public String getRouteType() {
+ public String getRouteFeature() {
synchronized (mControllerLock) {
- return mSessionInfo.getRouteType();
+ return mSessionInfo.getRouteFeature();
}
}
/**
- * @return the control hints used to control route session if available.
+ * @return the control hints used to control routing session if available.
*/
@Nullable
public Bundle getControlHints() {
@@ -1020,7 +986,7 @@
Client2 client;
synchronized (sRouterLock) {
- mSessionControllers.remove(getSessionId(), this);
+ mRoutingControllers.remove(getSessionId(), this);
client = mClient;
}
if (client != null) {
@@ -1037,13 +1003,13 @@
* @hide
*/
@NonNull
- public RouteSessionInfo getRouteSessionInfo() {
+ public RoutingSessionInfo getRoutingSessionInfo() {
synchronized (mControllerLock) {
return mSessionInfo;
}
}
- void setRouteSessionInfo(@NonNull RouteSessionInfo info) {
+ void setRoutingSessionInfo(@NonNull RoutingSessionInfo info) {
synchronized (mControllerLock) {
mSessionInfo = info;
}
@@ -1070,13 +1036,13 @@
final class RouteCallbackRecord {
public final Executor mExecutor;
public final RouteCallback mRouteCallback;
- public final RouteDiscoveryRequest mRequest;
+ public final RouteDiscoveryPreference mPreference;
RouteCallbackRecord(@Nullable Executor executor, @NonNull RouteCallback routeCallback,
- @Nullable RouteDiscoveryRequest request) {
+ @Nullable RouteDiscoveryPreference preference) {
mRouteCallback = routeCallback;
mExecutor = executor;
- mRequest = request;
+ mPreference = preference;
}
@Override
@@ -1125,13 +1091,13 @@
final class SessionCreationRequest {
public final MediaRoute2Info mRoute;
- public final String mRouteType;
+ public final String mRouteFeature;
public final int mRequestId;
SessionCreationRequest(int requestId, @NonNull MediaRoute2Info route,
- @NonNull String routeType) {
+ @NonNull String routeFeature) {
mRoute = route;
- mRouteType = routeType;
+ mRouteFeature = routeFeature;
mRequestId = requestId;
}
}
@@ -1159,19 +1125,19 @@
}
@Override
- public void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, int requestId) {
+ public void notifySessionCreated(@Nullable RoutingSessionInfo sessionInfo, int requestId) {
mHandler.sendMessage(obtainMessage(MediaRouter2::createControllerOnHandler,
MediaRouter2.this, sessionInfo, requestId));
}
@Override
- public void notifySessionInfoChanged(@Nullable RouteSessionInfo sessionInfo) {
+ public void notifySessionInfoChanged(@Nullable RoutingSessionInfo sessionInfo) {
mHandler.sendMessage(obtainMessage(MediaRouter2::changeSessionInfoOnHandler,
MediaRouter2.this, sessionInfo));
}
@Override
- public void notifySessionReleased(RouteSessionInfo sessionInfo) {
+ public void notifySessionReleased(RoutingSessionInfo sessionInfo) {
mHandler.sendMessage(obtainMessage(MediaRouter2::releaseControllerOnHandler,
MediaRouter2.this, sessionInfo));
}
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 1e6ec51..7022933 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -65,7 +65,7 @@
@GuardedBy("mRoutesLock")
private final Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
@NonNull
- final ConcurrentMap<String, List<String>> mRouteTypeMap = new ConcurrentHashMap<>();
+ final ConcurrentMap<String, List<String>> mPreferredFeaturesMap = new ConcurrentHashMap<>();
private AtomicInteger mNextRequestId = new AtomicInteger(1);
@@ -144,7 +144,7 @@
}
//TODO: clear mRoutes?
mClient = null;
- mRouteTypeMap.clear();
+ mPreferredFeaturesMap.clear();
}
}
}
@@ -160,14 +160,14 @@
public List<MediaRoute2Info> getAvailableRoutes(@NonNull String packageName) {
Objects.requireNonNull(packageName, "packageName must not be null");
- List<String> routeTypes = mRouteTypeMap.get(packageName);
- if (routeTypes == null) {
+ List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
+ if (preferredFeatures == null) {
return Collections.emptyList();
}
List<MediaRoute2Info> routes = new ArrayList<>();
synchronized (mRoutesLock) {
for (MediaRoute2Info route : mRoutes.values()) {
- if (route.containsRouteTypes(routeTypes)) {
+ if (route.hasAnyFeatures(preferredFeatures)) {
routes.add(route);
}
}
@@ -176,7 +176,7 @@
}
@NonNull
- public List<RouteSessionInfo> getActiveSessions() {
+ public List<RoutingSessionInfo> getActiveSessions() {
Client client;
synchronized (sLock) {
client = mClient;
@@ -352,15 +352,15 @@
}
}
- void updateRouteTypes(String packageName, List<String> routeTypes) {
- List<String> prevTypes = mRouteTypeMap.put(packageName, routeTypes);
- if ((prevTypes == null && routeTypes.size() == 0)
- || Objects.equals(routeTypes, prevTypes)) {
+ void updatePreferredFeatures(String packageName, List<String> preferredFeatures) {
+ List<String> prevFeatures = mPreferredFeaturesMap.put(packageName, preferredFeatures);
+ if ((prevFeatures == null && preferredFeatures.size() == 0)
+ || Objects.equals(preferredFeatures, prevFeatures)) {
return;
}
for (CallbackRecord record : mCallbackRecords) {
- record.mExecutor.execute(
- () -> record.mCallback.onControlCategoriesChanged(packageName, routeTypes));
+ record.mExecutor.execute(() -> record.mCallback
+ .onControlCategoriesChanged(packageName, preferredFeatures));
}
}
@@ -398,13 +398,13 @@
/**
- * Called when the route types of an app is changed.
+ * Called when the preferred route features of an app is changed.
*
* @param packageName the package name of the application
- * @param routeTypes the list of route types set by an application.
+ * @param preferredFeatures the list of preferred route features set by an application.
*/
public void onControlCategoriesChanged(@NonNull String packageName,
- @NonNull List<String> routeTypes) {}
+ @NonNull List<String> preferredFeatures) {}
}
final class CallbackRecord {
@@ -440,9 +440,9 @@
MediaRouter2Manager.this, packageName, route));
}
- public void notifyRouteTypesChanged(String packageName, List<String> routeTypes) {
- mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updateRouteTypes,
- MediaRouter2Manager.this, packageName, routeTypes));
+ public void notifyPreferredFeaturesChanged(String packageName, List<String> features) {
+ mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updatePreferredFeatures,
+ MediaRouter2Manager.this, packageName, features));
}
@Override
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/media/java/android/media/RouteDiscoveryPreference.aidl
similarity index 94%
copy from media/java/android/media/RouteSessionInfo.aidl
copy to media/java/android/media/RouteDiscoveryPreference.aidl
index fb5d836..898eb39 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/media/java/android/media/RouteDiscoveryPreference.aidl
@@ -16,4 +16,4 @@
package android.media;
-parcelable RouteSessionInfo;
+parcelable RouteDiscoveryPreference;
diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java
new file mode 100644
index 0000000..7ec1123
--- /dev/null
+++ b/media/java/android/media/RouteDiscoveryPreference.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A media route discovery preference describing the kinds of routes that media router
+ * would like to discover and whether to perform active scanning.
+ *
+ * @see MediaRouter2#registerRouteCallback
+ */
+public final class RouteDiscoveryPreference implements Parcelable {
+ @NonNull
+ public static final Creator<RouteDiscoveryPreference> CREATOR =
+ new Creator<RouteDiscoveryPreference>() {
+ @Override
+ public RouteDiscoveryPreference createFromParcel(Parcel in) {
+ return new RouteDiscoveryPreference(in);
+ }
+
+ @Override
+ public RouteDiscoveryPreference[] newArray(int size) {
+ return new RouteDiscoveryPreference[size];
+ }
+ };
+
+ @NonNull
+ private final List<String> mPreferredFeatures;
+ private final boolean mActiveScan;
+ @Nullable
+ private final Bundle mExtras;
+
+ /**
+ * @hide
+ */
+ public static final RouteDiscoveryPreference EMPTY =
+ new Builder(Collections.emptyList(), false).build();
+
+ RouteDiscoveryPreference(@NonNull Builder builder) {
+ mPreferredFeatures = builder.mPreferredFeatures;
+ mActiveScan = builder.mActiveScan;
+ mExtras = builder.mExtras;
+ }
+
+ RouteDiscoveryPreference(@NonNull Parcel in) {
+ mPreferredFeatures = in.createStringArrayList();
+ mActiveScan = in.readBoolean();
+ mExtras = in.readBundle();
+ }
+
+ @NonNull
+ public List<String> getPreferredFeatures() {
+ return mPreferredFeatures;
+ }
+
+ public boolean isActiveScan() {
+ return mActiveScan;
+ }
+
+ /**
+ * @hide
+ */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeStringList(mPreferredFeatures);
+ dest.writeBoolean(mActiveScan);
+ dest.writeBundle(mExtras);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder()
+ .append("RouteDiscoveryRequest{ ")
+ .append("preferredFeatures={")
+ .append(String.join(", ", mPreferredFeatures))
+ .append("}")
+ .append(", activeScan=")
+ .append(mActiveScan)
+ .append(" }");
+
+ return result.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof RouteDiscoveryPreference)) {
+ return false;
+ }
+ RouteDiscoveryPreference other = (RouteDiscoveryPreference) o;
+ return Objects.equals(mPreferredFeatures, other.mPreferredFeatures)
+ && mActiveScan == other.mActiveScan;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPreferredFeatures, mActiveScan);
+ }
+
+ /**
+ * Builder for {@link RouteDiscoveryPreference}.
+ */
+ public static final class Builder {
+ List<String> mPreferredFeatures;
+ boolean mActiveScan;
+ Bundle mExtras;
+
+ public Builder(@NonNull List<String> preferredFeatures, boolean activeScan) {
+ mPreferredFeatures = new ArrayList<>(Objects.requireNonNull(preferredFeatures,
+ "preferredFeatures must not be null"));
+ mActiveScan = activeScan;
+ }
+
+ public Builder(@NonNull RouteDiscoveryPreference preference) {
+ Objects.requireNonNull(preference, "preference must not be null");
+
+ mPreferredFeatures = preference.getPreferredFeatures();
+ mActiveScan = preference.isActiveScan();
+ mExtras = preference.getExtras();
+ }
+
+ /**
+ * A constructor to combine all of the preferences into a single preference .
+ * It ignores extras of preferences.
+ *
+ * @hide
+ */
+ public Builder(@NonNull Collection<RouteDiscoveryPreference> preferences) {
+ Objects.requireNonNull(preferences, "preferences must not be null");
+
+ Set<String> routeFeatureSet = new HashSet<>();
+ mActiveScan = false;
+ for (RouteDiscoveryPreference preference : preferences) {
+ routeFeatureSet.addAll(preference.mPreferredFeatures);
+ mActiveScan |= preference.mActiveScan;
+ }
+ mPreferredFeatures = new ArrayList<>(routeFeatureSet);
+ }
+
+ /**
+ * Sets preferred route features to discover.
+ */
+ @NonNull
+ public Builder setPreferredFeatures(@NonNull List<String> preferredFeatures) {
+ mPreferredFeatures = new ArrayList<>(Objects.requireNonNull(preferredFeatures,
+ "preferredFeatures must not be null"));
+ return this;
+ }
+
+ /**
+ * Sets if active scanning should be performed.
+ */
+ @NonNull
+ public Builder setActiveScan(boolean activeScan) {
+ mActiveScan = activeScan;
+ return this;
+ }
+
+ /**
+ * Sets the extras of the route.
+ * @hide
+ */
+ @NonNull
+ public Builder setExtras(@Nullable Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds the {@link RouteDiscoveryPreference}.
+ */
+ @NonNull
+ public RouteDiscoveryPreference build() {
+ return new RouteDiscoveryPreference(this);
+ }
+ }
+}
diff --git a/media/java/android/media/RouteDiscoveryRequest.aidl b/media/java/android/media/RouteDiscoveryRequest.aidl
deleted file mode 100644
index 744f656..0000000
--- a/media/java/android/media/RouteDiscoveryRequest.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media;
-
-parcelable RouteDiscoveryRequest;
diff --git a/media/java/android/media/RouteDiscoveryRequest.java b/media/java/android/media/RouteDiscoveryRequest.java
deleted file mode 100644
index 88b31fb..0000000
--- a/media/java/android/media/RouteDiscoveryRequest.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-
-
-/**
- * @hide
- */
-public final class RouteDiscoveryRequest implements Parcelable {
- @NonNull
- public static final Creator<RouteDiscoveryRequest> CREATOR =
- new Creator<RouteDiscoveryRequest>() {
- @Override
- public RouteDiscoveryRequest createFromParcel(Parcel in) {
- return new RouteDiscoveryRequest(in);
- }
-
- @Override
- public RouteDiscoveryRequest[] newArray(int size) {
- return new RouteDiscoveryRequest[size];
- }
- };
-
- @NonNull
- private final List<String> mRouteTypes;
- private final boolean mActiveScan;
- @Nullable
- private final Bundle mExtras;
-
- /**
- * @hide
- */
- public static final RouteDiscoveryRequest EMPTY =
- new Builder(Collections.emptyList(), false).build();
-
- RouteDiscoveryRequest(@NonNull Builder builder) {
- mRouteTypes = builder.mRouteTypes;
- mActiveScan = builder.mActiveScan;
- mExtras = builder.mExtras;
- }
-
- RouteDiscoveryRequest(@NonNull Parcel in) {
- mRouteTypes = in.createStringArrayList();
- mActiveScan = in.readBoolean();
- mExtras = in.readBundle();
- }
-
- @NonNull
- public List<String> getRouteTypes() {
- return mRouteTypes;
- }
-
- public boolean isActiveScan() {
- return mActiveScan;
- }
-
- /**
- * @hide
- */
- public Bundle getExtras() {
- return mExtras;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeStringList(mRouteTypes);
- dest.writeBoolean(mActiveScan);
- dest.writeBundle(mExtras);
- }
-
- @Override
- public String toString() {
- StringBuilder result = new StringBuilder()
- .append("RouteDiscoveryRequest{ ")
- .append("routeTypes={")
- .append(String.join(", ", mRouteTypes))
- .append("}")
- .append(", activeScan=")
- .append(mActiveScan)
- .append(" }");
-
- return result.toString();
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof RouteDiscoveryRequest)) {
- return false;
- }
- RouteDiscoveryRequest other = (RouteDiscoveryRequest) o;
- return Objects.equals(mRouteTypes, other.mRouteTypes)
- && mActiveScan == other.mActiveScan;
- }
-
- /**
- * Builder for {@link RouteDiscoveryRequest}.
- */
- public static final class Builder {
- List<String> mRouteTypes;
- boolean mActiveScan;
- Bundle mExtras;
-
- public Builder(@NonNull List<String> routeTypes, boolean activeScan) {
- mRouteTypes = new ArrayList<>(
- Objects.requireNonNull(routeTypes, "routeTypes must not be null"));
- mActiveScan = activeScan;
- }
-
- public Builder(@NonNull RouteDiscoveryRequest request) {
- Objects.requireNonNull(request, "request must not be null");
-
- mRouteTypes = request.getRouteTypes();
- mActiveScan = request.isActiveScan();
- mExtras = request.getExtras();
- }
-
- /**
- * A constructor to combine all of the requests into a single request.
- * It ignores extras of requests.
- */
- Builder(@NonNull Collection<RouteDiscoveryRequest> requests) {
- Set<String> routeTypeSet = new HashSet<>();
- mActiveScan = false;
- for (RouteDiscoveryRequest request : requests) {
- routeTypeSet.addAll(request.mRouteTypes);
- mActiveScan |= request.mActiveScan;
- }
- mRouteTypes = new ArrayList<>(routeTypeSet);
- }
-
- /**
- * Sets route types to discover.
- */
- public Builder setRouteTypes(@NonNull List<String> routeTypes) {
- mRouteTypes = new ArrayList<>(
- Objects.requireNonNull(routeTypes, "routeTypes must not be null"));
- return this;
- }
-
- /**
- * Sets if active scanning should be performed.
- */
- public Builder setActiveScan(boolean activeScan) {
- mActiveScan = activeScan;
- return this;
- }
-
- /**
- * Sets the extras of the route.
- * @hide
- */
- public Builder setExtras(@Nullable Bundle extras) {
- mExtras = extras;
- return this;
- }
-
- /**
- * Builds the {@link RouteDiscoveryRequest}.
- */
- public RouteDiscoveryRequest build() {
- return new RouteDiscoveryRequest(this);
- }
- }
-}
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/media/java/android/media/RoutingSessionInfo.aidl
similarity index 95%
rename from media/java/android/media/RouteSessionInfo.aidl
rename to media/java/android/media/RoutingSessionInfo.aidl
index fb5d836..7b8e3d9 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/media/java/android/media/RoutingSessionInfo.aidl
@@ -16,4 +16,4 @@
package android.media;
-parcelable RouteSessionInfo;
+parcelable RoutingSessionInfo;
diff --git a/media/java/android/media/RouteSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
similarity index 65%
rename from media/java/android/media/RouteSessionInfo.java
rename to media/java/android/media/RoutingSessionInfo.java
index 5330630..96acf6c 100644
--- a/media/java/android/media/RouteSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -30,29 +30,28 @@
import java.util.Objects;
/**
- * Describes a route session that is made when a media route is selected.
+ * Describes a routing session which is created when a media route is selected.
* @hide
*/
-public class RouteSessionInfo implements Parcelable {
-
+public final class RoutingSessionInfo implements Parcelable {
@NonNull
- public static final Creator<RouteSessionInfo> CREATOR =
- new Creator<RouteSessionInfo>() {
+ public static final Creator<RoutingSessionInfo> CREATOR =
+ new Creator<RoutingSessionInfo>() {
@Override
- public RouteSessionInfo createFromParcel(Parcel in) {
- return new RouteSessionInfo(in);
+ public RoutingSessionInfo createFromParcel(Parcel in) {
+ return new RoutingSessionInfo(in);
}
@Override
- public RouteSessionInfo[] newArray(int size) {
- return new RouteSessionInfo[size];
+ public RoutingSessionInfo[] newArray(int size) {
+ return new RoutingSessionInfo[size];
}
};
- public static final String TAG = "RouteSessionInfo";
+ private static final String TAG = "RoutingSessionInfo";
final String mId;
final String mClientPackageName;
- final String mRouteType;
+ final String mRouteFeature;
@Nullable
final String mProviderId;
final List<String> mSelectedRoutes;
@@ -62,14 +61,15 @@
@Nullable
final Bundle mControlHints;
- RouteSessionInfo(@NonNull Builder builder) {
+ RoutingSessionInfo(@NonNull Builder builder) {
Objects.requireNonNull(builder, "builder must not be null.");
mId = builder.mId;
mClientPackageName = builder.mClientPackageName;
- mRouteType = builder.mRouteType;
+ mRouteFeature = builder.mRouteFeature;
mProviderId = builder.mProviderId;
+ // TODO: Needs to check that the routes already have unique IDs.
mSelectedRoutes = Collections.unmodifiableList(
convertToUniqueRouteIds(builder.mSelectedRoutes));
mSelectableRoutes = Collections.unmodifiableList(
@@ -82,12 +82,12 @@
mControlHints = builder.mControlHints;
}
- RouteSessionInfo(@NonNull Parcel src) {
+ RoutingSessionInfo(@NonNull Parcel src) {
Objects.requireNonNull(src, "src must not be null.");
mId = ensureString(src.readString());
mClientPackageName = ensureString(src.readString());
- mRouteType = ensureString(src.readString());
+ mRouteFeature = ensureString(src.readString());
mProviderId = src.readString();
mSelectedRoutes = ensureList(src.createStringArrayList());
@@ -113,18 +113,6 @@
}
/**
- * Returns whether the session info is valid or not
- *
- * TODO in this CL: Remove this method.
- */
- public boolean isValid() {
- return !TextUtils.isEmpty(mId)
- && !TextUtils.isEmpty(mClientPackageName)
- && !TextUtils.isEmpty(mRouteType)
- && mSelectedRoutes.size() > 0;
- }
-
- /**
* Gets the id of the session. The sessions which are given by {@link MediaRouter2} will have
* unique IDs.
* <p>
@@ -160,12 +148,12 @@
}
/**
- * Gets the route type of the session.
- * Routes that don't have the type can't be added to the session.
+ * Gets the route feature of the session.
+ * Routes that don't have the feature can't be selected into the session.
*/
@NonNull
- public String getRouteType() {
- return mRouteType;
+ public String getRouteFeature() {
+ return mRouteFeature;
}
/**
@@ -226,7 +214,7 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mId);
dest.writeString(mClientPackageName);
- dest.writeString(mRouteType);
+ dest.writeString(mRouteFeature);
dest.writeString(mProviderId);
dest.writeStringList(mSelectedRoutes);
dest.writeStringList(mSelectableRoutes);
@@ -236,11 +224,37 @@
}
@Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof RoutingSessionInfo)) {
+ return false;
+ }
+
+ RoutingSessionInfo other = (RoutingSessionInfo) obj;
+ return Objects.equals(mId, other.mId)
+ && Objects.equals(mClientPackageName, other.mClientPackageName)
+ && Objects.equals(mRouteFeature, other.mRouteFeature)
+ && Objects.equals(mProviderId, other.mProviderId)
+ && Objects.equals(mSelectedRoutes, other.mSelectedRoutes)
+ && Objects.equals(mSelectableRoutes, other.mSelectableRoutes)
+ && Objects.equals(mDeselectableRoutes, other.mDeselectableRoutes)
+ && Objects.equals(mTransferrableRoutes, other.mTransferrableRoutes);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mId, mClientPackageName, mRouteFeature, mProviderId,
+ mSelectedRoutes, mSelectableRoutes, mDeselectableRoutes, mTransferrableRoutes);
+ }
+
+ @Override
public String toString() {
StringBuilder result = new StringBuilder()
- .append("RouteSessionInfo{ ")
+ .append("RoutingSessionInfo{ ")
.append("sessionId=").append(mId)
- .append(", routeType=").append(mRouteType)
+ .append(", routeFeature=").append(mRouteFeature)
.append(", selectedRoutes={")
.append(String.join(",", mSelectedRoutes))
.append("}")
@@ -276,12 +290,12 @@
}
/**
- * Builder class for {@link RouteSessionInfo}.
+ * Builder class for {@link RoutingSessionInfo}.
*/
public static final class Builder {
final String mId;
final String mClientPackageName;
- final String mRouteType;
+ final String mRouteFeature;
String mProviderId;
final List<String> mSelectedRoutes;
final List<String> mSelectableRoutes;
@@ -290,24 +304,32 @@
Bundle mControlHints;
/**
- * Constructor for builder to create {@link RouteSessionInfo}.
+ * Constructor for builder to create {@link RoutingSessionInfo}.
* <p>
* In order to ensure ID uniqueness in {@link MediaRouter2} side, the value of
- * {@link RouteSessionInfo#getId()} can be different from what was set in
+ * {@link RoutingSessionInfo#getId()} can be different from what was set in
* {@link MediaRoute2ProviderService}.
* </p>
*
+ * @param id ID of the session. Must not be empty.
+ * @param clientPackageName package name of the client app which uses this session.
+ * If is is unknown, then just use an empty string.
+ * @param routeFeature the route feature of session. Must not be empty.
* @see MediaRoute2Info#getId()
*/
public Builder(@NonNull String id, @NonNull String clientPackageName,
- @NonNull String routeType) {
+ @NonNull String routeFeature) {
if (TextUtils.isEmpty(id)) {
throw new IllegalArgumentException("id must not be empty");
}
+ Objects.requireNonNull(clientPackageName, "clientPackageName must not be null");
+ if (TextUtils.isEmpty(routeFeature)) {
+ throw new IllegalArgumentException("routeFeature must not be empty");
+ }
+
mId = id;
- mClientPackageName = Objects.requireNonNull(
- clientPackageName, "clientPackageName must not be null");
- mRouteType = Objects.requireNonNull(routeType, "routeType must not be null");
+ mClientPackageName = clientPackageName;
+ mRouteFeature = routeFeature;
mSelectedRoutes = new ArrayList<>();
mSelectableRoutes = new ArrayList<>();
mDeselectableRoutes = new ArrayList<>();
@@ -315,17 +337,17 @@
}
/**
- * Constructor for builder to create {@link RouteSessionInfo} with
- * existing {@link RouteSessionInfo} instance.
+ * Constructor for builder to create {@link RoutingSessionInfo} with
+ * existing {@link RoutingSessionInfo} instance.
*
* @param sessionInfo the existing instance to copy data from.
*/
- public Builder(@NonNull RouteSessionInfo sessionInfo) {
+ public Builder(@NonNull RoutingSessionInfo sessionInfo) {
Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
mId = sessionInfo.mId;
mClientPackageName = sessionInfo.mClientPackageName;
- mRouteType = sessionInfo.mRouteType;
+ mRouteFeature = sessionInfo.mRouteFeature;
mProviderId = sessionInfo.mProviderId;
mSelectedRoutes = new ArrayList<>(sessionInfo.mSelectedRoutes);
@@ -338,8 +360,6 @@
/**
* Sets the provider ID of the session.
- * Also, calling this method will make all type of route IDs be unique by adding
- * {@code providerId:} as a prefix. So do NOT call this method twice on same instance.
*
* @hide
*/
@@ -362,20 +382,26 @@
}
/**
- * Adds a route to the selected routes.
+ * Adds a route to the selected routes. The {@code routeId} must not be empty.
*/
@NonNull
public Builder addSelectedRoute(@NonNull String routeId) {
- mSelectedRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
+ if (TextUtils.isEmpty(routeId)) {
+ throw new IllegalArgumentException("routeId must not be empty");
+ }
+ mSelectedRoutes.add(routeId);
return this;
}
/**
- * Removes a route from the selected routes.
+ * Removes a route from the selected routes. The {@code routeId} must not be empty.
*/
@NonNull
public Builder removeSelectedRoute(@NonNull String routeId) {
- mSelectedRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null"));
+ if (TextUtils.isEmpty(routeId)) {
+ throw new IllegalArgumentException("routeId must not be empty");
+ }
+ mSelectedRoutes.remove(routeId);
return this;
}
@@ -389,20 +415,26 @@
}
/**
- * Adds a route to the selectable routes.
+ * Adds a route to the selectable routes. The {@code routeId} must not be empty.
*/
@NonNull
public Builder addSelectableRoute(@NonNull String routeId) {
- mSelectableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
+ if (TextUtils.isEmpty(routeId)) {
+ throw new IllegalArgumentException("routeId must not be empty");
+ }
+ mSelectableRoutes.add(routeId);
return this;
}
/**
- * Removes a route from the selectable routes.
+ * Removes a route from the selectable routes. The {@code routeId} must not be empty.
*/
@NonNull
public Builder removeSelectableRoute(@NonNull String routeId) {
- mSelectableRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null"));
+ if (TextUtils.isEmpty(routeId)) {
+ throw new IllegalArgumentException("routeId must not be empty");
+ }
+ mSelectableRoutes.remove(routeId);
return this;
}
@@ -416,20 +448,26 @@
}
/**
- * Adds a route to the deselectable routes.
+ * Adds a route to the deselectable routes. The {@code routeId} must not be empty.
*/
@NonNull
public Builder addDeselectableRoute(@NonNull String routeId) {
- mDeselectableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
+ if (TextUtils.isEmpty(routeId)) {
+ throw new IllegalArgumentException("routeId must not be empty");
+ }
+ mDeselectableRoutes.add(routeId);
return this;
}
/**
- * Removes a route from the deselectable routes.
+ * Removes a route from the deselectable routes. The {@code routeId} must not be empty.
*/
@NonNull
public Builder removeDeselectableRoute(@NonNull String routeId) {
- mDeselectableRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null"));
+ if (TextUtils.isEmpty(routeId)) {
+ throw new IllegalArgumentException("routeId must not be empty");
+ }
+ mDeselectableRoutes.remove(routeId);
return this;
}
@@ -443,21 +481,26 @@
}
/**
- * Adds a route to the transferrable routes.
+ * Adds a route to the transferrable routes. The {@code routeId} must not be empty.
*/
@NonNull
public Builder addTransferrableRoute(@NonNull String routeId) {
- mTransferrableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
+ if (TextUtils.isEmpty(routeId)) {
+ throw new IllegalArgumentException("routeId must not be empty");
+ }
+ mTransferrableRoutes.add(routeId);
return this;
}
/**
- * Removes a route from the transferrable routes.
+ * Removes a route from the transferrable routes. The {@code routeId} must not be empty.
*/
@NonNull
public Builder removeTransferrableRoute(@NonNull String routeId) {
- mTransferrableRoutes.remove(
- Objects.requireNonNull(routeId, "routeId must not be null"));
+ if (TextUtils.isEmpty(routeId)) {
+ throw new IllegalArgumentException("routeId must not be empty");
+ }
+ mTransferrableRoutes.remove(routeId);
return this;
}
@@ -471,11 +514,16 @@
}
/**
- * Builds a route session info.
+ * Builds a routing session info.
+ *
+ * @throws IllegalArgumentException if no selected routes are added.
*/
@NonNull
- public RouteSessionInfo build() {
- return new RouteSessionInfo(this);
+ public RoutingSessionInfo build() {
+ if (mSelectedRoutes.isEmpty()) {
+ throw new IllegalArgumentException("selectedRoutes must not be empty");
+ }
+ return new RoutingSessionInfo(this);
}
}
}
diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index 77596a5..118f65c 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -25,6 +25,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
import android.media.AudioFormat;
import android.os.Handler;
@@ -75,7 +76,9 @@
value = {
RECOGNITION_FLAG_NONE,
RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO,
- RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS
+ RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS,
+ RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION,
+ RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION,
})
public @interface RecognitionFlags {}
@@ -100,11 +103,34 @@
* triggers after a call to {@link #startRecognition(int)}, if the model
* triggers multiple times.
* When this isn't specified, the default behavior is to stop recognition once the
- * trigger happenss, till the caller starts recognition again.
+ * trigger happens, till the caller starts recognition again.
*/
public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 0x2;
/**
+ * Audio capabilities flag for {@link #startRecognition(int)} that indicates
+ * if the underlying recognition should use AEC.
+ * This capability may or may not be supported by the system, and support can be queried
+ * by calling {@link SoundTriggerManager#getModuleProperties()} and checking
+ * {@link ModuleProperties#audioCapabilities}. The corresponding capabilities field for
+ * this flag is {@link SoundTrigger.ModuleProperties#CAPABILITY_ECHO_CANCELLATION}.
+ * If this flag is passed without the audio capability supported, there will be no audio effect
+ * applied.
+ */
+ public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 0x4;
+
+ /**
+ * Audio capabilities flag for {@link #startRecognition(int)} that indicates
+ * if the underlying recognition should use noise suppression.
+ * This capability may or may not be supported by the system, and support can be queried
+ * by calling {@link SoundTriggerManager#getModuleProperties()} and checking
+ * {@link ModuleProperties#audioCapabilities}. The corresponding capabilities field for
+ * this flag is {@link SoundTrigger.ModuleProperties#CAPABILITY_NOISE_SUPPRESSION}. If this flag
+ * is passed without the audio capability supported, there will be no audio effect applied.
+ */
+ public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 0x8;
+
+ /**
* Additional payload for {@link Callback#onDetected}.
*/
public static class EventPayload {
@@ -267,11 +293,20 @@
boolean allowMultipleTriggers =
(recognitionFlags & RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS) != 0;
- int status = STATUS_OK;
+
+ int audioCapabilities = 0;
+ if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION) != 0) {
+ audioCapabilities |= SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION;
+ }
+ if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION) != 0) {
+ audioCapabilities |= SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION;
+ }
+
+ int status;
try {
status = mSoundTriggerService.startRecognition(new ParcelUuid(mSoundModelId),
mRecognitionCallback, new RecognitionConfig(captureTriggerAudio,
- allowMultipleTriggers, null, null));
+ allowMultipleTriggers, null, null, audioCapabilities));
} catch (RemoteException e) {
return false;
}
diff --git a/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl b/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl
new file mode 100644
index 0000000..97a8849
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+/**
+ * AudioCapabilities supported by the implemented HAL driver.
+ * @hide
+ */
+@Backing(type="int")
+enum AudioCapabilities {
+ /**
+ * If set the underlying module supports AEC.
+ */
+ ECHO_CANCELLATION = 1 << 0,
+ /**
+ * If set, the underlying module supports noise suppression.
+ */
+ NOISE_SUPPRESSION = 1 << 1,
+}
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
index c7642e8..5c0eeb1 100644
--- a/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
@@ -28,6 +28,12 @@
/* Configuration for each key phrase. */
PhraseRecognitionExtra[] phraseRecognitionExtras;
+ /**
+ * Bit field encoding of the AudioCapabilities
+ * supported by the firmware.
+ */
+ int audioCapabilities;
+
/** Opaque capture configuration data. */
byte[] data;
}
diff --git a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
index 909f1a00..9c56e7b 100644
--- a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
+++ b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
@@ -58,4 +58,11 @@
* Rated power consumption when detection is active with TDB
* silence/sound/speech ratio */
int powerConsumptionMw;
+ /**
+ * Bit field encoding of the AudioCapabilities
+ * supported by the firmware.
+ * This property is supported for soundtrigger HAL v2.3 and above.
+ * If running a previous version, this value will be 0.
+ */
+ int audioCapabilities;
}
diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java
index 4318a0a..5e0b1ea 100644
--- a/media/java/android/media/tv/TvTrackInfo.java
+++ b/media/java/android/media/tv/TvTrackInfo.java
@@ -318,7 +318,8 @@
* @param flags The flags used for parceling.
*/
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Preconditions.checkNotNull(dest);
dest.writeInt(mType);
dest.writeString(mId);
dest.writeString(mLanguage);
@@ -387,11 +388,13 @@
public static final @android.annotation.NonNull Parcelable.Creator<TvTrackInfo> CREATOR =
new Parcelable.Creator<TvTrackInfo>() {
@Override
+ @NonNull
public TvTrackInfo createFromParcel(Parcel in) {
return new TvTrackInfo(in);
}
@Override
+ @NonNull
public TvTrackInfo[] newArray(int size) {
return new TvTrackInfo[size];
}
@@ -444,7 +447,9 @@
*
* @param language The language string encoded by either ISO 639-1 or ISO 639-2/T.
*/
- public final Builder setLanguage(String language) {
+ @NonNull
+ public Builder setLanguage(@NonNull String language) {
+ Preconditions.checkNotNull(language);
mLanguage = language;
return this;
}
@@ -454,7 +459,9 @@
*
* @param description The user readable description.
*/
- public final Builder setDescription(CharSequence description) {
+ @NonNull
+ public Builder setDescription(@NonNull CharSequence description) {
+ Preconditions.checkNotNull(description);
mDescription = description;
return this;
}
@@ -479,7 +486,8 @@
* @param audioChannelCount The audio channel count.
* @throws IllegalStateException if not called on an audio track
*/
- public final Builder setAudioChannelCount(int audioChannelCount) {
+ @NonNull
+ public Builder setAudioChannelCount(int audioChannelCount) {
if (mType != TYPE_AUDIO) {
throw new IllegalStateException("Not an audio track");
}
@@ -494,7 +502,8 @@
* @param audioSampleRate The audio sample rate.
* @throws IllegalStateException if not called on an audio track
*/
- public final Builder setAudioSampleRate(int audioSampleRate) {
+ @NonNull
+ public Builder setAudioSampleRate(int audioSampleRate) {
if (mType != TYPE_AUDIO) {
throw new IllegalStateException("Not an audio track");
}
@@ -570,7 +579,8 @@
* @param videoWidth The width of the video.
* @throws IllegalStateException if not called on a video track
*/
- public final Builder setVideoWidth(int videoWidth) {
+ @NonNull
+ public Builder setVideoWidth(int videoWidth) {
if (mType != TYPE_VIDEO) {
throw new IllegalStateException("Not a video track");
}
@@ -585,7 +595,8 @@
* @param videoHeight The height of the video.
* @throws IllegalStateException if not called on a video track
*/
- public final Builder setVideoHeight(int videoHeight) {
+ @NonNull
+ public Builder setVideoHeight(int videoHeight) {
if (mType != TYPE_VIDEO) {
throw new IllegalStateException("Not a video track");
}
@@ -600,7 +611,8 @@
* @param videoFrameRate The frame rate of the video.
* @throws IllegalStateException if not called on a video track
*/
- public final Builder setVideoFrameRate(float videoFrameRate) {
+ @NonNull
+ public Builder setVideoFrameRate(float videoFrameRate) {
if (mType != TYPE_VIDEO) {
throw new IllegalStateException("Not a video track");
}
@@ -620,7 +632,8 @@
* @param videoPixelAspectRatio The pixel aspect ratio of the video.
* @throws IllegalStateException if not called on a video track
*/
- public final Builder setVideoPixelAspectRatio(float videoPixelAspectRatio) {
+ @NonNull
+ public Builder setVideoPixelAspectRatio(float videoPixelAspectRatio) {
if (mType != TYPE_VIDEO) {
throw new IllegalStateException("Not a video track");
}
@@ -640,7 +653,8 @@
* @param videoActiveFormatDescription The AFD code of the video.
* @throws IllegalStateException if not called on a video track
*/
- public final Builder setVideoActiveFormatDescription(byte videoActiveFormatDescription) {
+ @NonNull
+ public Builder setVideoActiveFormatDescription(byte videoActiveFormatDescription) {
if (mType != TYPE_VIDEO) {
throw new IllegalStateException("Not a video track");
}
@@ -653,7 +667,9 @@
*
* @param extra The extra information.
*/
- public final Builder setExtra(Bundle extra) {
+ @NonNull
+ public Builder setExtra(@NonNull Bundle extra) {
+ Preconditions.checkNotNull(extra);
mExtra = new Bundle(extra);
return this;
}
@@ -663,6 +679,7 @@
*
* @return The new {@link TvTrackInfo} instance
*/
+ @NonNull
public TvTrackInfo build() {
return new TvTrackInfo(mType, mId, mLanguage, mDescription, mEncrypted,
mAudioChannelCount, mAudioSampleRate, mAudioDescription, mHardOfHearing,
diff --git a/media/java/android/media/tv/tuner/Descrambler.java b/media/java/android/media/tv/tuner/Descrambler.java
index f9f7a22..0143582 100644
--- a/media/java/android/media/tv/tuner/Descrambler.java
+++ b/media/java/android/media/tv/tuner/Descrambler.java
@@ -16,9 +16,12 @@
package android.media.tv.tuner;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.media.tv.tuner.Tuner.Filter;
-import android.media.tv.tuner.TunerConstants.DemuxPidType;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* This class is used to interact with descramblers.
@@ -26,9 +29,25 @@
* <p> Descrambler is a hardware component used to descramble data.
*
* <p> This class controls the TIS interaction with Tuner HAL.
+ *
* @hide
*/
public class Descrambler implements AutoCloseable {
+ /** @hide */
+ @IntDef(prefix = "PID_TYPE_", value = {PID_TYPE_T, PID_TYPE_MMPT})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PidType {}
+
+ /**
+ * Packet ID is used to specify packets in transport stream.
+ */
+ public static final int PID_TYPE_T = 1;
+ /**
+ * Packet ID is used to specify packets in MMTP.
+ */
+ public static final int PID_TYPE_MMPT = 2;
+
+
private long mNativeContext;
private native int nativeAddPid(int pidType, int pid, Filter filter);
@@ -52,10 +71,8 @@
* @param pid the PID of packets to start to be descrambled.
* @param filter an optional filter instance to identify upper stream.
* @return result status of the operation.
- *
- * @hide
*/
- public int addPid(@DemuxPidType int pidType, int pid, @Nullable Filter filter) {
+ public int addPid(@PidType int pidType, int pid, @Nullable Filter filter) {
return nativeAddPid(pidType, pid, filter);
}
@@ -68,10 +85,8 @@
* @param pid the PID of packets to stop to be descrambled.
* @param filter an optional filter instance to identify upper stream.
* @return result status of the operation.
- *
- * @hide
*/
- public int removePid(@DemuxPidType int pidType, int pid, @Nullable Filter filter) {
+ public int removePid(@PidType int pidType, int pid, @Nullable Filter filter) {
return nativeRemovePid(pidType, pid, filter);
}
@@ -83,17 +98,13 @@
*
* @param keyToken the token to be used to link the key slot.
* @return result status of the operation.
- *
- * @hide
*/
- public int setKeyToken(byte[] keyToken) {
+ public int setKeyToken(@Nullable byte[] keyToken) {
return nativeSetKeyToken(keyToken);
}
/**
* Release the descrambler instance.
- *
- * @hide
*/
@Override
public void close() {
diff --git a/media/java/android/media/tv/tuner/Lnb.java b/media/java/android/media/tv/tuner/Lnb.java
index f181b49..c7cc9e6d 100644
--- a/media/java/android/media/tv/tuner/Lnb.java
+++ b/media/java/android/media/tv/tuner/Lnb.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.content.Context;
import android.hardware.tv.tuner.V1_0.Constants;
import android.media.tv.tuner.Tuner.LnbCallback;
@@ -37,6 +38,7 @@
*
* @hide
*/
+@SystemApi
public class Lnb implements AutoCloseable {
/** @hide */
@IntDef({VOLTAGE_NONE, VOLTAGE_5V, VOLTAGE_11V, VOLTAGE_12V, VOLTAGE_13V, VOLTAGE_14V,
diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java
index 76c3f23..c2d6c58c 100644
--- a/media/java/android/media/tv/tuner/TunerConstants.java
+++ b/media/java/android/media/tv/tuner/TunerConstants.java
@@ -51,16 +51,6 @@
/** @hide */
- @IntDef({DEMUX_T_PID, DEMUX_MMPT_PID})
- @Retention(RetentionPolicy.SOURCE)
- public @interface DemuxPidType {}
- /** @hide */
- public static final int DEMUX_T_PID = 1;
- /** @hide */
- public static final int DEMUX_MMPT_PID = 2;
-
-
- /** @hide */
@IntDef({FILTER_SUBTYPE_UNDEFINED, FILTER_SUBTYPE_SECTION, FILTER_SUBTYPE_PES,
FILTER_SUBTYPE_AUDIO, FILTER_SUBTYPE_VIDEO, FILTER_SUBTYPE_DOWNLOAD,
FILTER_SUBTYPE_RECORD, FILTER_SUBTYPE_TS, FILTER_SUBTYPE_PCR, FILTER_SUBTYPE_TEMI,
@@ -1308,37 +1298,30 @@
/**
* Operation succeeded.
- * @hide
*/
public static final int RESULT_SUCCESS = Constants.Result.SUCCESS;
/**
* Operation failed because the corresponding resources are not available.
- * @hide
*/
public static final int RESULT_UNAVAILABLE = Constants.Result.UNAVAILABLE;
/**
* Operation failed because the corresponding resources are not initialized.
- * @hide
*/
public static final int RESULT_NOT_INITIALIZED = Constants.Result.NOT_INITIALIZED;
/**
* Operation failed because it's not in a valid state.
- * @hide
*/
public static final int RESULT_INVALID_STATE = Constants.Result.INVALID_STATE;
/**
* Operation failed because there are invalid arguments.
- * @hide
*/
public static final int RESULT_INVALID_ARGUMENT = Constants.Result.INVALID_ARGUMENT;
/**
* Memory allocation failed.
- * @hide
*/
public static final int RESULT_OUT_OF_MEMORY = Constants.Result.OUT_OF_MEMORY;
/**
* Operation failed due to unknown errors.
- * @hide
*/
public static final int RESULT_UNKNOWN_ERROR = Constants.Result.UNKNOWN_ERROR;
diff --git a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
index 8c0273b..221783b 100644
--- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
@@ -16,14 +16,13 @@
package com.android.mediarouteprovider.example;
-import static android.media.MediaRoute2Info.DEVICE_TYPE_SPEAKER;
-import static android.media.MediaRoute2Info.DEVICE_TYPE_TV;
+import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_SPEAKER;
+import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_TV;
import android.content.Intent;
import android.media.MediaRoute2Info;
-import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2ProviderService;
-import android.media.RouteSessionInfo;
+import android.media.RoutingSessionInfo;
import android.os.IBinder;
import android.text.TextUtils;
@@ -46,8 +45,8 @@
public static final String ROUTE_ID5_TO_TRANSFER_TO = "route_id5_to_transfer_to";
public static final String ROUTE_NAME5 = "Sample Route 5 - Route to transfer to";
- public static final String ROUTE_ID_SPECIAL_TYPE = "route_special_type";
- public static final String ROUTE_NAME_SPECIAL_TYPE = "Special Type Route";
+ public static final String ROUTE_ID_SPECIAL_FEATURE = "route_special_feature";
+ public static final String ROUTE_NAME_SPECIAL_FEATURE = "Special Feature Route";
public static final int VOLUME_MAX = 100;
public static final String ROUTE_ID_FIXED_VOLUME = "route_fixed_volume";
@@ -58,49 +57,49 @@
public static final String ACTION_REMOVE_ROUTE =
"com.android.mediarouteprovider.action_remove_route";
- public static final String TYPE_SAMPLE =
- "com.android.mediarouteprovider.TYPE_SAMPLE";
- public static final String TYPE_SPECIAL =
- "com.android.mediarouteprovider.TYPE_SPECIAL";
+ public static final String FEATURE_SAMPLE =
+ "com.android.mediarouteprovider.FEATURE_SAMPLE";
+ public static final String FEATURE_SPECIAL =
+ "com.android.mediarouteprovider.FEATURE_SPECIAL";
Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
- Map<String, String> mRouteSessionMap = new HashMap<>();
+ Map<String, String> mRouteIdToSessionId = new HashMap<>();
private int mNextSessionId = 1000;
private void initializeRoutes() {
MediaRoute2Info route1 = new MediaRoute2Info.Builder(ROUTE_ID1, ROUTE_NAME1)
- .addRouteType(TYPE_SAMPLE)
- .setDeviceType(DEVICE_TYPE_TV)
+ .addFeature(FEATURE_SAMPLE)
+ .setDeviceType(DEVICE_TYPE_REMOTE_TV)
.build();
MediaRoute2Info route2 = new MediaRoute2Info.Builder(ROUTE_ID2, ROUTE_NAME2)
- .addRouteType(TYPE_SAMPLE)
- .setDeviceType(DEVICE_TYPE_SPEAKER)
+ .addFeature(FEATURE_SAMPLE)
+ .setDeviceType(DEVICE_TYPE_REMOTE_SPEAKER)
.build();
MediaRoute2Info route3 = new MediaRoute2Info.Builder(
ROUTE_ID3_SESSION_CREATION_FAILED, ROUTE_NAME3)
- .addRouteType(TYPE_SAMPLE)
+ .addFeature(FEATURE_SAMPLE)
.build();
MediaRoute2Info route4 = new MediaRoute2Info.Builder(
ROUTE_ID4_TO_SELECT_AND_DESELECT, ROUTE_NAME4)
- .addRouteType(TYPE_SAMPLE)
+ .addFeature(FEATURE_SAMPLE)
.build();
MediaRoute2Info route5 = new MediaRoute2Info.Builder(
ROUTE_ID5_TO_TRANSFER_TO, ROUTE_NAME5)
- .addRouteType(TYPE_SAMPLE)
+ .addFeature(FEATURE_SAMPLE)
.build();
MediaRoute2Info routeSpecial =
- new MediaRoute2Info.Builder(ROUTE_ID_SPECIAL_TYPE, ROUTE_NAME_SPECIAL_TYPE)
- .addRouteType(TYPE_SAMPLE)
- .addRouteType(TYPE_SPECIAL)
+ new MediaRoute2Info.Builder(ROUTE_ID_SPECIAL_FEATURE, ROUTE_NAME_SPECIAL_FEATURE)
+ .addFeature(FEATURE_SAMPLE)
+ .addFeature(FEATURE_SPECIAL)
.build();
MediaRoute2Info fixedVolumeRoute =
new MediaRoute2Info.Builder(ROUTE_ID_FIXED_VOLUME, ROUTE_NAME_FIXED_VOLUME)
- .addRouteType(TYPE_SAMPLE)
+ .addFeature(FEATURE_SAMPLE)
.setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_FIXED)
.build();
MediaRoute2Info variableVolumeRoute =
new MediaRoute2Info.Builder(ROUTE_ID_VARIABLE_VOLUME, ROUTE_NAME_VARIABLE_VOLUME)
- .addRouteType(TYPE_SAMPLE)
+ .addFeature(FEATURE_SAMPLE)
.setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
.setVolumeMax(VOLUME_MAX)
.build();
@@ -154,6 +153,7 @@
@Override
public void onUpdateVolume(String routeId, int delta) {
+ android.util.Log.d(TAG, "onUpdateVolume routeId= " + routeId + "delta=" + delta);
MediaRoute2Info route = mRoutes.get(routeId);
if (route == null) {
return;
@@ -167,7 +167,7 @@
}
@Override
- public void onCreateSession(String packageName, String routeId, String routeType,
+ public void onCreateSession(String packageName, String routeId, String routeFeature,
long requestId) {
MediaRoute2Info route = mRoutes.get(routeId);
if (route == null || TextUtils.equals(ROUTE_ID3_SESSION_CREATION_FAILED, routeId)) {
@@ -183,10 +183,10 @@
mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
.setClientPackageName(packageName)
.build());
- mRouteSessionMap.put(routeId, sessionId);
+ mRouteIdToSessionId.put(routeId, sessionId);
- RouteSessionInfo sessionInfo = new RouteSessionInfo.Builder(
- sessionId, packageName, routeType)
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ sessionId, packageName, routeFeature)
.addSelectedRoute(routeId)
.addSelectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT)
.addTransferrableRoute(ROUTE_ID5_TO_TRANSFER_TO)
@@ -196,9 +196,9 @@
}
@Override
- public void onDestroySession(String sessionId, RouteSessionInfo lastSessionInfo) {
+ public void onDestroySession(String sessionId, RoutingSessionInfo lastSessionInfo) {
for (String routeId : lastSessionInfo.getSelectedRoutes()) {
- mRouteSessionMap.remove(routeId);
+ mRouteIdToSessionId.remove(routeId);
MediaRoute2Info route = mRoutes.get(routeId);
if (route != null) {
mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
@@ -210,7 +210,7 @@
@Override
public void onSelectRoute(String sessionId, String routeId) {
- RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
+ RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
MediaRoute2Info route = mRoutes.get(routeId);
if (route == null || sessionInfo == null) {
return;
@@ -220,9 +220,9 @@
mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
.setClientPackageName(sessionInfo.getClientPackageName())
.build());
- mRouteSessionMap.put(routeId, sessionId);
+ mRouteIdToSessionId.put(routeId, sessionId);
- RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+ RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
.addSelectedRoute(routeId)
.removeSelectableRoute(routeId)
.addDeselectableRoute(routeId)
@@ -233,18 +233,25 @@
@Override
public void onDeselectRoute(String sessionId, String routeId) {
- RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
+ RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
MediaRoute2Info route = mRoutes.get(routeId);
- mRouteSessionMap.remove(routeId);
- if (sessionInfo == null || route == null) {
+ if (sessionInfo == null || route == null
+ || !sessionInfo.getSelectedRoutes().contains(routeId)) {
return;
}
+
+ mRouteIdToSessionId.remove(routeId);
mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
.setClientPackageName(null)
.build());
- RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+ if (sessionInfo.getSelectedRoutes().size() == 1) {
+ releaseSession(sessionId);
+ return;
+ }
+
+ RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
.removeSelectedRoute(routeId)
.addSelectableRoute(routeId)
.removeDeselectableRoute(routeId)
@@ -255,8 +262,8 @@
@Override
public void onTransferToRoute(String sessionId, String routeId) {
- RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
- RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+ RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
+ RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
.clearSelectedRoutes()
.addSelectedRoute(routeId)
.removeDeselectableRoute(routeId)
@@ -267,18 +274,15 @@
}
void maybeDeselectRoute(String routeId) {
- if (!mRouteSessionMap.containsKey(routeId)) {
+ if (!mRouteIdToSessionId.containsKey(routeId)) {
return;
}
- String sessionId = mRouteSessionMap.get(routeId);
+ String sessionId = mRouteIdToSessionId.get(routeId);
onDeselectRoute(sessionId, routeId);
}
void publishRoutes() {
- MediaRoute2ProviderInfo info = new MediaRoute2ProviderInfo.Builder()
- .addRoutes(mRoutes.values())
- .build();
- updateProviderInfo(info);
+ notifyRoutes(mRoutes.values());
}
}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
index ce4bb8e..007229a 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
@@ -18,23 +18,23 @@
import static android.media.MediaRoute2Info.CONNECTION_STATE_CONNECTED;
import static android.media.MediaRoute2Info.CONNECTION_STATE_CONNECTING;
-import static android.media.MediaRoute2Info.DEVICE_TYPE_SPEAKER;
-import static android.media.MediaRoute2Info.DEVICE_TYPE_TV;
+import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_SPEAKER;
+import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_TV;
import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_FIXED;
import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
+import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURES_ALL;
+import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURES_SPECIAL;
+import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURE_SAMPLE;
+import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURE_SPECIAL;
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID1;
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID2;
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID3_SESSION_CREATION_FAILED;
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID4_TO_SELECT_AND_DESELECT;
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID5_TO_TRANSFER_TO;
-import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_SPECIAL_TYPE;
+import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_SPECIAL_FEATURE;
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_VARIABLE_VOLUME;
import static com.android.mediaroutertest.MediaRouterManagerTest.SYSTEM_PROVIDER_ID;
-import static com.android.mediaroutertest.MediaRouterManagerTest.TYPES_ALL;
-import static com.android.mediaroutertest.MediaRouterManagerTest.TYPES_SPECIAL;
-import static com.android.mediaroutertest.MediaRouterManagerTest.TYPE_SAMPLE;
-import static com.android.mediaroutertest.MediaRouterManagerTest.TYPE_SPECIAL;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -48,10 +48,10 @@
import android.media.MediaRoute2Info;
import android.media.MediaRouter2;
import android.media.MediaRouter2.RouteCallback;
-import android.media.MediaRouter2.RouteSessionController;
+import android.media.MediaRouter2.RoutingController;
import android.media.MediaRouter2.SessionCallback;
-import android.media.RouteDiscoveryRequest;
-import android.media.RouteSessionInfo;
+import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
import android.net.Uri;
import android.os.Parcel;
import android.support.test.InstrumentationRegistry;
@@ -100,10 +100,10 @@
*/
@Test
public void testGetRoutes() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutes(TYPES_SPECIAL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURES_SPECIAL);
assertEquals(1, routes.size());
- assertNotNull(routes.get(ROUTE_ID_SPECIAL_TYPE));
+ assertNotNull(routes.get(ROUTE_ID_SPECIAL_FEATURE));
}
@Test
@@ -115,9 +115,9 @@
.setIconUri(new Uri.Builder().path("icon").build())
.setVolume(5)
.setVolumeMax(20)
- .addRouteType(TYPE_SAMPLE)
+ .addFeature(FEATURE_SAMPLE)
.setVolumeHandling(PLAYBACK_VOLUME_VARIABLE)
- .setDeviceType(DEVICE_TYPE_SPEAKER)
+ .setDeviceType(DEVICE_TYPE_REMOTE_SPEAKER)
.build();
MediaRoute2Info routeInfoRebuilt = new MediaRoute2Info.Builder(routeInfo).build();
@@ -138,21 +138,13 @@
.setClientPackageName("com.android.mediaroutertest")
.setConnectionState(CONNECTION_STATE_CONNECTING)
.setIconUri(new Uri.Builder().path("icon").build())
- .addRouteType(TYPE_SAMPLE)
+ .addFeature(FEATURE_SAMPLE)
.setVolume(5)
.setVolumeMax(20)
.setVolumeHandling(PLAYBACK_VOLUME_VARIABLE)
- .setDeviceType(DEVICE_TYPE_SPEAKER)
+ .setDeviceType(DEVICE_TYPE_REMOTE_SPEAKER)
.build();
- MediaRoute2Info routeId = new MediaRoute2Info.Builder(route)
- .setId("another id").build();
- assertNotEquals(route, routeId);
-
- MediaRoute2Info routeName = new MediaRoute2Info.Builder(route)
- .setName("another name").build();
- assertNotEquals(route, routeName);
-
MediaRoute2Info routeDescription = new MediaRoute2Info.Builder(route)
.setDescription("another description").build();
assertNotEquals(route, routeDescription);
@@ -170,7 +162,7 @@
assertNotEquals(route, routeClient);
MediaRoute2Info routeType = new MediaRoute2Info.Builder(route)
- .addRouteType(TYPE_SPECIAL).build();
+ .addFeature(FEATURE_SPECIAL).build();
assertNotEquals(route, routeType);
MediaRoute2Info routeVolume = new MediaRoute2Info.Builder(route)
@@ -186,13 +178,13 @@
assertNotEquals(route, routeVolumeHandling);
MediaRoute2Info routeDeviceType = new MediaRoute2Info.Builder(route)
- .setVolume(DEVICE_TYPE_TV).build();
+ .setVolume(DEVICE_TYPE_REMOTE_TV).build();
assertNotEquals(route, routeDeviceType);
}
@Test
public void testControlVolumeWithRouter() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutes(TYPES_ALL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURES_ALL);
MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
assertNotNull(volRoute);
@@ -204,13 +196,13 @@
() -> mRouter2.requestUpdateVolume(volRoute, deltaVolume),
ROUTE_ID_VARIABLE_VOLUME,
(route -> route.getVolume() == originalVolume + deltaVolume),
- TYPES_ALL);
+ FEATURES_ALL);
awaitOnRouteChanged(
() -> mRouter2.requestSetVolume(volRoute, originalVolume),
ROUTE_ID_VARIABLE_VOLUME,
(route -> route.getVolume() == originalVolume),
- TYPES_ALL);
+ FEATURES_ALL);
}
@Test
@@ -237,13 +229,13 @@
@Test
public void testRequestCreateSessionWithInvalidArguments() {
MediaRoute2Info route = new MediaRoute2Info.Builder("id", "name").build();
- String routeType = "routeType";
+ String routeFeature = "routeFeature";
// Tests null route
assertThrows(NullPointerException.class,
- () -> mRouter2.requestCreateSession(null, routeType));
+ () -> mRouter2.requestCreateSession(null, routeFeature));
- // Tests null or empty route type
+ // Tests null or empty route feature
assertThrows(IllegalArgumentException.class,
() -> mRouter2.requestCreateSession(route, null));
assertThrows(IllegalArgumentException.class,
@@ -252,42 +244,42 @@
@Test
public void testRequestCreateSessionSuccess() throws Exception {
- final List<String> sampleRouteType = new ArrayList<>();
- sampleRouteType.add(TYPE_SAMPLE);
+ final List<String> sampleRouteFeature = new ArrayList<>();
+ sampleRouteFeature.add(FEATURE_SAMPLE);
- Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteFeature);
MediaRoute2Info route = routes.get(ROUTE_ID1);
assertNotNull(route);
final CountDownLatch successLatch = new CountDownLatch(1);
final CountDownLatch failureLatch = new CountDownLatch(1);
- final List<RouteSessionController> controllers = new ArrayList<>();
+ final List<RoutingController> controllers = new ArrayList<>();
// Create session with this route
SessionCallback sessionCallback = new SessionCallback() {
@Override
- public void onSessionCreated(RouteSessionController controller) {
+ public void onSessionCreated(RoutingController controller) {
assertNotNull(controller);
assertTrue(createRouteMap(controller.getSelectedRoutes()).containsKey(ROUTE_ID1));
- assertTrue(TextUtils.equals(TYPE_SAMPLE, controller.getRouteType()));
+ assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature()));
controllers.add(controller);
successLatch.countDown();
}
@Override
public void onSessionCreationFailed(MediaRoute2Info requestedRoute,
- String requestedRouteType) {
+ String requestedRouteFeature) {
failureLatch.countDown();
}
};
// TODO: Remove this once the MediaRouter2 becomes always connected to the service.
RouteCallback routeCallback = new RouteCallback();
- mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY);
+ mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
try {
mRouter2.registerSessionCallback(mExecutor, sessionCallback);
- mRouter2.requestCreateSession(route, TYPE_SAMPLE);
+ mRouter2.requestCreateSession(route, FEATURE_SAMPLE);
assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
// onSessionCreationFailed should not be called.
@@ -302,7 +294,7 @@
@Test
public void testRequestCreateSessionFailure() throws Exception {
final List<String> sampleRouteType = new ArrayList<>();
- sampleRouteType.add(TYPE_SAMPLE);
+ sampleRouteType.add(FEATURE_SAMPLE);
Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
MediaRoute2Info route = routes.get(ROUTE_ID3_SESSION_CREATION_FAILED);
@@ -310,32 +302,32 @@
final CountDownLatch successLatch = new CountDownLatch(1);
final CountDownLatch failureLatch = new CountDownLatch(1);
- final List<RouteSessionController> controllers = new ArrayList<>();
+ final List<RoutingController> controllers = new ArrayList<>();
// Create session with this route
SessionCallback sessionCallback = new SessionCallback() {
@Override
- public void onSessionCreated(RouteSessionController controller) {
+ public void onSessionCreated(RoutingController controller) {
controllers.add(controller);
successLatch.countDown();
}
@Override
public void onSessionCreationFailed(MediaRoute2Info requestedRoute,
- String requestedRouteType) {
+ String requestedRouteFeature) {
assertEquals(route, requestedRoute);
- assertTrue(TextUtils.equals(TYPE_SAMPLE, requestedRouteType));
+ assertTrue(TextUtils.equals(FEATURE_SAMPLE, requestedRouteFeature));
failureLatch.countDown();
}
};
// TODO: Remove this once the MediaRouter2 becomes always connected to the service.
RouteCallback routeCallback = new RouteCallback();
- mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY);
+ mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
try {
mRouter2.registerSessionCallback(mExecutor, sessionCallback);
- mRouter2.requestCreateSession(route, TYPE_SAMPLE);
+ mRouter2.requestCreateSession(route, FEATURE_SAMPLE);
assertTrue(failureLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
// onSessionCreated should not be called.
@@ -350,23 +342,23 @@
@Test
public void testRequestCreateSessionMultipleSessions() throws Exception {
final List<String> sampleRouteType = new ArrayList<>();
- sampleRouteType.add(TYPE_SAMPLE);
+ sampleRouteType.add(FEATURE_SAMPLE);
final CountDownLatch successLatch = new CountDownLatch(2);
final CountDownLatch failureLatch = new CountDownLatch(1);
- final List<RouteSessionController> createdControllers = new ArrayList<>();
+ final List<RoutingController> createdControllers = new ArrayList<>();
// Create session with this route
SessionCallback sessionCallback = new SessionCallback() {
@Override
- public void onSessionCreated(RouteSessionController controller) {
+ public void onSessionCreated(RoutingController controller) {
createdControllers.add(controller);
successLatch.countDown();
}
@Override
public void onSessionCreationFailed(MediaRoute2Info requestedRoute,
- String requestedRouteType) {
+ String requestedRouteFeature) {
failureLatch.countDown();
}
};
@@ -379,12 +371,12 @@
// TODO: Remove this once the MediaRouter2 becomes always connected to the service.
RouteCallback routeCallback = new RouteCallback();
- mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY);
+ mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
try {
mRouter2.registerSessionCallback(mExecutor, sessionCallback);
- mRouter2.requestCreateSession(route1, TYPE_SAMPLE);
- mRouter2.requestCreateSession(route2, TYPE_SAMPLE);
+ mRouter2.requestCreateSession(route1, FEATURE_SAMPLE);
+ mRouter2.requestCreateSession(route2, FEATURE_SAMPLE);
assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
// onSessionCreationFailed should not be called.
@@ -392,14 +384,14 @@
// Created controllers should have proper info
assertEquals(2, createdControllers.size());
- RouteSessionController controller1 = createdControllers.get(0);
- RouteSessionController controller2 = createdControllers.get(1);
+ RoutingController controller1 = createdControllers.get(0);
+ RoutingController controller2 = createdControllers.get(1);
assertNotEquals(controller1.getSessionId(), controller2.getSessionId());
assertTrue(createRouteMap(controller1.getSelectedRoutes()).containsKey(ROUTE_ID1));
assertTrue(createRouteMap(controller2.getSelectedRoutes()).containsKey(ROUTE_ID2));
- assertTrue(TextUtils.equals(TYPE_SAMPLE, controller1.getRouteType()));
- assertTrue(TextUtils.equals(TYPE_SAMPLE, controller2.getRouteType()));
+ assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller1.getRouteFeature()));
+ assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller2.getRouteFeature()));
} finally {
releaseControllers(createdControllers);
@@ -411,7 +403,7 @@
@Test
public void testSessionCallbackIsNotCalledAfterUnregistered() throws Exception {
final List<String> sampleRouteType = new ArrayList<>();
- sampleRouteType.add(TYPE_SAMPLE);
+ sampleRouteType.add(FEATURE_SAMPLE);
Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
MediaRoute2Info route = routes.get(ROUTE_ID1);
@@ -419,30 +411,30 @@
final CountDownLatch successLatch = new CountDownLatch(1);
final CountDownLatch failureLatch = new CountDownLatch(1);
- final List<RouteSessionController> controllers = new ArrayList<>();
+ final List<RoutingController> controllers = new ArrayList<>();
// Create session with this route
SessionCallback sessionCallback = new SessionCallback() {
@Override
- public void onSessionCreated(RouteSessionController controller) {
+ public void onSessionCreated(RoutingController controller) {
controllers.add(controller);
successLatch.countDown();
}
@Override
public void onSessionCreationFailed(MediaRoute2Info requestedRoute,
- String requestedRouteType) {
+ String requestedRouteFeature) {
failureLatch.countDown();
}
};
// TODO: Remove this once the MediaRouter2 becomes always connected to the service.
RouteCallback routeCallback = new RouteCallback();
- mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY);
+ mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
try {
mRouter2.registerSessionCallback(mExecutor, sessionCallback);
- mRouter2.requestCreateSession(route, TYPE_SAMPLE);
+ mRouter2.requestCreateSession(route, FEATURE_SAMPLE);
// Unregisters session callback
mRouter2.unregisterSessionCallback(sessionCallback);
@@ -459,9 +451,9 @@
// TODO: Add tests for illegal inputs if needed (e.g. selecting already selected route)
@Test
- public void testRouteSessionControllerSelectAndDeselectRoute() throws Exception {
+ public void testRoutingControllerSelectAndDeselectRoute() throws Exception {
final List<String> sampleRouteType = new ArrayList<>();
- sampleRouteType.add(TYPE_SAMPLE);
+ sampleRouteType.add(FEATURE_SAMPLE);
Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1);
@@ -470,22 +462,22 @@
final CountDownLatch onSessionCreatedLatch = new CountDownLatch(1);
final CountDownLatch onSessionInfoChangedLatchForSelect = new CountDownLatch(1);
final CountDownLatch onSessionInfoChangedLatchForDeselect = new CountDownLatch(1);
- final List<RouteSessionController> controllers = new ArrayList<>();
+ final List<RoutingController> controllers = new ArrayList<>();
// Create session with ROUTE_ID1
SessionCallback sessionCallback = new SessionCallback() {
@Override
- public void onSessionCreated(RouteSessionController controller) {
+ public void onSessionCreated(RoutingController controller) {
assertNotNull(controller);
assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1));
- assertTrue(TextUtils.equals(TYPE_SAMPLE, controller.getRouteType()));
+ assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature()));
controllers.add(controller);
onSessionCreatedLatch.countDown();
}
@Override
- public void onSessionInfoChanged(RouteSessionController controller,
- RouteSessionInfo oldInfo, RouteSessionInfo newInfo) {
+ public void onSessionInfoChanged(RoutingController controller,
+ RoutingSessionInfo oldInfo, RoutingSessionInfo newInfo) {
if (onSessionCreatedLatch.getCount() != 0
|| !TextUtils.equals(
controllers.get(0).getSessionId(), controller.getSessionId())) {
@@ -527,15 +519,15 @@
// TODO: Remove this once the MediaRouter2 becomes always connected to the service.
RouteCallback routeCallback = new RouteCallback();
- mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY);
+ mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
try {
mRouter2.registerSessionCallback(mExecutor, sessionCallback);
- mRouter2.requestCreateSession(routeToCreateSessionWith, TYPE_SAMPLE);
+ mRouter2.requestCreateSession(routeToCreateSessionWith, FEATURE_SAMPLE);
assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertEquals(1, controllers.size());
- RouteSessionController controller = controllers.get(0);
+ RoutingController controller = controllers.get(0);
assertTrue(getRouteIds(controller.getSelectableRoutes())
.contains(ROUTE_ID4_TO_SELECT_AND_DESELECT));
@@ -558,9 +550,9 @@
}
@Test
- public void testRouteSessionControllerTransferToRoute() throws Exception {
+ public void testRoutingControllerTransferToRoute() throws Exception {
final List<String> sampleRouteType = new ArrayList<>();
- sampleRouteType.add(TYPE_SAMPLE);
+ sampleRouteType.add(FEATURE_SAMPLE);
Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1);
@@ -568,26 +560,22 @@
final CountDownLatch onSessionCreatedLatch = new CountDownLatch(1);
final CountDownLatch onSessionInfoChangedLatch = new CountDownLatch(1);
- final List<RouteSessionController> controllers = new ArrayList<>();
+ final List<RoutingController> controllers = new ArrayList<>();
// Create session with ROUTE_ID1
SessionCallback sessionCallback = new SessionCallback() {
@Override
- public void onSessionCreated(RouteSessionController controller) {
+ public void onSessionCreated(RoutingController controller) {
assertNotNull(controller);
- android.util.Log.d(TAG, "selected route ids ");
- for (String routeId : getRouteIds(controller.getSelectedRoutes())) {
- android.util.Log.d(TAG, "route id : " + routeId);
- }
assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1));
- assertTrue(TextUtils.equals(TYPE_SAMPLE, controller.getRouteType()));
+ assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature()));
controllers.add(controller);
onSessionCreatedLatch.countDown();
}
@Override
- public void onSessionInfoChanged(RouteSessionController controller,
- RouteSessionInfo oldInfo, RouteSessionInfo newInfo) {
+ public void onSessionInfoChanged(RoutingController controller,
+ RoutingSessionInfo oldInfo, RoutingSessionInfo newInfo) {
if (onSessionCreatedLatch.getCount() != 0
|| !TextUtils.equals(
controllers.get(0).getSessionId(), controller.getSessionId())) {
@@ -613,15 +601,15 @@
// TODO: Remove this once the MediaRouter2 becomes always connected to the service.
RouteCallback routeCallback = new RouteCallback();
- mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY);
+ mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
try {
mRouter2.registerSessionCallback(mExecutor, sessionCallback);
- mRouter2.requestCreateSession(routeToCreateSessionWith, TYPE_SAMPLE);
+ mRouter2.requestCreateSession(routeToCreateSessionWith, FEATURE_SAMPLE);
assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertEquals(1, controllers.size());
- RouteSessionController controller = controllers.get(0);
+ RoutingController controller = controllers.get(0);
assertTrue(getRouteIds(controller.getTransferrableRoutes())
.contains(ROUTE_ID5_TO_TRANSFER_TO));
@@ -641,9 +629,9 @@
// TODO: Add tests for onSessionReleased() call.
@Test
- public void testRouteSessionControllerReleaseShouldIgnoreTransferTo() throws Exception {
+ public void testRoutingControllerReleaseShouldIgnoreTransferTo() throws Exception {
final List<String> sampleRouteType = new ArrayList<>();
- sampleRouteType.add(TYPE_SAMPLE);
+ sampleRouteType.add(FEATURE_SAMPLE);
Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1);
@@ -651,22 +639,22 @@
final CountDownLatch onSessionCreatedLatch = new CountDownLatch(1);
final CountDownLatch onSessionInfoChangedLatch = new CountDownLatch(1);
- final List<RouteSessionController> controllers = new ArrayList<>();
+ final List<RoutingController> controllers = new ArrayList<>();
// Create session with ROUTE_ID1
SessionCallback sessionCallback = new SessionCallback() {
@Override
- public void onSessionCreated(RouteSessionController controller) {
+ public void onSessionCreated(RoutingController controller) {
assertNotNull(controller);
assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1));
- assertTrue(TextUtils.equals(TYPE_SAMPLE, controller.getRouteType()));
+ assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature()));
controllers.add(controller);
onSessionCreatedLatch.countDown();
}
@Override
- public void onSessionInfoChanged(RouteSessionController controller,
- RouteSessionInfo oldInfo, RouteSessionInfo newInfo) {
+ public void onSessionInfoChanged(RoutingController controller,
+ RoutingSessionInfo oldInfo, RoutingSessionInfo newInfo) {
if (onSessionCreatedLatch.getCount() != 0
|| !TextUtils.equals(
controllers.get(0).getSessionId(), controller.getSessionId())) {
@@ -678,15 +666,15 @@
// TODO: Remove this once the MediaRouter2 becomes always connected to the service.
RouteCallback routeCallback = new RouteCallback();
- mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY);
+ mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
try {
mRouter2.registerSessionCallback(mExecutor, sessionCallback);
- mRouter2.requestCreateSession(routeToCreateSessionWith, TYPE_SAMPLE);
+ mRouter2.requestCreateSession(routeToCreateSessionWith, FEATURE_SAMPLE);
assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertEquals(1, controllers.size());
- RouteSessionController controller = controllers.get(0);
+ RoutingController controller = controllers.get(0);
assertTrue(getRouteIds(controller.getTransferrableRoutes())
.contains(ROUTE_ID5_TO_TRANSFER_TO));
@@ -735,7 +723,7 @@
};
mRouter2.registerRouteCallback(mExecutor, routeCallback,
- new RouteDiscoveryRequest.Builder(routeTypes, true).build());
+ new RouteDiscoveryPreference.Builder(routeTypes, true).build());
try {
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
return createRouteMap(mRouter2.getRoutes());
@@ -744,8 +732,8 @@
}
}
- static void releaseControllers(@NonNull List<RouteSessionController> controllers) {
- for (RouteSessionController controller : controllers) {
+ static void releaseControllers(@NonNull List<RoutingController> controllers) {
+ for (RoutingController controller : controllers) {
controller.release();
}
}
@@ -775,7 +763,7 @@
}
};
mRouter2.registerRouteCallback(mExecutor, routeCallback,
- new RouteDiscoveryRequest.Builder(routeTypes, true).build());
+ new RouteDiscoveryPreference.Builder(routeTypes, true).build());
try {
task.run();
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
index 9ff9177c..ae15b91 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
@@ -31,7 +31,7 @@
import android.media.MediaRouter2.RouteCallback;
import android.media.MediaRouter2.SessionCallback;
import android.media.MediaRouter2Manager;
-import android.media.RouteDiscoveryRequest;
+import android.media.RouteDiscoveryPreference;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -76,9 +76,9 @@
SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id5_to_transfer_to";
public static final String ROUTE_NAME5 = "Sample Route 5 - Route to transfer to";
- public static final String ROUTE_ID_SPECIAL_TYPE =
- SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_special_type";
- public static final String ROUTE_NAME_SPECIAL_TYPE = "Special Type Route";
+ public static final String ROUTE_ID_SPECIAL_FEATURE =
+ SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_special_feature";
+ public static final String ROUTE_NAME_SPECIAL_FEATURE = "Special Feature Route";
public static final String SYSTEM_PROVIDER_ID =
"com.android.server.media/.SystemMediaRoute2Provider";
@@ -94,12 +94,12 @@
public static final String ACTION_REMOVE_ROUTE =
"com.android.mediarouteprovider.action_remove_route";
- public static final String TYPE_SAMPLE =
- "com.android.mediarouteprovider.TYPE_SAMPLE";
- public static final String TYPE_SPECIAL =
- "com.android.mediarouteprovider.TYPE_SPECIAL";
+ public static final String FEATURE_SAMPLE =
+ "com.android.mediarouteprovider.FEATURE_SAMPLE";
+ public static final String FEATURE_SPECIAL =
+ "com.android.mediarouteprovider.FEATURE_SPECIAL";
- private static final String TYPE_LIVE_AUDIO = "android.media.intent.route.TYPE_LIVE_AUDIO";
+ private static final String FEATURE_LIVE_AUDIO = "android.media.intent.route.LIVE_AUDIO";
private static final int TIMEOUT_MS = 5000;
@@ -113,18 +113,18 @@
private final List<RouteCallback> mRouteCallbacks = new ArrayList<>();
private final List<SessionCallback> mSessionCallbacks = new ArrayList<>();
- public static final List<String> TYPES_ALL = new ArrayList();
- public static final List<String> TYPES_SPECIAL = new ArrayList();
- private static final List<String> TYPES_LIVE_AUDIO = new ArrayList<>();
+ public static final List<String> FEATURES_ALL = new ArrayList();
+ public static final List<String> FEATURES_SPECIAL = new ArrayList();
+ private static final List<String> FEATURES_LIVE_AUDIO = new ArrayList<>();
static {
- TYPES_ALL.add(TYPE_SAMPLE);
- TYPES_ALL.add(TYPE_SPECIAL);
- TYPES_ALL.add(TYPE_LIVE_AUDIO);
+ FEATURES_ALL.add(FEATURE_SAMPLE);
+ FEATURES_ALL.add(FEATURE_SPECIAL);
+ FEATURES_ALL.add(FEATURE_LIVE_AUDIO);
- TYPES_SPECIAL.add(TYPE_SPECIAL);
+ FEATURES_SPECIAL.add(FEATURE_SPECIAL);
- TYPES_LIVE_AUDIO.add(TYPE_LIVE_AUDIO);
+ FEATURES_LIVE_AUDIO.add(FEATURE_LIVE_AUDIO);
}
@Before
@@ -181,7 +181,7 @@
@Test
public void testOnRoutesRemoved() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
addRouterCallback(new RouteCallback());
addManagerCallback(new MediaRouter2Manager.Callback() {
@@ -203,14 +203,14 @@
}
/**
- * Tests if we get proper routes for application that has special route type.
+ * Tests if we get proper routes for application that has special route feature.
*/
@Test
- public void testRouteType() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_SPECIAL);
+ public void testRouteFeatures() throws Exception {
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_SPECIAL);
assertEquals(1, routes.size());
- assertNotNull(routes.get(ROUTE_ID_SPECIAL_TYPE));
+ assertNotNull(routes.get(ROUTE_ID_SPECIAL_FEATURE));
}
/**
@@ -219,7 +219,7 @@
*/
@Test
public void testRouterOnSessionCreated() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
CountDownLatch latch = new CountDownLatch(1);
@@ -228,7 +228,7 @@
addRouterCallback(new MediaRouter2.RouteCallback());
addSessionCallback(new SessionCallback() {
@Override
- public void onSessionCreated(MediaRouter2.RouteSessionController controller) {
+ public void onSessionCreated(MediaRouter2.RoutingController controller) {
if (createRouteMap(controller.getSelectedRoutes()).containsKey(ROUTE_ID1)) {
latch.countDown();
}
@@ -255,7 +255,7 @@
@Ignore("TODO: test session created callback instead of onRouteSelected")
public void testManagerOnRouteSelected() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
addRouterCallback(new RouteCallback());
addManagerCallback(new MediaRouter2Manager.Callback() {
@@ -285,7 +285,7 @@
public void testGetActiveRoutes() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
addRouterCallback(new RouteCallback());
addManagerCallback(new MediaRouter2Manager.Callback() {
@Override
@@ -321,7 +321,7 @@
@Test
@Ignore("TODO: enable when session is released")
public void testSingleProviderSelect() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
addRouterCallback(new RouteCallback());
awaitOnRouteChangedManager(
@@ -346,7 +346,7 @@
@Test
public void testControlVolumeWithManager() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
int originalVolume = volRoute.getVolume();
@@ -365,7 +365,7 @@
@Test
public void testVolumeHandling() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
MediaRoute2Info fixedVolumeRoute = routes.get(ROUTE_ID_FIXED_VOLUME);
MediaRoute2Info variableVolumeRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
@@ -375,11 +375,11 @@
assertEquals(VOLUME_MAX, variableVolumeRoute.getVolumeMax());
}
- Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> routeTypes)
+ Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> routeFeatures)
throws Exception {
CountDownLatch latch = new CountDownLatch(2);
- // A dummy callback is required to send route type info.
+ // A dummy callback is required to send route feature info.
RouteCallback routeCallback = new RouteCallback();
MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() {
@Override
@@ -394,16 +394,17 @@
}
@Override
- public void onControlCategoriesChanged(String packageName, List<String> routeTypes) {
+ public void onControlCategoriesChanged(String packageName,
+ List<String> preferredFeatures) {
if (TextUtils.equals(mPackageName, packageName)
- && routeTypes.equals(routeTypes)) {
+ && preferredFeatures.equals(preferredFeatures)) {
latch.countDown();
}
}
};
mManager.registerCallback(mExecutor, managerCallback);
mRouter2.registerRouteCallback(mExecutor, routeCallback,
- new RouteDiscoveryRequest.Builder(routeTypes, true).build());
+ new RouteDiscoveryPreference.Builder(routeFeatures, true).build());
try {
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
return createRouteMap(mManager.getAvailableRoutes(mPackageName));
@@ -450,7 +451,7 @@
private void addRouterCallback(RouteCallback routeCallback) {
mRouteCallbacks.add(routeCallback);
- mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY);
+ mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
}
private void addSessionCallback(SessionCallback sessionCallback) {
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryRequestTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryPreferenceTest.java
similarity index 75%
rename from media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryRequestTest.java
rename to media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryPreferenceTest.java
index 60d131a..fa12935 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryRequestTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryPreferenceTest.java
@@ -19,7 +19,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import android.media.RouteDiscoveryRequest;
+import android.media.RouteDiscoveryPreference;
import android.os.Parcel;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -34,7 +34,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
-public class RouteDiscoveryRequestTest {
+public class RouteDiscoveryPreferenceTest {
@Before
public void setUp() throws Exception { }
@@ -46,10 +46,10 @@
List<String> testTypes = new ArrayList<>();
testTypes.add("TEST_TYPE_1");
testTypes.add("TEST_TYPE_2");
- RouteDiscoveryRequest request = new RouteDiscoveryRequest.Builder(testTypes, true)
+ RouteDiscoveryPreference request = new RouteDiscoveryPreference.Builder(testTypes, true)
.build();
- RouteDiscoveryRequest requestRebuilt = new RouteDiscoveryRequest.Builder(request)
+ RouteDiscoveryPreference requestRebuilt = new RouteDiscoveryPreference.Builder(request)
.build();
assertEquals(request, requestRebuilt);
@@ -57,7 +57,7 @@
Parcel parcel = Parcel.obtain();
parcel.writeParcelable(request, 0);
parcel.setDataPosition(0);
- RouteDiscoveryRequest requestFromParcel = parcel.readParcelable(null);
+ RouteDiscoveryPreference requestFromParcel = parcel.readParcelable(null);
assertEquals(request, requestFromParcel);
}
@@ -71,15 +71,15 @@
List<String> testTypes2 = new ArrayList<>();
testTypes.add("TEST_TYPE_3");
- RouteDiscoveryRequest request = new RouteDiscoveryRequest.Builder(testTypes, true)
+ RouteDiscoveryPreference request = new RouteDiscoveryPreference.Builder(testTypes, true)
.build();
- RouteDiscoveryRequest requestTypes = new RouteDiscoveryRequest.Builder(request)
- .setRouteTypes(testTypes2)
+ RouteDiscoveryPreference requestTypes = new RouteDiscoveryPreference.Builder(request)
+ .setPreferredFeatures(testTypes2)
.build();
assertNotEquals(request, requestTypes);
- RouteDiscoveryRequest requestActiveScan = new RouteDiscoveryRequest.Builder(request)
+ RouteDiscoveryPreference requestActiveScan = new RouteDiscoveryPreference.Builder(request)
.setActiveScan(false)
.build();
assertNotEquals(request, requestActiveScan);
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java
deleted file mode 100644
index 9971fc3..0000000
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.mediaroutertest;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.media.RouteSessionInfo;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class RouteSessionTest {
- private static final String TEST_SESSION_ID = "test_session_id";
- private static final String TEST_PACKAGE_NAME = "com.android.mediaroutertest";
- private static final String TEST_CONTROL_CATEGORY = "com.android.mediaroutertest.category";
-
- private static final String TEST_ROUTE_ID1 = "route_id1";
-
- @Test
- public void testValidity() {
- RouteSessionInfo emptyPackageSession = new RouteSessionInfo.Builder(TEST_SESSION_ID,
- "",
- TEST_CONTROL_CATEGORY)
- .addSelectedRoute(TEST_ROUTE_ID1)
- .build();
- RouteSessionInfo emptyCategorySession = new RouteSessionInfo.Builder(TEST_SESSION_ID,
- TEST_PACKAGE_NAME, "")
- .addSelectedRoute(TEST_ROUTE_ID1)
- .build();
-
- RouteSessionInfo emptySelectedRouteSession = new RouteSessionInfo.Builder(TEST_SESSION_ID,
- TEST_PACKAGE_NAME, TEST_CONTROL_CATEGORY)
- .build();
-
- RouteSessionInfo validSession = new RouteSessionInfo.Builder(emptySelectedRouteSession)
- .addSelectedRoute(TEST_ROUTE_ID1)
- .build();
-
- assertFalse(emptyPackageSession.isValid());
- assertFalse(emptyCategorySession.isValid());
- assertFalse(emptySelectedRouteSession.isValid());
- assertTrue(validSession.isValid());
- }
-}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java
new file mode 100644
index 0000000..3f59736
--- /dev/null
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java
@@ -0,0 +1,560 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mediaroutertest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertThrows;
+
+import android.media.RoutingSessionInfo;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests {@link RoutingSessionInfo} and its {@link RoutingSessionInfo.Builder builder}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RoutingSessionInfoTest {
+ public static final String TEST_ID = "test_id";
+ public static final String TEST_CLIENT_PACKAGE_NAME = "com.test.client.package.name";
+ public static final String TEST_ROUTE_FEATURE = "test_route_feature";
+
+ public static final String TEST_ROUTE_ID_0 = "test_route_type_0";
+ public static final String TEST_ROUTE_ID_1 = "test_route_type_1";
+ public static final String TEST_ROUTE_ID_2 = "test_route_type_2";
+ public static final String TEST_ROUTE_ID_3 = "test_route_type_3";
+ public static final String TEST_ROUTE_ID_4 = "test_route_type_4";
+ public static final String TEST_ROUTE_ID_5 = "test_route_type_5";
+ public static final String TEST_ROUTE_ID_6 = "test_route_type_6";
+ public static final String TEST_ROUTE_ID_7 = "test_route_type_7";
+
+ public static final String TEST_KEY = "test_key";
+ public static final String TEST_VALUE = "test_value";
+
+ @Test
+ public void testBuilderConstructorWithInvalidValues() {
+ final String nullId = null;
+ final String nullClientPackageName = null;
+ final String nullRouteFeature = null;
+
+ final String emptyId = "";
+ // Note: An empty string as client package name is valid.
+ final String emptyRouteFeature = "";
+
+ final String validId = TEST_ID;
+ final String validClientPackageName = TEST_CLIENT_PACKAGE_NAME;
+ final String validRouteFeature = TEST_ROUTE_FEATURE;
+
+ // ID is invalid
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ nullId, validClientPackageName, validRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ emptyId, validClientPackageName, validRouteFeature));
+
+ // client package name is invalid (null)
+ assertThrows(NullPointerException.class, () -> new RoutingSessionInfo.Builder(
+ validId, nullClientPackageName, validRouteFeature));
+
+ // route feature is invalid
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ validId, validClientPackageName, nullRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ validId, validClientPackageName, emptyRouteFeature));
+
+ // Two arguments are invalid - (1) ID and clientPackageName
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ nullId, nullClientPackageName, validRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ emptyId, nullClientPackageName, validRouteFeature));
+
+ // Two arguments are invalid - (2) ID and routeFeature
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ nullId, validClientPackageName, nullRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ nullId, validClientPackageName, emptyRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ emptyId, validClientPackageName, nullRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ emptyId, validClientPackageName, emptyRouteFeature));
+
+ // Two arguments are invalid - (3) clientPackageName and routeFeature
+ // Note that this throws NullPointerException.
+ assertThrows(NullPointerException.class, () -> new RoutingSessionInfo.Builder(
+ validId, nullClientPackageName, nullRouteFeature));
+ assertThrows(NullPointerException.class, () -> new RoutingSessionInfo.Builder(
+ validId, nullClientPackageName, emptyRouteFeature));
+
+ // All arguments are invalid
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ nullId, nullClientPackageName, nullRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ nullId, nullClientPackageName, emptyRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ emptyId, nullClientPackageName, nullRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ emptyId, nullClientPackageName, emptyRouteFeature));
+
+ // Null RouteInfo (1-argument constructor)
+ final RoutingSessionInfo nullRoutingSessionInfo = null;
+ assertThrows(NullPointerException.class,
+ () -> new RoutingSessionInfo.Builder(nullRoutingSessionInfo));
+ }
+
+ @Test
+ public void testBuilderConstructorWithEmptyClientPackageName() {
+ // An empty string for client package name is valid. (for unknown cases)
+ // Creating builder with it should not throw any exception.
+ RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
+ TEST_ID, "" /* clientPackageName*/, TEST_ROUTE_FEATURE);
+ }
+
+ @Test
+ public void testBuilderBuildWithEmptySelectedRoutesThrowsIAE() {
+ RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE);
+ // Note: Calling build() without adding any selected routes.
+ assertThrows(IllegalArgumentException.class, () -> builder.build());
+ }
+
+ @Test
+ public void testBuilderAddRouteMethodsWithIllegalArgumentsThrowsIAE() {
+ RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE);
+
+ final String nullRouteId = null;
+ final String emptyRouteId = "";
+
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addSelectedRoute(nullRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addSelectableRoute(nullRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addDeselectableRoute(nullRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addTransferrableRoute(nullRouteId));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addSelectedRoute(emptyRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addSelectableRoute(emptyRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addDeselectableRoute(emptyRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addTransferrableRoute(emptyRouteId));
+ }
+
+ @Test
+ public void testBuilderRemoveRouteMethodsWithIllegalArgumentsThrowsIAE() {
+ RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE);
+
+ final String nullRouteId = null;
+ final String emptyRouteId = "";
+
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.removeSelectedRoute(nullRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.removeSelectableRoute(nullRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.removeDeselectableRoute(nullRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.removeTransferrableRoute(nullRouteId));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.removeSelectedRoute(emptyRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.removeSelectableRoute(emptyRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.removeDeselectableRoute(emptyRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.removeTransferrableRoute(emptyRouteId));
+ }
+
+ @Test
+ public void testBuilderAndGettersOfRoutingSessionInfo() {
+ Bundle controlHints = new Bundle();
+ controlHints.putString(TEST_KEY, TEST_VALUE);
+
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .setControlHints(controlHints)
+ .build();
+
+ assertEquals(TEST_ID, sessionInfo.getId());
+ assertEquals(TEST_CLIENT_PACKAGE_NAME, sessionInfo.getClientPackageName());
+ assertEquals(TEST_ROUTE_FEATURE, sessionInfo.getRouteFeature());
+
+ assertEquals(2, sessionInfo.getSelectedRoutes().size());
+ assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0));
+ assertEquals(TEST_ROUTE_ID_1, sessionInfo.getSelectedRoutes().get(1));
+
+ assertEquals(2, sessionInfo.getSelectableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_2, sessionInfo.getSelectableRoutes().get(0));
+ assertEquals(TEST_ROUTE_ID_3, sessionInfo.getSelectableRoutes().get(1));
+
+ assertEquals(2, sessionInfo.getDeselectableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_4, sessionInfo.getDeselectableRoutes().get(0));
+ assertEquals(TEST_ROUTE_ID_5, sessionInfo.getDeselectableRoutes().get(1));
+
+ assertEquals(2, sessionInfo.getTransferrableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_6, sessionInfo.getTransferrableRoutes().get(0));
+ assertEquals(TEST_ROUTE_ID_7, sessionInfo.getTransferrableRoutes().get(1));
+
+ Bundle controlHintsOut = sessionInfo.getControlHints();
+ assertNotNull(controlHintsOut);
+ assertTrue(controlHintsOut.containsKey(TEST_KEY));
+ assertEquals(TEST_VALUE, controlHintsOut.getString(TEST_KEY));
+ }
+
+ @Test
+ public void testBuilderAddRouteMethodsWithBuilderCopyConstructor() {
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .build();
+
+ RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .build();
+
+ assertEquals(2, newSessionInfo.getSelectedRoutes().size());
+ assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0));
+ assertEquals(TEST_ROUTE_ID_1, newSessionInfo.getSelectedRoutes().get(1));
+
+ assertEquals(2, newSessionInfo.getSelectableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_2, newSessionInfo.getSelectableRoutes().get(0));
+ assertEquals(TEST_ROUTE_ID_3, newSessionInfo.getSelectableRoutes().get(1));
+
+ assertEquals(2, newSessionInfo.getDeselectableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_4, newSessionInfo.getDeselectableRoutes().get(0));
+ assertEquals(TEST_ROUTE_ID_5, newSessionInfo.getDeselectableRoutes().get(1));
+
+ assertEquals(2, newSessionInfo.getTransferrableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_6, newSessionInfo.getTransferrableRoutes().get(0));
+ assertEquals(TEST_ROUTE_ID_7, newSessionInfo.getTransferrableRoutes().get(1));
+ }
+
+ @Test
+ public void testBuilderRemoveRouteMethods() {
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .removeSelectedRoute(TEST_ROUTE_ID_1)
+
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .removeSelectableRoute(TEST_ROUTE_ID_3)
+
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .removeDeselectableRoute(TEST_ROUTE_ID_5)
+
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .removeTransferrableRoute(TEST_ROUTE_ID_7)
+
+ .build();
+
+ assertEquals(1, sessionInfo.getSelectedRoutes().size());
+ assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0));
+
+ assertEquals(1, sessionInfo.getSelectableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_2, sessionInfo.getSelectableRoutes().get(0));
+
+ assertEquals(1, sessionInfo.getDeselectableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_4, sessionInfo.getDeselectableRoutes().get(0));
+
+ assertEquals(1, sessionInfo.getTransferrableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_6, sessionInfo.getTransferrableRoutes().get(0));
+ }
+
+ @Test
+ public void testBuilderRemoveRouteMethodsWithBuilderCopyConstructor() {
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .build();
+
+ RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+ .removeSelectedRoute(TEST_ROUTE_ID_1)
+ .removeSelectableRoute(TEST_ROUTE_ID_3)
+ .removeDeselectableRoute(TEST_ROUTE_ID_5)
+ .removeTransferrableRoute(TEST_ROUTE_ID_7)
+ .build();
+
+ assertEquals(1, newSessionInfo.getSelectedRoutes().size());
+ assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0));
+
+ assertEquals(1, newSessionInfo.getSelectableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_2, newSessionInfo.getSelectableRoutes().get(0));
+
+ assertEquals(1, newSessionInfo.getDeselectableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_4, newSessionInfo.getDeselectableRoutes().get(0));
+
+ assertEquals(1, newSessionInfo.getTransferrableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_6, newSessionInfo.getTransferrableRoutes().get(0));
+ }
+
+ @Test
+ public void testBuilderClearRouteMethods() {
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .clearSelectedRoutes()
+
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .clearSelectableRoutes()
+
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .clearDeselectableRoutes()
+
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .clearTransferrableRoutes()
+
+ // SelectedRoutes must not be empty
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .build();
+
+ assertEquals(1, sessionInfo.getSelectedRoutes().size());
+ assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0));
+
+ assertTrue(sessionInfo.getSelectableRoutes().isEmpty());
+ assertTrue(sessionInfo.getDeselectableRoutes().isEmpty());
+ assertTrue(sessionInfo.getTransferrableRoutes().isEmpty());
+ }
+
+ @Test
+ public void testBuilderClearRouteMethodsWithBuilderCopyConstructor() {
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .build();
+
+ RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+ .clearSelectedRoutes()
+ .clearSelectableRoutes()
+ .clearDeselectableRoutes()
+ .clearTransferrableRoutes()
+ // SelectedRoutes must not be empty
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .build();
+
+ assertEquals(1, newSessionInfo.getSelectedRoutes().size());
+ assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0));
+
+ assertTrue(newSessionInfo.getSelectableRoutes().isEmpty());
+ assertTrue(newSessionInfo.getDeselectableRoutes().isEmpty());
+ assertTrue(newSessionInfo.getTransferrableRoutes().isEmpty());
+ }
+
+ @Test
+ public void testEqualsCreatedWithSameArguments() {
+ Bundle controlHints = new Bundle();
+ controlHints.putString(TEST_KEY, TEST_VALUE);
+
+ RoutingSessionInfo sessionInfo1 = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .setControlHints(controlHints)
+ .build();
+
+ RoutingSessionInfo sessionInfo2 = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .setControlHints(controlHints)
+ .build();
+
+ assertEquals(sessionInfo1, sessionInfo2);
+ assertEquals(sessionInfo1.hashCode(), sessionInfo2.hashCode());
+ }
+
+ @Test
+ public void testEqualsCreatedWithBuilderCopyConstructor() {
+ Bundle controlHints = new Bundle();
+ controlHints.putString(TEST_KEY, TEST_VALUE);
+
+ RoutingSessionInfo sessionInfo1 = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .setControlHints(controlHints)
+ .build();
+
+ RoutingSessionInfo sessionInfo2 = new RoutingSessionInfo.Builder(sessionInfo1).build();
+
+ assertEquals(sessionInfo1, sessionInfo2);
+ assertEquals(sessionInfo1.hashCode(), sessionInfo2.hashCode());
+ }
+
+ @Test
+ public void testEqualsReturnFalse() {
+ Bundle controlHints = new Bundle();
+ controlHints.putString(TEST_KEY, TEST_VALUE);
+
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .setControlHints(controlHints)
+ .build();
+
+ // Now, we will use copy constructor
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .addSelectedRoute("randomRoute")
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .addSelectableRoute("randomRoute")
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .addDeselectableRoute("randomRoute")
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .addTransferrableRoute("randomRoute")
+ .build());
+
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .removeSelectedRoute(TEST_ROUTE_ID_1)
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .removeSelectableRoute(TEST_ROUTE_ID_3)
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .removeDeselectableRoute(TEST_ROUTE_ID_5)
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .removeTransferrableRoute(TEST_ROUTE_ID_7)
+ .build());
+
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .clearSelectedRoutes()
+ // Note: Calling build() with empty selected routes will throw IAE.
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .clearSelectableRoutes()
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .clearDeselectableRoutes()
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .clearTransferrableRoutes()
+ .build());
+
+ // Note: ControlHints will not affect the equals.
+ }
+
+ @Test
+ public void testParcelingAndUnParceling() {
+ Bundle controlHints = new Bundle();
+ controlHints.putString(TEST_KEY, TEST_VALUE);
+
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .setControlHints(controlHints)
+ .build();
+
+ Parcel parcel = Parcel.obtain();
+ sessionInfo.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ RoutingSessionInfo sessionInfoFromParcel =
+ RoutingSessionInfo.CREATOR.createFromParcel(parcel);
+ assertEquals(sessionInfo, sessionInfoFromParcel);
+ assertEquals(sessionInfo.hashCode(), sessionInfoFromParcel.hashCode());
+
+ // Check controlHints
+ Bundle controlHintsOut = sessionInfoFromParcel.getControlHints();
+ assertNotNull(controlHintsOut);
+ assertTrue(controlHintsOut.containsKey(TEST_KEY));
+ assertEquals(TEST_VALUE, controlHintsOut.getString(TEST_KEY));
+ }
+}
diff --git a/mime/java-res/android.mime.types b/mime/java-res/android.mime.types
index cb04d92..c1f8b3f 100644
--- a/mime/java-res/android.mime.types
+++ b/mime/java-res/android.mime.types
@@ -46,6 +46,7 @@
# <extension1> and <extension2> are mapped (or remapped) to <mimeType>.
?application/epub+zip epub
+?application/lrc lrc
?application/pkix-cert cer
?application/rss+xml rss
?application/sdp sdp
@@ -65,6 +66,7 @@
?application/x-mpegurl m3u m3u8
?application/x-pem-file pem
?application/x-pkcs12 p12 pfx
+?application/x-subrip srt
?application/x-webarchive webarchive
?application/x-webarchive-xml webarchivexml
?application/x-x509-server-cert crt
diff --git a/mms/java/android/telephony/MmsManager.java b/mms/java/android/telephony/MmsManager.java
index 6554267..cf55eba 100644
--- a/mms/java/android/telephony/MmsManager.java
+++ b/mms/java/android/telephony/MmsManager.java
@@ -16,8 +16,12 @@
package android.telephony;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemService;
import android.app.ActivityThread;
import android.app.PendingIntent;
+import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
@@ -27,22 +31,18 @@
/**
* Manages MMS operations such as sending multimedia messages.
- * Get this object by calling the static method {@link #getInstance()}.
- * @hide
+ * Get this object by calling Context#getSystemService(Context#MMS_SERVICE).
*/
+@SystemService(Context.MMS_SERVICE)
public class MmsManager {
private static final String TAG = "MmsManager";
-
- /** Singleton object constructed during class initialization. */
- private static final MmsManager sInstance = new MmsManager();
+ private final Context mContext;
/**
- * Get the MmsManager singleton instance.
- *
- * @return the {@link MmsManager} singleton instance.
+ * @hide
*/
- public static MmsManager getInstance() {
- return sInstance;
+ public MmsManager(@NonNull Context context) {
+ mContext = context;
}
/**
@@ -56,8 +56,9 @@
* @param sentIntent if not NULL this <code>PendingIntent</code> is broadcast when the message
* is successfully sent, or failed
*/
- public void sendMultimediaMessage(int subId, Uri contentUri, String locationUrl,
- Bundle configOverrides, PendingIntent sentIntent) {
+ public void sendMultimediaMessage(int subId, @NonNull Uri contentUri,
+ @Nullable String locationUrl, @Nullable Bundle configOverrides,
+ @Nullable PendingIntent sentIntent) {
try {
final IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
if (iMms == null) {
@@ -84,8 +85,9 @@
* broadcast when the message is downloaded, or the download is failed
* @throws IllegalArgumentException if locationUrl or contentUri is empty
*/
- public void downloadMultimediaMessage(int subId, String locationUrl, Uri contentUri,
- Bundle configOverrides, PendingIntent downloadedIntent) {
+ public void downloadMultimediaMessage(int subId, @NonNull String locationUrl,
+ @NonNull Uri contentUri, @Nullable Bundle configOverrides,
+ @Nullable PendingIntent downloadedIntent) {
try {
final IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
if (iMms == null) {
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 942eafd..376ea77 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -24,12 +24,21 @@
// our source files
//
- srcs: ["bitmap.cpp"],
+ srcs: [
+ "aassetstreamadaptor.cpp",
+ "bitmap.cpp",
+ "imagedecoder.cpp",
+ ],
shared_libs: [
+ "libandroid",
"libandroid_runtime",
+ "libhwui",
+ "liblog",
],
+ static_libs: ["libarect"],
+
arch: {
arm: {
// TODO: This is to work around b/24465209. Remove after root cause is fixed
diff --git a/native/graphics/jni/aassetstreamadaptor.cpp b/native/graphics/jni/aassetstreamadaptor.cpp
new file mode 100644
index 0000000..016008b
--- /dev/null
+++ b/native/graphics/jni/aassetstreamadaptor.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "aassetstreamadaptor.h"
+
+#include <log/log.h>
+
+AAssetStreamAdaptor::AAssetStreamAdaptor(AAsset* asset)
+ : mAAsset(asset)
+{
+}
+
+bool AAssetStreamAdaptor::rewind() {
+ off64_t pos = AAsset_seek64(mAAsset, 0, SEEK_SET);
+ if (pos == (off64_t)-1) {
+ ALOGE("rewind failed!");
+ return false;
+ }
+ return true;
+}
+
+size_t AAssetStreamAdaptor::getLength() const {
+ return AAsset_getLength64(mAAsset);
+}
+
+bool AAssetStreamAdaptor::isAtEnd() const {
+ return AAsset_getRemainingLength64(mAAsset) == 0;
+}
+
+SkStreamRewindable* AAssetStreamAdaptor::onDuplicate() const {
+ // Cannot sensibly create a duplicate, since each AAssetStreamAdaptor
+ // would be modifying the same AAsset.
+ //return new AAssetStreamAdaptor(mAAsset);
+ return nullptr;
+}
+
+bool AAssetStreamAdaptor::hasPosition() const {
+ return AAsset_seek64(mAAsset, 0, SEEK_CUR) != -1;
+}
+
+size_t AAssetStreamAdaptor::getPosition() const {
+ const off64_t offset = AAsset_seek64(mAAsset, 0, SEEK_CUR);
+ if (offset == -1) {
+ ALOGE("getPosition failed!");
+ return 0;
+ }
+
+ return offset;
+}
+
+bool AAssetStreamAdaptor::seek(size_t position) {
+ if (AAsset_seek64(mAAsset, position, SEEK_SET) == -1) {
+ ALOGE("seek failed!");
+ return false;
+ }
+
+ return true;
+}
+
+bool AAssetStreamAdaptor::move(long offset) {
+ if (AAsset_seek64(mAAsset, offset, SEEK_CUR) == -1) {
+ ALOGE("move failed!");
+ return false;
+ }
+
+ return true;
+}
+
+size_t AAssetStreamAdaptor::read(void* buffer, size_t size) {
+ ssize_t amount;
+
+ if (!buffer) {
+ if (!size) {
+ return 0;
+ }
+
+ // asset->seek returns new total offset
+ // we want to return amount that was skipped
+ const off64_t oldOffset = AAsset_seek64(mAAsset, 0, SEEK_CUR);
+ if (oldOffset == -1) {
+ ALOGE("seek(oldOffset) failed!");
+ return 0;
+ }
+
+ const off64_t newOffset = AAsset_seek64(mAAsset, size, SEEK_CUR);
+ if (-1 == newOffset) {
+ ALOGE("seek(%zu) failed!", size);
+ return 0;
+ }
+ amount = newOffset - oldOffset;
+ } else {
+ amount = AAsset_read(mAAsset, buffer, size);
+ }
+
+ if (amount < 0) {
+ amount = 0;
+ }
+ return amount;
+}
+
+const void* AAssetStreamAdaptor::getMemoryBase() {
+ return AAsset_getBuffer(mAAsset);
+}
+
diff --git a/native/graphics/jni/aassetstreamadaptor.h b/native/graphics/jni/aassetstreamadaptor.h
new file mode 100644
index 0000000..c1fddb0
--- /dev/null
+++ b/native/graphics/jni/aassetstreamadaptor.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "SkStream.h"
+
+#include <android/asset_manager.h>
+
+// Like AssetStreamAdaptor, but operates on AAsset, a public NDK API.
+class AAssetStreamAdaptor : public SkStreamRewindable {
+public:
+ /**
+ * Create an SkStream that reads from an AAsset.
+ *
+ * The AAsset must remain open for the lifetime of the Adaptor. The Adaptor
+ * does *not* close the AAsset.
+ */
+ explicit AAssetStreamAdaptor(AAsset*);
+
+ bool rewind() override;
+ size_t read(void* buffer, size_t size) override;
+ bool hasLength() const override { return true; }
+ size_t getLength() const override;
+ bool hasPosition() const override;
+ size_t getPosition() const override;
+ bool seek(size_t position) override;
+ bool move(long offset) override;
+ bool isAtEnd() const override;
+ const void* getMemoryBase() override;
+protected:
+ SkStreamRewindable* onDuplicate() const override;
+
+private:
+ AAsset* mAAsset;
+};
+
diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp
new file mode 100644
index 0000000..2ef203d
--- /dev/null
+++ b/native/graphics/jni/imagedecoder.cpp
@@ -0,0 +1,320 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "aassetstreamadaptor.h"
+
+#include <android/asset_manager.h>
+#include <android/bitmap.h>
+#include <android/imagedecoder.h>
+#include <android/graphics/MimeType.h>
+#include <android/rect.h>
+#include <hwui/ImageDecoder.h>
+#include <log/log.h>
+#include <SkAndroidCodec.h>
+
+#include <fcntl.h>
+#include <optional>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+using namespace android;
+
+int ResultToErrorCode(SkCodec::Result result) {
+ switch (result) {
+ case SkCodec::kIncompleteInput:
+ return ANDROID_IMAGE_DECODER_INCOMPLETE;
+ case SkCodec::kErrorInInput:
+ return ANDROID_IMAGE_DECODER_ERROR;
+ case SkCodec::kInvalidInput:
+ return ANDROID_IMAGE_DECODER_INVALID_INPUT;
+ case SkCodec::kCouldNotRewind:
+ return ANDROID_IMAGE_DECODER_SEEK_ERROR;
+ case SkCodec::kUnimplemented:
+ return ANDROID_IMAGE_DECODER_UNSUPPORTED_FORMAT;
+ case SkCodec::kInvalidConversion:
+ return ANDROID_IMAGE_DECODER_INVALID_CONVERSION;
+ case SkCodec::kInvalidParameters:
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ case SkCodec::kSuccess:
+ return ANDROID_IMAGE_DECODER_SUCCESS;
+ case SkCodec::kInvalidScale:
+ return ANDROID_IMAGE_DECODER_INVALID_SCALE;
+ case SkCodec::kInternalError:
+ return ANDROID_IMAGE_DECODER_INTERNAL_ERROR;
+ }
+}
+
+static int createFromStream(std::unique_ptr<SkStreamRewindable> stream, AImageDecoder** outDecoder) {
+ SkCodec::Result result;
+ auto codec = SkCodec::MakeFromStream(std::move(stream), &result, nullptr,
+ SkCodec::SelectionPolicy::kPreferAnimation);
+ auto androidCodec = SkAndroidCodec::MakeFromCodec(std::move(codec),
+ SkAndroidCodec::ExifOrientationBehavior::kRespect);
+ if (!androidCodec) {
+ return ResultToErrorCode(result);
+ }
+
+ *outDecoder = reinterpret_cast<AImageDecoder*>(new ImageDecoder(std::move(androidCodec)));
+ return ANDROID_IMAGE_DECODER_SUCCESS;
+}
+
+int AImageDecoder_createFromAAsset(AAsset* asset, AImageDecoder** outDecoder) {
+ if (!asset || !outDecoder) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+ *outDecoder = nullptr;
+
+ auto stream = std::make_unique<AAssetStreamAdaptor>(asset);
+ return createFromStream(std::move(stream), outDecoder);
+}
+
+static bool isSeekable(int descriptor) {
+ return ::lseek64(descriptor, 0, SEEK_CUR) != -1;
+}
+
+int AImageDecoder_createFromFd(int fd, AImageDecoder** outDecoder) {
+ if (fd <= 0 || !outDecoder) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ struct stat fdStat;
+ if (fstat(fd, &fdStat) == -1) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ if (!isSeekable(fd)) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ // SkFILEStream will close its descriptor. Duplicate it so the client will
+ // still be responsible for closing the original.
+ int dupDescriptor = fcntl(fd, F_DUPFD_CLOEXEC, 0);
+ FILE* file = fdopen(dupDescriptor, "r");
+ if (!file) {
+ close(dupDescriptor);
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ auto stream = std::unique_ptr<SkStreamRewindable>(new SkFILEStream(file));
+ return createFromStream(std::move(stream), outDecoder);
+}
+
+int AImageDecoder_createFromBuffer(const void* buffer, size_t length,
+ AImageDecoder** outDecoder) {
+ if (!buffer || !length || !outDecoder) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+ *outDecoder = nullptr;
+
+ // The client is expected to keep the buffer alive as long as the
+ // AImageDecoder, so we do not need to copy the buffer.
+ auto stream = std::unique_ptr<SkStreamRewindable>(
+ new SkMemoryStream(buffer, length, false /* copyData */));
+ return createFromStream(std::move(stream), outDecoder);
+}
+
+static ImageDecoder* toDecoder(AImageDecoder* d) {
+ return reinterpret_cast<ImageDecoder*>(d);
+}
+
+// Note: This differs from the version in android_bitmap.cpp in that this
+// version returns kGray_8_SkColorType for ANDROID_BITMAP_FORMAT_A_8. SkCodec
+// allows decoding single channel images to gray, which Android then treats
+// as A_8/ALPHA_8.
+static SkColorType getColorType(AndroidBitmapFormat format) {
+ switch (format) {
+ case ANDROID_BITMAP_FORMAT_RGBA_8888:
+ return kN32_SkColorType;
+ case ANDROID_BITMAP_FORMAT_RGB_565:
+ return kRGB_565_SkColorType;
+ case ANDROID_BITMAP_FORMAT_RGBA_4444:
+ return kARGB_4444_SkColorType;
+ case ANDROID_BITMAP_FORMAT_A_8:
+ return kGray_8_SkColorType;
+ case ANDROID_BITMAP_FORMAT_RGBA_F16:
+ return kRGBA_F16_SkColorType;
+ default:
+ return kUnknown_SkColorType;
+ }
+}
+
+int AImageDecoder_setAndroidBitmapFormat(AImageDecoder* decoder, int32_t format) {
+ if (!decoder || format < ANDROID_BITMAP_FORMAT_NONE
+ || format > ANDROID_BITMAP_FORMAT_RGBA_F16) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+ return toDecoder(decoder)->setOutColorType(getColorType((AndroidBitmapFormat) format))
+ ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_CONVERSION;
+}
+
+const AImageDecoderHeaderInfo* AImageDecoder_getHeaderInfo(const AImageDecoder* decoder) {
+ return reinterpret_cast<const AImageDecoderHeaderInfo*>(decoder);
+}
+
+static const ImageDecoder* toDecoder(const AImageDecoderHeaderInfo* info) {
+ return reinterpret_cast<const ImageDecoder*>(info);
+}
+
+int32_t AImageDecoderHeaderInfo_getWidth(const AImageDecoderHeaderInfo* info) {
+ if (!info) {
+ return 0;
+ }
+ return toDecoder(info)->mCodec->getInfo().width();
+}
+
+int32_t AImageDecoderHeaderInfo_getHeight(const AImageDecoderHeaderInfo* info) {
+ if (!info) {
+ return 0;
+ }
+ return toDecoder(info)->mCodec->getInfo().height();
+}
+
+const char* AImageDecoderHeaderInfo_getMimeType(const AImageDecoderHeaderInfo* info) {
+ if (!info) {
+ return nullptr;
+ }
+ return getMimeType(toDecoder(info)->mCodec->getEncodedFormat());
+}
+
+bool AImageDecoderHeaderInfo_isAnimated(const AImageDecoderHeaderInfo* info) {
+ if (!info) {
+ return false;
+ }
+ return toDecoder(info)->mCodec->codec()->getFrameCount() > 1;
+}
+
+// FIXME: Share with getFormat in android_bitmap.cpp?
+static AndroidBitmapFormat getFormat(SkColorType colorType) {
+ switch (colorType) {
+ case kN32_SkColorType:
+ return ANDROID_BITMAP_FORMAT_RGBA_8888;
+ case kRGB_565_SkColorType:
+ return ANDROID_BITMAP_FORMAT_RGB_565;
+ case kARGB_4444_SkColorType:
+ return ANDROID_BITMAP_FORMAT_RGBA_4444;
+ case kAlpha_8_SkColorType:
+ return ANDROID_BITMAP_FORMAT_A_8;
+ case kRGBA_F16_SkColorType:
+ return ANDROID_BITMAP_FORMAT_RGBA_F16;
+ default:
+ return ANDROID_BITMAP_FORMAT_NONE;
+ }
+}
+
+AndroidBitmapFormat AImageDecoderHeaderInfo_getAndroidBitmapFormat(
+ const AImageDecoderHeaderInfo* info) {
+ if (!info) {
+ return ANDROID_BITMAP_FORMAT_NONE;
+ }
+ return getFormat(toDecoder(info)->mCodec->computeOutputColorType(kN32_SkColorType));
+}
+
+int AImageDecoderHeaderInfo_getAlphaFlags(const AImageDecoderHeaderInfo* info) {
+ if (!info) {
+ // FIXME: Better invalid?
+ return -1;
+ }
+ switch (toDecoder(info)->mCodec->getInfo().alphaType()) {
+ case kUnknown_SkAlphaType:
+ LOG_ALWAYS_FATAL("Invalid alpha type");
+ return -1;
+ case kUnpremul_SkAlphaType:
+ // fall through. premul is the default.
+ case kPremul_SkAlphaType:
+ return ANDROID_BITMAP_FLAGS_ALPHA_PREMUL;
+ case kOpaque_SkAlphaType:
+ return ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE;
+ }
+}
+
+SkAlphaType toAlphaType(int androidBitmapFlags) {
+ switch (androidBitmapFlags) {
+ case ANDROID_BITMAP_FLAGS_ALPHA_PREMUL:
+ return kPremul_SkAlphaType;
+ case ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL:
+ return kUnpremul_SkAlphaType;
+ case ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE:
+ return kOpaque_SkAlphaType;
+ default:
+ return kUnknown_SkAlphaType;
+ }
+}
+
+int AImageDecoder_setAlphaFlags(AImageDecoder* decoder, int alphaFlag) {
+ if (!decoder || alphaFlag < ANDROID_BITMAP_FLAGS_ALPHA_PREMUL
+ || alphaFlag > ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ return toDecoder(decoder)->setOutAlphaType(toAlphaType(alphaFlag))
+ ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_CONVERSION;
+}
+
+int AImageDecoder_setTargetSize(AImageDecoder* decoder, int width, int height) {
+ if (!decoder) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ return toDecoder(decoder)->setTargetSize(width, height)
+ ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_SCALE;
+}
+
+int AImageDecoder_setCrop(AImageDecoder* decoder, ARect crop) {
+ if (!decoder) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ SkIRect cropIRect;
+ cropIRect.setLTRB(crop.left, crop.top, crop.right, crop.bottom);
+ SkIRect* cropPtr = cropIRect == SkIRect::MakeEmpty() ? nullptr : &cropIRect;
+ return toDecoder(decoder)->setCropRect(cropPtr)
+ ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+}
+
+
+size_t AImageDecoder_getMinimumStride(AImageDecoder* decoder) {
+ if (!decoder) {
+ return 0;
+ }
+
+ SkImageInfo info = toDecoder(decoder)->getOutputInfo();
+ return info.minRowBytes();
+}
+
+int AImageDecoder_decodeImage(AImageDecoder* decoder,
+ void* pixels, size_t stride,
+ size_t size) {
+ if (!decoder || !pixels || !stride) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ ImageDecoder* imageDecoder = toDecoder(decoder);
+
+ const int height = imageDecoder->getOutputInfo().height();
+ const size_t minStride = AImageDecoder_getMinimumStride(decoder);
+ // If this calculation were to overflow, it would have been caught in
+ // setTargetSize.
+ if (stride < minStride || size < stride * (height - 1) + minStride) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ return ResultToErrorCode(imageDecoder->decode(pixels, stride));
+}
+
+void AImageDecoder_delete(AImageDecoder* decoder) {
+ delete toDecoder(decoder);
+}
diff --git a/native/graphics/jni/libjnigraphics.map.txt b/native/graphics/jni/libjnigraphics.map.txt
index a601d8a..bdd7f63 100644
--- a/native/graphics/jni/libjnigraphics.map.txt
+++ b/native/graphics/jni/libjnigraphics.map.txt
@@ -1,5 +1,22 @@
LIBJNIGRAPHICS {
global:
+ AImageDecoder_createFromAAsset;
+ AImageDecoder_createFromFd;
+ AImageDecoder_createFromBuffer;
+ AImageDecoder_delete;
+ AImageDecoder_setAndroidBitmapFormat;
+ AImageDecoder_setAlphaFlags;
+ AImageDecoder_getHeaderInfo;
+ AImageDecoder_getMinimumStride;
+ AImageDecoder_decodeImage;
+ AImageDecoder_setTargetSize;
+ AImageDecoder_setCrop;
+ AImageDecoderHeaderInfo_getWidth;
+ AImageDecoderHeaderInfo_getHeight;
+ AImageDecoderHeaderInfo_getMimeType;
+ AImageDecoderHeaderInfo_getAlphaFlags;
+ AImageDecoderHeaderInfo_isAnimated;
+ AImageDecoderHeaderInfo_getAndroidBitmapFormat;
AndroidBitmap_getInfo;
AndroidBitmap_lockPixels;
AndroidBitmap_unlockPixels;
diff --git a/packages/CarSystemUI/res/layout/sysui_primary_window.xml b/packages/CarSystemUI/res/layout/sysui_primary_window.xml
new file mode 100644
index 0000000..309d9ef
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/sysui_primary_window.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:background="@android:color/transparent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java b/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java
new file mode 100644
index 0000000..e3e9ab7
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car;
+
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.os.Binder;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Controls the expansion state of the primary window which will contain all of the fullscreen sysui
+ * behavior. This window still has a collapsed state in order to watch for swipe events to expand
+ * this window for the notification panel.
+ */
+@Singleton
+public class SystemUIPrimaryWindowController implements
+ ConfigurationController.ConfigurationListener {
+
+ private final Context mContext;
+ private final Resources mResources;
+ private final WindowManager mWindowManager;
+
+ private final int mStatusBarHeight;
+ private final int mNavBarHeight;
+ private final int mDisplayHeight;
+ private ViewGroup mBaseLayout;
+ private WindowManager.LayoutParams mLp;
+ private WindowManager.LayoutParams mLpChanged;
+
+ @Inject
+ public SystemUIPrimaryWindowController(
+ Context context,
+ @Main Resources resources,
+ WindowManager windowManager,
+ ConfigurationController configurationController
+ ) {
+ mContext = context;
+ mResources = resources;
+ mWindowManager = windowManager;
+
+ Point display = new Point();
+ mWindowManager.getDefaultDisplay().getSize(display);
+ mDisplayHeight = display.y;
+
+ mStatusBarHeight = mResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_height);
+ mNavBarHeight = mResources.getDimensionPixelSize(R.dimen.navigation_bar_height);
+
+ mLpChanged = new WindowManager.LayoutParams();
+ mBaseLayout = (ViewGroup) LayoutInflater.from(context)
+ .inflate(R.layout.sysui_primary_window, /* root= */ null, false);
+
+ configurationController.addCallback(this);
+ }
+
+ /** Returns the base view of the primary window. */
+ public ViewGroup getBaseLayout() {
+ return mBaseLayout;
+ }
+
+ /** Attaches the window to the window manager. */
+ public void attach() {
+ // Now that the status bar window encompasses the sliding panel and its
+ // translucent backdrop, the entire thing is made TRANSLUCENT and is
+ // hardware-accelerated.
+ mLp = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ mStatusBarHeight,
+ WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
+ PixelFormat.TRANSLUCENT);
+ mLp.token = new Binder();
+ mLp.gravity = Gravity.TOP;
+ mLp.setFitWindowInsetsTypes(/* types= */ 0);
+ mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+ mLp.setTitle("NotificationShade");
+ mLp.packageName = mContext.getPackageName();
+ mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+ mWindowManager.addView(mBaseLayout, mLp);
+ mLpChanged.copyFrom(mLp);
+ }
+
+ /** Sets the window to the expanded state. */
+ public void setWindowExpanded(boolean expanded) {
+ if (expanded) {
+ // TODO: Update this so that the windowing type gets the full height of the display
+ // when we use MATCH_PARENT.
+ mLpChanged.height = mDisplayHeight + mNavBarHeight;
+ } else {
+ mLpChanged.height = mStatusBarHeight;
+ }
+ updateWindow();
+ }
+
+ /** Returns {@code true} if the window is expanded */
+ public boolean isWindowExpanded() {
+ return mLp.height != mStatusBarHeight;
+ }
+
+ private void updateWindow() {
+ if (mLp != null && mLp.copyFrom(mLpChanged) != 0) {
+ mWindowManager.updateViewLayout(mBaseLayout, mLp);
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
index 69bd0ed..ff00fb3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
@@ -16,8 +16,6 @@
package com.android.settingslib;
-import static android.content.Context.TELEPHONY_SERVICE;
-
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -172,36 +170,38 @@
}
}
- public static String getFormattedPhoneNumber(Context context, SubscriptionInfo subscriptionInfo) {
+ /**
+ * Format a phone number.
+ * @param subscriptionInfo {@link SubscriptionInfo} subscription information.
+ * @return Returns formatted phone number.
+ */
+ public static String getFormattedPhoneNumber(Context context,
+ SubscriptionInfo subscriptionInfo) {
String formattedNumber = null;
if (subscriptionInfo != null) {
- final TelephonyManager telephonyManager =
- (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE);
- final String rawNumber =
- telephonyManager.getLine1Number(subscriptionInfo.getSubscriptionId());
+ final TelephonyManager telephonyManager = context.getSystemService(
+ TelephonyManager.class);
+ final String rawNumber = telephonyManager.createForSubscriptionId(
+ subscriptionInfo.getSubscriptionId()).getLine1Number();
if (!TextUtils.isEmpty(rawNumber)) {
formattedNumber = PhoneNumberUtils.formatNumber(rawNumber);
}
-
}
return formattedNumber;
}
public static String getFormattedPhoneNumbers(Context context,
- List<SubscriptionInfo> subscriptionInfo) {
+ List<SubscriptionInfo> subscriptionInfoList) {
StringBuilder sb = new StringBuilder();
- if (subscriptionInfo != null) {
- final TelephonyManager telephonyManager =
- (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE);
- final int count = subscriptionInfo.size();
- for (int i = 0; i < count; i++) {
- final String rawNumber = telephonyManager.getLine1Number(
- subscriptionInfo.get(i).getSubscriptionId());
+ if (subscriptionInfoList != null) {
+ final TelephonyManager telephonyManager = context.getSystemService(
+ TelephonyManager.class);
+ final int count = subscriptionInfoList.size();
+ for (SubscriptionInfo subscriptionInfo : subscriptionInfoList) {
+ final String rawNumber = telephonyManager.createForSubscriptionId(
+ subscriptionInfo.getSubscriptionId()).getLine1Number();
if (!TextUtils.isEmpty(rawNumber)) {
- sb.append(PhoneNumberUtils.formatNumber(rawNumber));
- if (i < count - 1) {
- sb.append("\n");
- }
+ sb.append(PhoneNumberUtils.formatNumber(rawNumber)).append("\n");
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
index ebca962..853c77e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
@@ -38,7 +38,7 @@
final SubscriptionManager subscriptionManager = context.getSystemService(
SubscriptionManager.class);
final NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll(
- telephonyManager.getSubscriberId(subId));
+ telephonyManager.createForSubscriptionId(subId).getSubscriberId());
if (!subscriptionManager.isActiveSubId(subId)) {
Log.i(TAG, "Subscription is not active: " + subId);
diff --git a/packages/SystemUI/res/layout-land/global_actions_grid_item.xml b/packages/SystemUI/res/layout-land/global_actions_grid_item.xml
index bc12338..0f9deaa 100644
--- a/packages/SystemUI/res/layout-land/global_actions_grid_item.xml
+++ b/packages/SystemUI/res/layout-land/global_actions_grid_item.xml
@@ -57,15 +57,5 @@
android:textColor="@color/global_actions_text"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
-
- <TextView
- android:visibility="gone"
- android:id="@*android:id/status"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:textColor="@color/global_actions_text"
- android:textAppearance="?android:attr/textAppearanceSmall"
- />
</LinearLayout>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/global_actions_grid_item.xml b/packages/SystemUI/res/layout/global_actions_grid_item.xml
index 4404c874..31c7cbf 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_item.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_item.xml
@@ -56,15 +56,5 @@
android:textColor="@color/global_actions_text"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
-
- <TextView
- android:visibility="gone"
- android:id="@*android:id/status"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:textColor="@color/global_actions_text"
- android:textAppearance="?android:attr/textAppearanceSmall"
- />
</LinearLayout>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml
index 6ac9da4..995cb7d 100644
--- a/packages/SystemUI/res/layout/global_screenshot.xml
+++ b/packages/SystemUI/res/layout/global_screenshot.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
+<!-- Copyright (C) 2020 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/packages/SystemUI/res/layout/global_screenshot_legacy.xml b/packages/SystemUI/res/layout/global_screenshot_legacy.xml
new file mode 100644
index 0000000..791c7ea
--- /dev/null
+++ b/packages/SystemUI/res/layout/global_screenshot_legacy.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <ImageView android:id="@+id/global_screenshot_legacy_background"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:src="@android:color/black"
+ android:visibility="gone" />
+ <ImageView android:id="@+id/global_screenshot_legacy"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:background="@drawable/screenshot_panel"
+ android:visibility="gone"
+ android:adjustViewBounds="true" />
+ <ImageView android:id="@+id/global_screenshot_legacy_flash"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:src="@android:color/white"
+ android:visibility="gone" />
+ <com.android.systemui.screenshot.ScreenshotSelectorView
+ android:id="@+id/global_screenshot_legacy_selector"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ android:pointerIcon="crosshair"/>
+</FrameLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index da0323a..5a1151e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -288,6 +288,7 @@
<!-- Dimensions related to screenshots -->
<!-- The padding on the global screenshot background image -->
+ <dimen name="global_screenshot_legacy_bg_padding">20dp</dimen>
<dimen name="global_screenshot_bg_padding">20dp</dimen>
<dimen name="screenshot_action_container_corner_radius">10dp</dimen>
<dimen name="screenshot_action_container_padding">20dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index b083123..23d6458 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -18,7 +18,6 @@
import android.app.AppOpsManager;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
@@ -64,7 +63,6 @@
private H mBGHandler;
private final List<AppOpsController.Callback> mCallbacks = new ArrayList<>();
private final ArrayMap<Integer, Set<Callback>> mCallbacksByCode = new ArrayMap<>();
- private final PermissionFlagsCache mFlagsCache;
private boolean mListening;
@GuardedBy("mActiveItems")
@@ -81,17 +79,10 @@
};
@Inject
- public AppOpsControllerImpl(Context context, @Background Looper bgLooper,
- DumpController dumpController) {
- this(context, bgLooper, new PermissionFlagsCache(context), dumpController);
- }
-
- @VisibleForTesting
- protected AppOpsControllerImpl(Context context, Looper bgLooper, PermissionFlagsCache cache,
- DumpController dumpController) {
+ public AppOpsControllerImpl(Context context,
+ @Background Looper bgLooper, DumpController dumpController) {
mContext = context;
mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
- mFlagsCache = cache;
mBGHandler = new H(bgLooper);
final int numOps = OPS.length;
for (int i = 0; i < numOps; i++) {
@@ -209,7 +200,14 @@
mNotedItems.remove(item);
if (DEBUG) Log.w(TAG, "Removed item: " + item.toString());
}
- notifySuscribers(code, uid, packageName, false);
+ boolean active;
+ // Check if the item is also active
+ synchronized (mActiveItems) {
+ active = getAppOpItemLocked(mActiveItems, code, uid, packageName) != null;
+ }
+ if (!active) {
+ notifySuscribers(code, uid, packageName, false);
+ }
}
private boolean addNoted(int code, int uid, String packageName) {
@@ -224,64 +222,13 @@
createdNew = true;
}
}
+ // We should keep this so we make sure it cannot time out.
+ mBGHandler.removeCallbacksAndMessages(item);
mBGHandler.scheduleRemoval(item, NOTED_OP_TIME_DELAY_MS);
return createdNew;
}
/**
- * Does the app-op code refer to a user sensitive permission for the specified user id
- * and package. Only user sensitive permission should be shown to the user by default.
- *
- * @param appOpCode The code of the app-op.
- * @param uid The uid of the user.
- * @param packageName The name of the package.
- *
- * @return {@code true} iff the app-op item is user sensitive
- */
- private boolean isUserSensitive(int appOpCode, int uid, String packageName) {
- String permission = AppOpsManager.opToPermission(appOpCode);
- if (permission == null) {
- return false;
- }
- int permFlags = mFlagsCache.getPermissionFlags(permission,
- packageName, UserHandle.getUserHandleForUid(uid));
- return (permFlags & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) != 0;
- }
-
- /**
- * Does the app-op item refer to an operation that should be shown to the user.
- * Only specficic ops (like SYSTEM_ALERT_WINDOW) or ops that refer to user sensitive
- * permission should be shown to the user by default.
- *
- * @param item The item
- *
- * @return {@code true} iff the app-op item should be shown to the user
- */
- private boolean isUserVisible(AppOpItem item) {
- return isUserVisible(item.getCode(), item.getUid(), item.getPackageName());
- }
-
-
- /**
- * Does the app-op, uid and package name, refer to an operation that should be shown to the
- * user. Only specficic ops (like {@link AppOpsManager.OP_SYSTEM_ALERT_WINDOW}) or
- * ops that refer to user sensitive permission should be shown to the user by default.
- *
- * @param item The item
- *
- * @return {@code true} iff the app-op for should be shown to the user
- */
- private boolean isUserVisible(int appOpCode, int uid, String packageName) {
- // currently OP_SYSTEM_ALERT_WINDOW does not correspond to a platform permission
- // which may be user senstive, so for now always show it to the user.
- if (appOpCode == AppOpsManager.OP_SYSTEM_ALERT_WINDOW) {
- return true;
- }
-
- return isUserSensitive(appOpCode, uid, packageName);
- }
-
- /**
* Returns a copy of the list containing all the active AppOps that the controller tracks.
*
* @return List of active AppOps information
@@ -304,8 +251,8 @@
final int numActiveItems = mActiveItems.size();
for (int i = 0; i < numActiveItems; i++) {
AppOpItem item = mActiveItems.get(i);
- if ((userId == UserHandle.USER_ALL || UserHandle.getUserId(item.getUid()) == userId)
- && isUserVisible(item)) {
+ if ((userId == UserHandle.USER_ALL
+ || UserHandle.getUserId(item.getUid()) == userId)) {
list.add(item);
}
}
@@ -314,8 +261,8 @@
final int numNotedItems = mNotedItems.size();
for (int i = 0; i < numNotedItems; i++) {
AppOpItem item = mNotedItems.get(i);
- if ((userId == UserHandle.USER_ALL || UserHandle.getUserId(item.getUid()) == userId)
- && isUserVisible(item)) {
+ if ((userId == UserHandle.USER_ALL
+ || UserHandle.getUserId(item.getUid()) == userId)) {
list.add(item);
}
}
@@ -325,7 +272,21 @@
@Override
public void onOpActiveChanged(int code, int uid, String packageName, boolean active) {
- if (updateActives(code, uid, packageName, active)) {
+ if (DEBUG) {
+ Log.w(TAG, String.format("onActiveChanged(%d,%d,%s,%s", code, uid, packageName,
+ Boolean.toString(active)));
+ }
+ boolean activeChanged = updateActives(code, uid, packageName, active);
+ if (!activeChanged) return; // early return
+ // Check if the item is also noted, in that case, there's no update.
+ boolean alsoNoted;
+ synchronized (mNotedItems) {
+ alsoNoted = getAppOpItemLocked(mNotedItems, code, uid, packageName) != null;
+ }
+ // If active is true, we only send the update if the op is not actively noted (already true)
+ // If active is false, we only send the update if the op is not actively noted (prevent
+ // early removal)
+ if (!alsoNoted) {
mBGHandler.post(() -> notifySuscribers(code, uid, packageName, active));
}
}
@@ -333,17 +294,23 @@
@Override
public void onOpNoted(int code, int uid, String packageName, int result) {
if (DEBUG) {
- Log.w(TAG, "Op: " + code + " with result " + AppOpsManager.MODE_NAMES[result]);
+ Log.w(TAG, "Noted op: " + code + " with result "
+ + AppOpsManager.MODE_NAMES[result] + " for package " + packageName);
}
if (result != AppOpsManager.MODE_ALLOWED) return;
- if (addNoted(code, uid, packageName)) {
+ boolean notedAdded = addNoted(code, uid, packageName);
+ if (!notedAdded) return; // early return
+ boolean alsoActive;
+ synchronized (mActiveItems) {
+ alsoActive = getAppOpItemLocked(mActiveItems, code, uid, packageName) != null;
+ }
+ if (!alsoActive) {
mBGHandler.post(() -> notifySuscribers(code, uid, packageName, true));
}
}
private void notifySuscribers(int code, int uid, String packageName, boolean active) {
- if (mCallbacksByCode.containsKey(code)
- && isUserVisible(code, uid, packageName)) {
+ if (mCallbacksByCode.containsKey(code)) {
if (DEBUG) Log.d(TAG, "Notifying of change in package " + packageName);
for (Callback cb: mCallbacksByCode.get(code)) {
cb.onActiveStateChanged(code, uid, packageName, active);
@@ -368,7 +335,7 @@
}
- protected final class H extends Handler {
+ protected class H extends Handler {
H(Looper looper) {
super(looper);
}
diff --git a/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt b/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt
deleted file mode 100644
index f02c7af..0000000
--- a/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.appops
-
-import android.content.Context
-import android.content.pm.PackageManager
-import android.os.UserHandle
-import android.util.ArrayMap
-import com.android.internal.annotations.VisibleForTesting
-
-private data class PermissionFlag(val flag: Int, val timestamp: Long)
-
-private data class PermissionFlagKey(
- val permission: String,
- val packageName: String,
- val user: UserHandle
-)
-
-internal const val CACHE_EXPIRATION = 10000L
-
-/**
- * Cache for PackageManager's PermissionFlags.
- *
- * Flags older than [CACHE_EXPIRATION] will be retrieved again.
- */
-internal open class PermissionFlagsCache(context: Context) {
- private val packageManager = context.packageManager
- private val permissionFlagsCache = ArrayMap<PermissionFlagKey, PermissionFlag>()
-
- /**
- * Retrieve permission flags from cache or PackageManager. There parameters will be passed
- * directly to [PackageManager].
- *
- * Calls to this method should be done from a background thread.
- */
- fun getPermissionFlags(permission: String, packageName: String, user: UserHandle): Int {
- val key = PermissionFlagKey(permission, packageName, user)
- val now = getCurrentTime()
- val value = permissionFlagsCache.getOrPut(key) {
- PermissionFlag(getFlags(key), now)
- }
- if (now - value.timestamp > CACHE_EXPIRATION) {
- val newValue = PermissionFlag(getFlags(key), now)
- permissionFlagsCache.put(key, newValue)
- return newValue.flag
- } else {
- return value.flag
- }
- }
-
- private fun getFlags(key: PermissionFlagKey) =
- packageManager.getPermissionFlags(key.permission, key.packageName, key.user)
-
- @VisibleForTesting
- protected open fun getCurrentTime() = System.currentTimeMillis()
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index fc19fa0..91bb80c 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -1166,13 +1166,6 @@
TextView messageView = (TextView) v.findViewById(R.id.message);
messageView.setSelected(true); // necessary for marquee to work
- TextView statusView = (TextView) v.findViewById(R.id.status);
- final String status = getStatus();
- if (!TextUtils.isEmpty(status)) {
- statusView.setText(status);
- } else {
- statusView.setVisibility(View.GONE);
- }
if (mIcon != null) {
icon.setImageDrawable(mIcon);
icon.setScaleType(ScaleType.CENTER_CROP);
@@ -1257,32 +1250,26 @@
LayoutInflater inflater) {
willCreate();
- View v = inflater.inflate(R
- .layout.global_actions_item, parent, false);
+ View v = inflater.inflate(com.android.systemui.R
+ .layout.global_actions_grid_item, parent, false);
ImageView icon = (ImageView) v.findViewById(R.id.icon);
TextView messageView = (TextView) v.findViewById(R.id.message);
- TextView statusView = (TextView) v.findViewById(R.id.status);
final boolean enabled = isEnabled();
+ boolean on = ((mState == State.On) || (mState == State.TurningOn));
if (messageView != null) {
- messageView.setText(mMessageResId);
+ messageView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
messageView.setEnabled(enabled);
messageView.setSelected(true); // necessary for marquee to work
}
- boolean on = ((mState == State.On) || (mState == State.TurningOn));
if (icon != null) {
icon.setImageDrawable(context.getDrawable(
(on ? mEnabledIconResId : mDisabledIconResid)));
icon.setEnabled(enabled);
}
- if (statusView != null) {
- statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
- statusView.setVisibility(View.VISIBLE);
- statusView.setEnabled(enabled);
- }
v.setEnabled(enabled);
return v;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 02c4beb..9f2bbc6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -20,7 +20,7 @@
import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_CORNER_FLOW;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED;
import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT;
import android.animation.Animator;
@@ -246,20 +246,13 @@
mSaveInBgTask.cancel(false);
}
- if (!DeviceConfig.getBoolean(
- NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false)) {
- mNotificationsController.reset();
- mNotificationsController.setImage(mScreenBitmap);
- mNotificationsController.showSavingScreenshotNotification();
- }
mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data).execute();
}
/**
* Takes a screenshot of the current display and shows an animation.
*/
- private void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible,
- boolean navBarVisible, Rect crop) {
+ private void takeScreenshot(Consumer<Uri> finisher, Rect crop) {
int rot = mDisplay.getRotation();
int width = crop.width();
int height = crop.height();
@@ -278,21 +271,20 @@
mScreenBitmap.prepareToDraw();
// Start the post-screenshot animation
- startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
- statusBarVisible, navBarVisible);
+ startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels);
}
- void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, boolean navBarVisible) {
+ void takeScreenshot(Consumer<Uri> finisher) {
mDisplay.getRealMetrics(mDisplayMetrics);
- takeScreenshot(finisher, statusBarVisible, navBarVisible,
+ takeScreenshot(
+ finisher,
new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
}
/**
* Displays a screenshot selector
*/
- void takeScreenshotPartial(final Consumer<Uri> finisher, final boolean statusBarVisible,
- final boolean navBarVisible) {
+ void takeScreenshotPartial(final Consumer<Uri> finisher) {
mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() {
@Override
@@ -312,8 +304,7 @@
if (rect != null) {
if (rect.width() != 0 && rect.height() != 0) {
// Need mScreenshotLayout to handle it after the view disappears
- mScreenshotLayout.post(() -> takeScreenshot(
- finisher, statusBarVisible, navBarVisible, rect));
+ mScreenshotLayout.post(() -> takeScreenshot(finisher, rect));
}
}
@@ -364,8 +355,7 @@
/**
* Starts the animation after taking the screenshot
*/
- private void startAnimation(final Consumer<Uri> finisher, int w, int h,
- boolean statusBarVisible, boolean navBarVisible) {
+ private void startAnimation(final Consumer<Uri> finisher, int w, int h) {
// If power save is on, show a toast so there is some visual indication that a screenshot
// has been taken.
PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -385,50 +375,30 @@
mScreenshotAnimation.removeAllListeners();
}
- boolean useCornerFlow =
- DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false);
mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
- ValueAnimator screenshotFadeOutAnim = useCornerFlow
- ? createScreenshotToCornerAnimation(w, h)
- : createScreenshotDropOutAnimation(w, h, statusBarVisible, navBarVisible);
+ ValueAnimator screenshotFadeOutAnim = createScreenshotToCornerAnimation(w, h);
mScreenshotAnimation = new AnimatorSet();
mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);
mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// Save the screenshot once we have a bit of time now
- if (!useCornerFlow) {
- saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
- @Override
- void onActionsReady(Uri uri, List<Notification.Action> actions) {
- if (uri == null) {
- mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_capture_text);
- } else {
- mNotificationsController
- .showScreenshotActionsNotification(uri, actions);
- }
+ saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
+ @Override
+ void onActionsReady(Uri uri, List<Notification.Action> actions) {
+ if (uri == null) {
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ } else {
+ mScreenshotHandler.post(() ->
+ createScreenshotActionsShadeAnimation(actions).start());
}
- });
- clearScreenshot();
- } else {
- saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
- @Override
- void onActionsReady(Uri uri, List<Notification.Action> actions) {
- if (uri == null) {
- mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_capture_text);
- } else {
- mScreenshotHandler.post(() ->
- createScreenshotActionsShadeAnimation(actions).start());
- }
- }
- });
- mScreenshotHandler.sendMessageDelayed(
- mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
- SCREENSHOT_CORNER_TIMEOUT_MILLIS);
- }
+ }
+ });
+ mScreenshotHandler.sendMessageDelayed(
+ mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
+ SCREENSHOT_CORNER_TIMEOUT_MILLIS);
}
});
mScreenshotHandler.post(() -> {
@@ -492,7 +462,7 @@
}
@Override
- public void onAnimationEnd(android.animation.Animator animation) {
+ public void onAnimationEnd(Animator animation) {
mScreenshotFlash.setVisibility(View.GONE);
}
});
@@ -513,81 +483,6 @@
return anim;
}
- private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible,
- boolean navBarVisible) {
- ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
- anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mBackgroundView.setVisibility(View.GONE);
- mScreenshotView.setVisibility(View.GONE);
- mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
- }
- });
-
- if (!statusBarVisible || !navBarVisible) {
- // There is no status bar/nav bar, so just fade the screenshot away in place
- anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION);
- anim.addUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float t = (Float) animation.getAnimatedValue();
- float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
- - t * (SCREENSHOT_DROP_IN_MIN_SCALE
- - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE);
- mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
- mScreenshotView.setAlpha(1f - t);
- mScreenshotView.setScaleX(scaleT);
- mScreenshotView.setScaleY(scaleT);
- }
- });
- } else {
- // In the case where there is a status bar, animate to the origin of the bar (top-left)
- final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION
- / SCREENSHOT_DROP_OUT_DURATION;
- final Interpolator scaleInterpolator = new Interpolator() {
- @Override
- public float getInterpolation(float x) {
- if (x < scaleDurationPct) {
- // Decelerate, and scale the input accordingly
- return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f));
- }
- return 1f;
- }
- };
-
- // Determine the bounds of how to scale
- float halfScreenWidth = (w - 2f * mBgPadding) / 2f;
- float halfScreenHeight = (h - 2f * mBgPadding) / 2f;
- final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET;
- final PointF finalPos = new PointF(
- -halfScreenWidth
- + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth,
- -halfScreenHeight
- + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight);
-
- // Animate the screenshot to the status bar
- anim.setDuration(SCREENSHOT_DROP_OUT_DURATION);
- anim.addUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float t = (Float) animation.getAnimatedValue();
- float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
- - scaleInterpolator.getInterpolation(t)
- * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE);
- mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
- mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t));
- mScreenshotView.setScaleX(scaleT);
- mScreenshotView.setScaleY(scaleT);
- mScreenshotView.setTranslationX(t * finalPos.x);
- mScreenshotView.setTranslationY(t * finalPos.y);
- }
- });
- }
- return anim;
- }
-
private ValueAnimator createScreenshotToCornerAnimation(int w, int h) {
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
@@ -648,13 +543,16 @@
});
mActionsView.addView(actionChip);
}
- TextView scrollChip = (TextView) inflater.inflate(
- R.layout.global_screenshot_action_chip, mActionsView, false);
- Toast scrollNotImplemented = Toast.makeText(
- mContext, "Not implemented", Toast.LENGTH_SHORT);
- scrollChip.setText("Scroll"); // TODO (mkephart): add resource and translate
- scrollChip.setOnClickListener(v -> scrollNotImplemented.show());
- mActionsView.addView(scrollChip);
+
+ if (DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SCREENSHOT_SCROLLING_ENABLED, false)) {
+ TextView scrollChip = (TextView) inflater.inflate(
+ R.layout.global_screenshot_action_chip, mActionsView, false);
+ Toast scrollNotImplemented = Toast.makeText(
+ mContext, "Not implemented", Toast.LENGTH_SHORT);
+ scrollChip.setText("Scroll"); // TODO (mkephart): add resource and translate
+ scrollChip.setOnClickListener(v -> scrollNotImplemented.show());
+ mActionsView.addView(scrollChip);
+ }
ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
mActionsView.setY(mDisplayMetrics.heightPixels);
@@ -776,8 +674,7 @@
String actionType = intent.getStringExtra(EXTRA_ACTION_TYPE);
Slog.d(TAG, "Executing smart action [" + actionType + "]:" + actionIntent);
ActivityOptions opts = ActivityOptions.makeBasic();
- context.startActivityAsUser(actionIntent, opts.toBundle(),
- UserHandle.CURRENT);
+ context.startActivityAsUser(actionIntent, opts.toBundle(), UserHandle.CURRENT);
ScreenshotSmartActions.notifyScreenshotAction(
context, intent.getStringExtra(EXTRA_ID), actionType, true);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java
new file mode 100644
index 0000000..11aa80b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java
@@ -0,0 +1,508 @@
+/*
+ * Copyright (C) 2011 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.screenshot;
+
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.media.MediaActionSound;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.PowerManager;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.Interpolator;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Class for handling device screen shots
+ *
+ * @deprecated will be removed when corner flow is complete and tested
+ */
+@Singleton
+@Deprecated
+public class GlobalScreenshotLegacy {
+
+ // These strings are used for communicating the action invoked to
+ // ScreenshotNotificationSmartActionsProvider.
+ static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
+ static final String EXTRA_ID = "android:screenshot_id";
+ static final String ACTION_TYPE_DELETE = "Delete";
+ static final String ACTION_TYPE_SHARE = "Share";
+ static final String ACTION_TYPE_EDIT = "Edit";
+ static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
+ static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
+
+ static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
+ static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
+ static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip";
+
+ private static final String TAG = "GlobalScreenshot";
+
+ private static final int SCREENSHOT_FLASH_TO_PEAK_DURATION = 130;
+ private static final int SCREENSHOT_DROP_IN_DURATION = 430;
+ private static final int SCREENSHOT_DROP_OUT_DELAY = 500;
+ private static final int SCREENSHOT_DROP_OUT_DURATION = 430;
+ private static final int SCREENSHOT_DROP_OUT_SCALE_DURATION = 370;
+ private static final int SCREENSHOT_FAST_DROP_OUT_DURATION = 320;
+ private static final float BACKGROUND_ALPHA = 0.5f;
+ private static final float SCREENSHOT_SCALE = 1f;
+ private static final float SCREENSHOT_DROP_IN_MIN_SCALE = SCREENSHOT_SCALE * 0.725f;
+ private static final float SCREENSHOT_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.45f;
+ private static final float SCREENSHOT_FAST_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.6f;
+ private static final float SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET = 0f;
+
+ private final ScreenshotNotificationsController mNotificationsController;
+
+ private Context mContext;
+ private WindowManager mWindowManager;
+ private WindowManager.LayoutParams mWindowLayoutParams;
+ private Display mDisplay;
+ private DisplayMetrics mDisplayMetrics;
+
+ private Bitmap mScreenBitmap;
+ private View mScreenshotLayout;
+ private ScreenshotSelectorView mScreenshotSelectorView;
+ private ImageView mBackgroundView;
+ private ImageView mScreenshotView;
+ private ImageView mScreenshotFlash;
+
+ private AnimatorSet mScreenshotAnimation;
+
+ private float mBgPadding;
+ private float mBgPaddingScale;
+
+ private AsyncTask<Void, Void, Void> mSaveInBgTask;
+
+ private MediaActionSound mCameraSound;
+
+ /**
+ * @param context everything needs a context :(
+ */
+ @Inject
+ public GlobalScreenshotLegacy(
+ Context context, @Main Resources resources, LayoutInflater layoutInflater,
+ ScreenshotNotificationsController screenshotNotificationsController) {
+ mContext = context;
+ mNotificationsController = screenshotNotificationsController;
+
+ // Inflate the screenshot layout
+ mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot_legacy, null);
+ mBackgroundView = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy_background);
+ mScreenshotView = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy);
+
+ mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy_flash);
+ mScreenshotSelectorView = mScreenshotLayout.findViewById(
+ R.id.global_screenshot_legacy_selector);
+ mScreenshotLayout.setFocusable(true);
+ mScreenshotSelectorView.setFocusable(true);
+ mScreenshotSelectorView.setFocusableInTouchMode(true);
+ mScreenshotLayout.setOnTouchListener((v, event) -> {
+ // Intercept and ignore all touch events
+ return true;
+ });
+
+ // Setup the window that we are going to use
+ mWindowLayoutParams = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
+ WindowManager.LayoutParams.TYPE_SCREENSHOT,
+ WindowManager.LayoutParams.FLAG_FULLSCREEN
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
+ PixelFormat.TRANSLUCENT);
+ mWindowLayoutParams.setTitle("ScreenshotAnimation");
+ mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ mWindowLayoutParams.setFitWindowInsetsTypes(0 /* types */);
+ mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ mDisplay = mWindowManager.getDefaultDisplay();
+ mDisplayMetrics = new DisplayMetrics();
+ mDisplay.getRealMetrics(mDisplayMetrics);
+
+ // Scale has to account for both sides of the bg
+ mBgPadding = (float) resources.getDimensionPixelSize(
+ R.dimen.global_screenshot_legacy_bg_padding);
+ mBgPaddingScale = mBgPadding / mDisplayMetrics.widthPixels;
+
+
+ // Setup the Camera shutter sound
+ mCameraSound = new MediaActionSound();
+ mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
+ }
+
+ /**
+ * Creates a new worker thread and saves the screenshot to the media store.
+ */
+ private void saveScreenshotInWorkerThread(
+ Consumer<Uri> finisher,
+ @Nullable GlobalScreenshot.ActionsReadyListener actionsReadyListener) {
+ GlobalScreenshot.SaveImageInBackgroundData data =
+ new GlobalScreenshot.SaveImageInBackgroundData();
+ data.image = mScreenBitmap;
+ data.finisher = finisher;
+ data.mActionsReadyListener = actionsReadyListener;
+ if (mSaveInBgTask != null) {
+ mSaveInBgTask.cancel(false);
+ }
+
+ mNotificationsController.reset();
+ mNotificationsController.setImage(mScreenBitmap);
+ mNotificationsController.showSavingScreenshotNotification();
+
+ mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data).execute();
+ }
+
+ /**
+ * Takes a screenshot of the current display and shows an animation.
+ */
+ private void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible,
+ boolean navBarVisible, Rect crop) {
+ int rot = mDisplay.getRotation();
+ int width = crop.width();
+ int height = crop.height();
+
+ // Take the screenshot
+ mScreenBitmap = SurfaceControl.screenshot(crop, width, height, rot);
+ if (mScreenBitmap == null) {
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ finisher.accept(null);
+ return;
+ }
+
+ // Optimizations
+ mScreenBitmap.setHasAlpha(false);
+ mScreenBitmap.prepareToDraw();
+
+ // Start the post-screenshot animation
+ startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
+ statusBarVisible, navBarVisible);
+ }
+
+ void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, boolean navBarVisible) {
+ mDisplay.getRealMetrics(mDisplayMetrics);
+ takeScreenshot(finisher, statusBarVisible, navBarVisible,
+ new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
+ }
+
+ /**
+ * Displays a screenshot selector
+ */
+ void takeScreenshotPartial(final Consumer<Uri> finisher, final boolean statusBarVisible,
+ final boolean navBarVisible) {
+ mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
+ mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ ScreenshotSelectorView view = (ScreenshotSelectorView) v;
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ view.startSelection((int) event.getX(), (int) event.getY());
+ return true;
+ case MotionEvent.ACTION_MOVE:
+ view.updateSelection((int) event.getX(), (int) event.getY());
+ return true;
+ case MotionEvent.ACTION_UP:
+ view.setVisibility(View.GONE);
+ mWindowManager.removeView(mScreenshotLayout);
+ final Rect rect = view.getSelectionRect();
+ if (rect != null) {
+ if (rect.width() != 0 && rect.height() != 0) {
+ // Need mScreenshotLayout to handle it after the view disappears
+ mScreenshotLayout.post(() -> takeScreenshot(
+ finisher, statusBarVisible, navBarVisible, rect));
+ }
+ }
+
+ view.stopSelection();
+ return true;
+ }
+
+ return false;
+ }
+ });
+ mScreenshotLayout.post(new Runnable() {
+ @Override
+ public void run() {
+ mScreenshotSelectorView.setVisibility(View.VISIBLE);
+ mScreenshotSelectorView.requestFocus();
+ }
+ });
+ }
+
+ /**
+ * Cancels screenshot request
+ */
+ void stopScreenshot() {
+ // If the selector layer still presents on screen, we remove it and resets its state.
+ if (mScreenshotSelectorView.getSelectionRect() != null) {
+ mWindowManager.removeView(mScreenshotLayout);
+ mScreenshotSelectorView.stopSelection();
+ }
+ }
+
+ /**
+ * Clears current screenshot
+ */
+ private void clearScreenshot() {
+ if (mScreenshotLayout.isAttachedToWindow()) {
+ mWindowManager.removeView(mScreenshotLayout);
+ }
+
+ // Clear any references to the bitmap
+ mScreenBitmap = null;
+ mScreenshotView.setImageBitmap(null);
+ mBackgroundView.setVisibility(View.GONE);
+ mScreenshotView.setVisibility(View.GONE);
+ mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+
+ /**
+ * Starts the animation after taking the screenshot
+ */
+ private void startAnimation(final Consumer<Uri> finisher, int w, int h,
+ boolean statusBarVisible, boolean navBarVisible) {
+ // If power save is on, show a toast so there is some visual indication that a screenshot
+ // has been taken.
+ PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ if (powerManager.isPowerSaveMode()) {
+ Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show();
+ }
+
+ // Add the view for the animation
+ mScreenshotView.setImageBitmap(mScreenBitmap);
+ mScreenshotLayout.requestFocus();
+
+ // Setup the animation with the screenshot just taken
+ if (mScreenshotAnimation != null) {
+ if (mScreenshotAnimation.isStarted()) {
+ mScreenshotAnimation.end();
+ }
+ mScreenshotAnimation.removeAllListeners();
+ }
+
+ mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
+ ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
+ ValueAnimator screenshotFadeOutAnim = createScreenshotDropOutAnimation(w, h,
+ statusBarVisible, navBarVisible);
+ mScreenshotAnimation = new AnimatorSet();
+ mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);
+ mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // Save the screenshot once we have a bit of time now
+ saveScreenshotInWorkerThread(finisher, new GlobalScreenshot.ActionsReadyListener() {
+ @Override
+ void onActionsReady(Uri uri, List<Notification.Action> actions) {
+ if (uri == null) {
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ } else {
+ mNotificationsController.showScreenshotActionsNotification(
+ uri, actions);
+ }
+ }
+ });
+ clearScreenshot();
+ }
+ });
+ mScreenshotLayout.post(() -> {
+ // Play the shutter sound to notify that we've taken a screenshot
+ mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
+
+ mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ mScreenshotView.buildLayer();
+ mScreenshotAnimation.start();
+ });
+ }
+
+ private ValueAnimator createScreenshotDropInAnimation() {
+ final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION)
+ / SCREENSHOT_DROP_IN_DURATION);
+ final float flashDurationPct = 2f * flashPeakDurationPct;
+ final Interpolator flashAlphaInterpolator = new Interpolator() {
+ @Override
+ public float getInterpolation(float x) {
+ // Flash the flash view in and out quickly
+ if (x <= flashDurationPct) {
+ return (float) Math.sin(Math.PI * (x / flashDurationPct));
+ }
+ return 0;
+ }
+ };
+ final Interpolator scaleInterpolator = new Interpolator() {
+ @Override
+ public float getInterpolation(float x) {
+ // We start scaling when the flash is at it's peak
+ if (x < flashPeakDurationPct) {
+ return 0;
+ }
+ return (x - flashDurationPct) / (1f - flashDurationPct);
+ }
+ };
+
+ Resources r = mContext.getResources();
+ if ((r.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
+ == Configuration.UI_MODE_NIGHT_YES) {
+ mScreenshotView.getBackground().setTint(Color.BLACK);
+ } else {
+ mScreenshotView.getBackground().setTintList(null);
+ }
+
+ ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+ anim.setDuration(SCREENSHOT_DROP_IN_DURATION);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mBackgroundView.setAlpha(0f);
+ mBackgroundView.setVisibility(View.VISIBLE);
+ mScreenshotView.setAlpha(0f);
+ mScreenshotView.setTranslationX(0f);
+ mScreenshotView.setTranslationY(0f);
+ mScreenshotView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale);
+ mScreenshotView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale);
+ mScreenshotView.setVisibility(View.VISIBLE);
+ mScreenshotFlash.setAlpha(0f);
+ mScreenshotFlash.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onAnimationEnd(android.animation.Animator animation) {
+ mScreenshotFlash.setVisibility(View.GONE);
+ }
+ });
+ anim.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float t = (Float) animation.getAnimatedValue();
+ float scaleT = (SCREENSHOT_SCALE + mBgPaddingScale)
+ - scaleInterpolator.getInterpolation(t)
+ * (SCREENSHOT_SCALE - SCREENSHOT_DROP_IN_MIN_SCALE);
+ mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA);
+ mScreenshotView.setAlpha(t);
+ mScreenshotView.setScaleX(scaleT);
+ mScreenshotView.setScaleY(scaleT);
+ mScreenshotFlash.setAlpha(flashAlphaInterpolator.getInterpolation(t));
+ }
+ });
+ return anim;
+ }
+
+ private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible,
+ boolean navBarVisible) {
+ ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+ anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mBackgroundView.setVisibility(View.GONE);
+ mScreenshotView.setVisibility(View.GONE);
+ mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+ });
+
+ if (!statusBarVisible || !navBarVisible) {
+ // There is no status bar/nav bar, so just fade the screenshot away in place
+ anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION);
+ anim.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float t = (Float) animation.getAnimatedValue();
+ float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
+ - t * (SCREENSHOT_DROP_IN_MIN_SCALE
+ - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE);
+ mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
+ mScreenshotView.setAlpha(1f - t);
+ mScreenshotView.setScaleX(scaleT);
+ mScreenshotView.setScaleY(scaleT);
+ }
+ });
+ } else {
+ // In the case where there is a status bar, animate to the origin of the bar (top-left)
+ final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION
+ / SCREENSHOT_DROP_OUT_DURATION;
+ final Interpolator scaleInterpolator = new Interpolator() {
+ @Override
+ public float getInterpolation(float x) {
+ if (x < scaleDurationPct) {
+ // Decelerate, and scale the input accordingly
+ return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f));
+ }
+ return 1f;
+ }
+ };
+
+ // Determine the bounds of how to scale
+ float halfScreenWidth = (w - 2f * mBgPadding) / 2f;
+ float halfScreenHeight = (h - 2f * mBgPadding) / 2f;
+ final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET;
+ final PointF finalPos = new PointF(
+ -halfScreenWidth
+ + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth,
+ -halfScreenHeight
+ + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight);
+
+ // Animate the screenshot to the status bar
+ anim.setDuration(SCREENSHOT_DROP_OUT_DURATION);
+ anim.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float t = (Float) animation.getAnimatedValue();
+ float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
+ - scaleInterpolator.getInterpolation(t)
+ * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE);
+ mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
+ mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t));
+ mScreenshotView.setScaleX(scaleT);
+ mScreenshotView.setScaleY(scaleT);
+ mScreenshotView.setTranslationX(t * finalPos.x);
+ mScreenshotView.setTranslationY(t * finalPos.y);
+ }
+ });
+ }
+ return anim;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 29b96a9..4f045d5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -16,15 +16,21 @@
package com.android.systemui.screenshot;
+import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
+
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_CORNER_FLOW;
+
import android.app.Service;
import android.content.Intent;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.os.UserManager;
+import android.provider.DeviceConfig;
import android.util.Log;
import android.view.WindowManager;
@@ -36,9 +42,10 @@
private static final String TAG = "TakeScreenshotService";
private final GlobalScreenshot mScreenshot;
+ private final GlobalScreenshotLegacy mScreenshotLegacy;
private final UserManager mUserManager;
- private Handler mHandler = new Handler() {
+ private Handler mHandler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(Message msg) {
final Messenger callback = msg.replyTo;
@@ -59,12 +66,24 @@
return;
}
+ // TODO (mkephart): clean up once notifications flow is fully deprecated
+ boolean useCornerFlow = DeviceConfig.getBoolean(
+ NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false);
switch (msg.what) {
case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
- mScreenshot.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);
+ if (useCornerFlow) {
+ mScreenshot.takeScreenshot(finisher);
+ } else {
+ mScreenshotLegacy.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);
+ }
break;
case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
- mScreenshot.takeScreenshotPartial(finisher, msg.arg1 > 0, msg.arg2 > 0);
+ if (useCornerFlow) {
+ mScreenshot.takeScreenshotPartial(finisher);
+ } else {
+ mScreenshotLegacy.takeScreenshotPartial(
+ finisher, msg.arg1 > 0, msg.arg2 > 0);
+ }
break;
default:
Log.d(TAG, "Invalid screenshot option: " + msg.what);
@@ -73,8 +92,10 @@
};
@Inject
- public TakeScreenshotService(GlobalScreenshot globalScreenshot, UserManager userManager) {
+ public TakeScreenshotService(GlobalScreenshot globalScreenshot,
+ GlobalScreenshotLegacy globalScreenshotLegacy, UserManager userManager) {
mScreenshot = globalScreenshot;
+ mScreenshotLegacy = globalScreenshotLegacy;
mUserManager = userManager;
}
@@ -86,6 +107,7 @@
@Override
public boolean onUnbind(Intent intent) {
if (mScreenshot != null) mScreenshot.stopScreenshot();
+ if (mScreenshotLegacy != null) mScreenshotLegacy.stopScreenshot();
return true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 43d0399..667e721 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -584,7 +584,15 @@
public void bindRow(ExpandableNotificationRow row) {
row.setRemoteInputController(mRemoteInputController);
- row.setRemoteViewClickHandler(mOnClickHandler);
+ }
+
+ /**
+ * Return on-click handler for notification remote views
+ *
+ * @return on-click handler
+ */
+ public RemoteViews.OnClickHandler getRemoteViewsOnClickHandler() {
+ return mOnClickHandler;
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java
index d1f6ebf..ec8dbea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java
@@ -18,6 +18,8 @@
import android.content.Context;
+import com.android.systemui.statusbar.notification.row.NotificationRowModule;
+
import javax.inject.Singleton;
import dagger.Module;
@@ -26,7 +28,7 @@
/**
* Dagger Module providing common dependencies of StatusBar.
*/
-@Module
+@Module(includes = {NotificationRowModule.class})
public class StatusBarDependenciesModule {
/**
* Provides our instance of CommandQueue which is considered optional.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
index 6a2774b..80b5b8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
@@ -41,8 +41,8 @@
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
import com.android.systemui.statusbar.notification.row.RowInflaterTask;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -67,6 +67,7 @@
private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
private final Context mContext;
+ private final NotificationRowContentBinder mRowContentBinder;
private final NotificationMessagingUtil mMessagingUtil;
private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger =
this::logNotificationExpansion;
@@ -79,7 +80,7 @@
private NotificationPresenter mPresenter;
private NotificationListContainer mListContainer;
private HeadsUpManager mHeadsUpManager;
- private NotificationContentInflater.InflationCallback mInflationCallback;
+ private NotificationRowContentBinder.InflationCallback mInflationCallback;
private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener;
private BindRowCallback mBindRowCallback;
private NotificationClicker mNotificationClicker;
@@ -90,6 +91,7 @@
Context context,
NotificationRemoteInputManager notificationRemoteInputManager,
NotificationLockscreenUserManager notificationLockscreenUserManager,
+ NotificationRowContentBinder rowContentBinder,
@Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
KeyguardBypassController keyguardBypassController,
StatusBarStateController statusBarStateController,
@@ -98,6 +100,7 @@
NotificationInterruptionStateProvider notificationInterruptionStateProvider,
NotificationLogger logger) {
mContext = context;
+ mRowContentBinder = rowContentBinder;
mMessagingUtil = new NotificationMessagingUtil(context);
mNotificationRemoteInputManager = notificationRemoteInputManager;
mNotificationLockscreenUserManager = notificationLockscreenUserManager;
@@ -124,7 +127,7 @@
mOnAppOpsClickListener = mGutsManager::openGuts;
}
- public void setInflationCallback(NotificationContentInflater.InflationCallback callback) {
+ public void setInflationCallback(NotificationRowContentBinder.InflationCallback callback) {
mInflationCallback = callback;
}
@@ -163,19 +166,6 @@
private void bindRow(NotificationEntry entry, PackageManager pmUser,
StatusBarNotification sbn, ExpandableNotificationRow row,
Runnable onDismissRunnable) {
- row.setExpansionLogger(mExpansionLogger, entry.getSbn().getKey());
- row.setBypassController(mKeyguardBypassController);
- row.setStatusBarStateController(mStatusBarStateController);
- row.setGroupManager(mGroupManager);
- row.setHeadsUpManager(mHeadsUpManager);
- row.setOnExpandClickListener(mPresenter);
- row.setInflationCallback(mInflationCallback);
- if (mAllowLongPress) {
- row.setLongPressListener(mGutsManager::openGuts);
- }
- mListContainer.bindRow(row);
- mNotificationRemoteInputManager.bindRow(row);
-
// Get the app name.
// Note that Notification.Builder#bindHeaderAppName has similar logic
// but since this field is used in the guts, it must be accurate.
@@ -193,15 +183,33 @@
} catch (PackageManager.NameNotFoundException e) {
// Do nothing
}
- row.setAppName(appname);
+
+ row.initialize(
+ appname,
+ sbn.getKey(),
+ mExpansionLogger,
+ mKeyguardBypassController,
+ mGroupManager,
+ mHeadsUpManager,
+ mRowContentBinder,
+ mPresenter);
+
+ // TODO: Either move these into ExpandableNotificationRow#initialize or out of row entirely
+ row.setStatusBarStateController(mStatusBarStateController);
+ row.setInflationCallback(mInflationCallback);
+ row.setAppOpsOnClickListener(mOnAppOpsClickListener);
+ if (mAllowLongPress) {
+ row.setLongPressListener(mGutsManager::openGuts);
+ }
+ mListContainer.bindRow(row);
+ mNotificationRemoteInputManager.bindRow(row);
+
row.setOnDismissRunnable(onDismissRunnable);
row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
if (ENABLE_REMOTE_INPUT) {
row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
}
- row.setAppOpsOnClickListener(mOnAppOpsClickListener);
-
mBindRowCallback.onBindRow(entry, pmUser, sbn, row);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 3c247df..a8a35d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -65,7 +65,6 @@
import android.widget.Chronometer;
import android.widget.FrameLayout;
import android.widget.ImageView;
-import android.widget.RemoteViews;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
@@ -150,7 +149,7 @@
private StatusBarStateController mStatusbarStateController;
private KeyguardBypassController mBypassController;
private LayoutListener mLayoutListener;
- private final NotificationContentInflater mNotificationInflater;
+ private NotificationRowContentBinder mNotificationContentBinder;
private int mIconTransformContentShift;
private int mIconTransformContentShiftNoIcon;
private int mMaxHeadsUpHeightBeforeN;
@@ -464,7 +463,7 @@
* Inflate views based off the inflation flags set. Inflation happens asynchronously.
*/
public void inflateViews() {
- mNotificationInflater.bindContent(mEntry, this, mInflationFlags, mBindParams,
+ mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams,
false /* forceInflate */, mInflationCallback);
}
@@ -478,7 +477,7 @@
// View should not be reinflated in the future
clearInflationFlags(inflationFlag);
Runnable freeViewRunnable =
- () -> mNotificationInflater.unbindContent(mEntry, this, inflationFlag);
+ () -> mNotificationContentBinder.unbindContent(mEntry, this, inflationFlag);
switch (inflationFlag) {
case FLAG_CONTENT_VIEW_HEADS_UP:
getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP,
@@ -742,23 +741,10 @@
return mIsHeadsUp || mHeadsupDisappearRunning;
}
-
- public void setGroupManager(NotificationGroupManager groupManager) {
- mGroupManager = groupManager;
- mPrivateLayout.setGroupManager(groupManager);
- }
-
public void setRemoteInputController(RemoteInputController r) {
mPrivateLayout.setRemoteInputController(r);
}
- public void setAppName(String appName) {
- mAppName = appName;
- if (mMenuRow != null && mMenuRow.getMenuView() != null) {
- mMenuRow.setAppName(mAppName);
- }
- }
-
public void addChildNotification(ExpandableNotificationRow row) {
addChildNotification(row, -1);
}
@@ -852,7 +838,7 @@
mIsChildInGroup = isChildInGroup;
if (mIsLowPriority) {
int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED;
- mNotificationInflater.bindContent(mEntry, this, flags, mBindParams,
+ mNotificationContentBinder.bindContent(mEntry, this, flags, mBindParams,
false /* forceInflate */, mInflationCallback);
}
}
@@ -1105,10 +1091,6 @@
return mPrivateLayout.getContractedNotificationHeader();
}
- public void setOnExpandClickListener(OnExpandClickListener onExpandClickListener) {
- mOnExpandClickListener = onExpandClickListener;
- }
-
public void setLongPressListener(LongPressListener longPressListener) {
mLongPressListener = longPressListener;
}
@@ -1131,10 +1113,6 @@
}
}
- public void setHeadsUpManager(HeadsUpManager headsUpManager) {
- mHeadsUpManager = headsUpManager;
- }
-
public HeadsUpManager getHeadsUpManager() {
return mHeadsUpManager;
}
@@ -1259,7 +1237,7 @@
l.reInflateViews();
}
mEntry.getSbn().clearPackageContext();
- mNotificationInflater.bindContent(mEntry, this, mInflationFlags, mBindParams,
+ mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams,
true /* forceInflate */, mInflationCallback);
}
@@ -1634,10 +1612,6 @@
mBindParams.usesIncreasedHeadsUpHeight = use;
}
- public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) {
- mNotificationInflater.setRemoteViewClickHandler(remoteViewClickHandler);
- }
-
/**
* Set callback for notification content inflation
*
@@ -1652,7 +1626,7 @@
mNeedsRedaction = needsRedaction;
if (needsRedaction) {
setInflationFlags(FLAG_CONTENT_VIEW_PUBLIC);
- mNotificationInflater.bindContent(mEntry, this, FLAG_CONTENT_VIEW_PUBLIC,
+ mNotificationContentBinder.bindContent(mEntry, this, FLAG_CONTENT_VIEW_PUBLIC,
mBindParams, false /* forceInflate */, mInflationCallback);
} else {
clearInflationFlags(FLAG_CONTENT_VIEW_PUBLIC);
@@ -1661,18 +1635,12 @@
}
}
- @VisibleForTesting
- public NotificationContentInflater getNotificationInflater() {
- return mNotificationInflater;
- }
-
public interface ExpansionLogger {
void logNotificationExpansion(String key, boolean userAction, boolean expanded);
}
public ExpandableNotificationRow(Context context, AttributeSet attrs) {
super(context, attrs);
- mNotificationInflater = new NotificationContentInflater();
mMenuRow = new NotificationMenuRow(mContext);
mImageResolver = new NotificationInlineImageResolver(context,
new NotificationInlineImageCache());
@@ -1680,8 +1648,30 @@
initDimens();
}
- public void setBypassController(KeyguardBypassController bypassController) {
+ /**
+ * Initialize row.
+ */
+ public void initialize(
+ String appName,
+ String notificationKey,
+ ExpansionLogger logger,
+ KeyguardBypassController bypassController,
+ NotificationGroupManager groupManager,
+ HeadsUpManager headsUpManager,
+ NotificationRowContentBinder rowContentBinder,
+ OnExpandClickListener onExpandClickListener) {
+ mAppName = appName;
+ if (mMenuRow != null && mMenuRow.getMenuView() != null) {
+ mMenuRow.setAppName(mAppName);
+ }
+ mLogger = logger;
+ mLoggingKey = notificationKey;
mBypassController = bypassController;
+ mGroupManager = groupManager;
+ mPrivateLayout.setGroupManager(groupManager);
+ mHeadsUpManager = headsUpManager;
+ mNotificationContentBinder = rowContentBinder;
+ mOnExpandClickListener = onExpandClickListener;
}
public void setStatusBarStateController(StatusBarStateController statusBarStateController) {
@@ -2920,11 +2910,6 @@
return 0;
}
- public void setExpansionLogger(ExpansionLogger logger, String key) {
- mLogger = logger;
- mLoggingKey = key;
- }
-
public void onExpandedByGesture(boolean userExpanded) {
int event = MetricsEvent.ACTION_NOTIFICATION_GESTURE_EXPANDER;
if (mGroupManager.isSummaryOfGroup(mEntry.getSbn())) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java
new file mode 100644
index 0000000..c11c60f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import android.widget.RemoteViews;
+
+import androidx.annotation.Nullable;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+
+/**
+ * Caches {@link RemoteViews} for a notification's content views.
+ */
+public interface NotifRemoteViewCache {
+
+ /**
+ * Whether the notification has the remote view cached
+ *
+ * @param entry notification
+ * @param flag inflation flag for content view
+ * @return true if the remote view is cached
+ */
+ boolean hasCachedView(NotificationEntry entry, @InflationFlag int flag);
+
+ /**
+ * Get the remote view for the content flag specified.
+ *
+ * @param entry notification
+ * @param flag inflation flag for the content view
+ * @return the remote view if it is cached, null otherwise
+ */
+ @Nullable RemoteViews getCachedView(NotificationEntry entry, @InflationFlag int flag);
+
+ /**
+ * Cache a remote view for a given content flag on a notification.
+ *
+ * @param entry notification
+ * @param flag inflation flag for the content view
+ * @param remoteView remote view to store
+ */
+ void putCachedView(
+ NotificationEntry entry,
+ @InflationFlag int flag,
+ RemoteViews remoteView);
+
+ /**
+ * Remove a cached remote view for a given content flag on a notification.
+ *
+ * @param entry notification
+ * @param flag inflation flag for the content view
+ */
+ void removeCachedView(NotificationEntry entry, @InflationFlag int flag);
+
+ /**
+ * Clear a notification's remote view cache.
+ *
+ * @param entry notification
+ */
+ void clearCache(NotificationEntry entry);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java
new file mode 100644
index 0000000..a6e5c2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import android.util.ArrayMap;
+import android.util.SparseArray;
+import android.widget.RemoteViews;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+
+import java.util.Map;
+
+import javax.inject.Inject;
+
+/**
+ * Implementation of remote view cache that keeps remote views cached for all active notifications.
+ */
+public class NotifRemoteViewCacheImpl implements NotifRemoteViewCache {
+ private final Map<NotificationEntry, SparseArray<RemoteViews>> mNotifCachedContentViews =
+ new ArrayMap<>();
+
+ @Inject
+ NotifRemoteViewCacheImpl(NotificationEntryManager entryManager) {
+ entryManager.addNotificationEntryListener(mEntryListener);
+ }
+
+ @Override
+ public boolean hasCachedView(NotificationEntry entry, @InflationFlag int flag) {
+ return getCachedView(entry, flag) != null;
+ }
+
+ @Override
+ public @Nullable RemoteViews getCachedView(NotificationEntry entry, @InflationFlag int flag) {
+ SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry);
+ if (contentViews == null) {
+ return null;
+ }
+ return contentViews.get(flag);
+ }
+
+ @Override
+ public void putCachedView(
+ NotificationEntry entry,
+ @InflationFlag int flag,
+ RemoteViews remoteView) {
+ /**
+ * TODO: We should be more strict here in the future (i.e. throw an exception) if the
+ * content views aren't created. We don't do that right now because we have edge cases
+ * where we may bind/unbind content after a notification is removed.
+ */
+ SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry);
+ if (contentViews == null) {
+ return;
+ }
+ contentViews.put(flag, remoteView);
+ }
+
+ @Override
+ public void removeCachedView(NotificationEntry entry, @InflationFlag int flag) {
+ SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry);
+ if (contentViews == null) {
+ return;
+ }
+ contentViews.remove(flag);
+ }
+
+ @Override
+ public void clearCache(NotificationEntry entry) {
+ SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry);
+ if (contentViews == null) {
+ return;
+ }
+ contentViews.clear();
+ }
+
+ private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
+ @Override
+ public void onPendingEntryAdded(NotificationEntry entry) {
+ mNotifCachedContentViews.put(entry, new SparseArray<>());
+ }
+
+ @Override
+ public void onEntryRemoved(
+ NotificationEntry entry,
+ @Nullable NotificationVisibility visibility,
+ boolean removedByUser) {
+ mNotifCachedContentViews.remove(entry);
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 30f22ac..e1a6747 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
@@ -26,7 +27,6 @@
import android.os.AsyncTask;
import android.os.CancellationSignal;
import android.service.notification.StatusBarNotification;
-import android.util.ArrayMap;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;
@@ -35,6 +35,7 @@
import com.android.internal.widget.ImageMessageConsumer;
import com.android.systemui.Dependency;
import com.android.systemui.statusbar.InflationTask;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.MediaNotificationProcessor;
@@ -49,17 +50,30 @@
import java.util.HashMap;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
/**
* {@link NotificationContentInflater} binds content to a {@link ExpandableNotificationRow} by
* asynchronously building the content's {@link RemoteViews} and applying it to the row.
*/
+@Singleton
+@VisibleForTesting(visibility = PACKAGE)
public class NotificationContentInflater implements NotificationRowContentBinder {
public static final String TAG = "NotifContentInflater";
- private RemoteViews.OnClickHandler mRemoteViewClickHandler;
private boolean mInflateSynchronously = false;
- private final ArrayMap<Integer, RemoteViews> mCachedContentViews = new ArrayMap<>();
+ private final NotificationRemoteInputManager mRemoteInputManager;
+ private final NotifRemoteViewCache mRemoteViewCache;
+
+ @Inject
+ public NotificationContentInflater(
+ NotifRemoteViewCache remoteViewCache,
+ NotificationRemoteInputManager remoteInputManager) {
+ mRemoteViewCache = remoteViewCache;
+ mRemoteInputManager = remoteInputManager;
+ }
@Override
public void bindContent(
@@ -76,27 +90,27 @@
return;
}
- StatusBarNotification sbn = row.getEntry().getSbn();
+ StatusBarNotification sbn = entry.getSbn();
// To check if the notification has inline image and preload inline image if necessary.
row.getImageResolver().preloadImages(sbn.getNotification());
if (forceInflate) {
- mCachedContentViews.clear();
+ mRemoteViewCache.clearCache(entry);
}
AsyncInflationTask task = new AsyncInflationTask(
- sbn,
mInflateSynchronously,
contentToBind,
- mCachedContentViews,
+ mRemoteViewCache,
+ entry,
row,
bindParams.isLowPriority,
bindParams.isChildInGroup,
bindParams.usesIncreasedHeight,
bindParams.usesIncreasedHeadsUpHeight,
callback,
- mRemoteViewClickHandler);
+ mRemoteInputManager.getRemoteViewsOnClickHandler());
if (mInflateSynchronously) {
task.onPostExecute(task.doInBackground());
} else {
@@ -123,13 +137,15 @@
result = inflateSmartReplyViews(result, reInflateFlags, entry,
row.getContext(), packageContext, row.getHeadsUpManager(),
row.getExistingSmartRepliesAndActions());
+
apply(
inflateSynchronously,
result,
reInflateFlags,
- mCachedContentViews,
+ mRemoteViewCache,
+ entry,
row,
- mRemoteViewClickHandler,
+ mRemoteInputManager.getRemoteViewsOnClickHandler(),
null);
return result;
}
@@ -149,7 +165,7 @@
int curFlag = 1;
while (contentToUnbind != 0) {
if ((contentToUnbind & curFlag) != 0) {
- freeNotificationView(row, curFlag);
+ freeNotificationView(entry, row, curFlag);
}
contentToUnbind &= ~curFlag;
curFlag = curFlag << 1;
@@ -157,34 +173,25 @@
}
/**
- * Set click handler for notification remote views
- *
- * @param remoteViewClickHandler click handler for remote views
- */
- public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) {
- mRemoteViewClickHandler = remoteViewClickHandler;
- }
-
- /**
* Frees the content view associated with the inflation flag. Will only succeed if the
* view is safe to remove.
*
* @param inflateFlag the flag corresponding to the content view which should be freed
*/
- private void freeNotificationView(ExpandableNotificationRow row,
+ private void freeNotificationView(NotificationEntry entry, ExpandableNotificationRow row,
@InflationFlag int inflateFlag) {
switch (inflateFlag) {
case FLAG_CONTENT_VIEW_HEADS_UP:
if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_HEADSUP)) {
row.getPrivateLayout().setHeadsUpChild(null);
- mCachedContentViews.remove(FLAG_CONTENT_VIEW_HEADS_UP);
+ mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP);
row.getPrivateLayout().setHeadsUpInflatedSmartReplies(null);
}
break;
case FLAG_CONTENT_VIEW_PUBLIC:
if (row.getPublicLayout().isContentViewInactive(VISIBLE_TYPE_CONTRACTED)) {
row.getPublicLayout().setContractedChild(null);
- mCachedContentViews.remove(FLAG_CONTENT_VIEW_PUBLIC);
+ mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC);
}
break;
case FLAG_CONTENT_VIEW_CONTRACTED:
@@ -245,11 +252,12 @@
return result;
}
- public static CancellationSignal apply(
+ private static CancellationSignal apply(
boolean inflateSynchronously,
InflationProgress result,
@InflationFlag int reInflateFlags,
- ArrayMap<Integer, RemoteViews> cachedContentViews,
+ NotifRemoteViewCache remoteViewCache,
+ NotificationEntry entry,
ExpandableNotificationRow row,
RemoteViews.OnClickHandler remoteViewClickHandler,
@Nullable InflationCallback callback) {
@@ -261,7 +269,7 @@
if ((reInflateFlags & flag) != 0) {
boolean isNewView =
!canReapplyRemoteView(result.newContentView,
- cachedContentViews.get(FLAG_CONTENT_VIEW_CONTRACTED));
+ remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED));
ApplyCallback applyCallback = new ApplyCallback() {
@Override
public void setResultView(View v) {
@@ -273,8 +281,8 @@
return result.newContentView;
}
};
- applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, cachedContentViews,
- row, isNewView, remoteViewClickHandler, callback, privateLayout,
+ applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
+ entry, row, isNewView, remoteViewClickHandler, callback, privateLayout,
privateLayout.getContractedChild(), privateLayout.getVisibleWrapper(
NotificationContentView.VISIBLE_TYPE_CONTRACTED),
runningInflations, applyCallback);
@@ -285,7 +293,7 @@
if (result.newExpandedView != null) {
boolean isNewView =
!canReapplyRemoteView(result.newExpandedView,
- cachedContentViews.get(FLAG_CONTENT_VIEW_EXPANDED));
+ remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED));
ApplyCallback applyCallback = new ApplyCallback() {
@Override
public void setResultView(View v) {
@@ -297,8 +305,8 @@
return result.newExpandedView;
}
};
- applyRemoteView(inflateSynchronously, result, reInflateFlags, flag,
- cachedContentViews, row, isNewView, remoteViewClickHandler,
+ applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
+ entry, row, isNewView, remoteViewClickHandler,
callback, privateLayout, privateLayout.getExpandedChild(),
privateLayout.getVisibleWrapper(
NotificationContentView.VISIBLE_TYPE_EXPANDED), runningInflations,
@@ -311,7 +319,7 @@
if (result.newHeadsUpView != null) {
boolean isNewView =
!canReapplyRemoteView(result.newHeadsUpView,
- cachedContentViews.get(FLAG_CONTENT_VIEW_HEADS_UP));
+ remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP));
ApplyCallback applyCallback = new ApplyCallback() {
@Override
public void setResultView(View v) {
@@ -323,8 +331,8 @@
return result.newHeadsUpView;
}
};
- applyRemoteView(inflateSynchronously, result, reInflateFlags, flag,
- cachedContentViews, row, isNewView, remoteViewClickHandler,
+ applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
+ entry, row, isNewView, remoteViewClickHandler,
callback, privateLayout, privateLayout.getHeadsUpChild(),
privateLayout.getVisibleWrapper(
VISIBLE_TYPE_HEADSUP), runningInflations,
@@ -336,7 +344,7 @@
if ((reInflateFlags & flag) != 0) {
boolean isNewView =
!canReapplyRemoteView(result.newPublicView,
- cachedContentViews.get(FLAG_CONTENT_VIEW_PUBLIC));
+ remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC));
ApplyCallback applyCallback = new ApplyCallback() {
@Override
public void setResultView(View v) {
@@ -348,15 +356,16 @@
return result.newPublicView;
}
};
- applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, cachedContentViews,
- row, isNewView, remoteViewClickHandler, callback,
+ applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
+ entry, row, isNewView, remoteViewClickHandler, callback,
publicLayout, publicLayout.getContractedChild(),
publicLayout.getVisibleWrapper(NotificationContentView.VISIBLE_TYPE_CONTRACTED),
runningInflations, applyCallback);
}
// Let's try to finish, maybe nobody is even inflating anything
- finishIfDone(result, reInflateFlags, cachedContentViews, runningInflations, callback, row);
+ finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations, callback, entry,
+ row);
CancellationSignal cancellationSignal = new CancellationSignal();
cancellationSignal.setOnCancelListener(
() -> runningInflations.values().forEach(CancellationSignal::cancel));
@@ -369,7 +378,8 @@
final InflationProgress result,
final @InflationFlag int reInflateFlags,
@InflationFlag int inflationId,
- final ArrayMap<Integer, RemoteViews> cachedContentViews,
+ final NotifRemoteViewCache remoteViewCache,
+ final NotificationEntry entry,
final ExpandableNotificationRow row,
boolean isNewView,
RemoteViews.OnClickHandler remoteViewClickHandler,
@@ -422,8 +432,8 @@
existingWrapper.onReinflated();
}
runningInflations.remove(inflationId);
- finishIfDone(result, reInflateFlags, cachedContentViews, runningInflations,
- callback, row);
+ finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations,
+ callback, entry, row);
}
@Override
@@ -488,11 +498,11 @@
* @return true if the inflation was finished
*/
private static boolean finishIfDone(InflationProgress result,
- @InflationFlag int reInflateFlags, ArrayMap<Integer, RemoteViews> cachedContentViews,
+ @InflationFlag int reInflateFlags, NotifRemoteViewCache remoteViewCache,
HashMap<Integer, CancellationSignal> runningInflations,
- @Nullable InflationCallback endListener, ExpandableNotificationRow row) {
+ @Nullable InflationCallback endListener, NotificationEntry entry,
+ ExpandableNotificationRow row) {
Assert.isMainThread();
- NotificationEntry entry = row.getEntry();
NotificationContentView privateLayout = row.getPrivateLayout();
NotificationContentView publicLayout = row.getPublicLayout();
if (runningInflations.isEmpty()) {
@@ -500,23 +510,27 @@
if (result.inflatedContentView != null) {
// New view case
privateLayout.setContractedChild(result.inflatedContentView);
- cachedContentViews.put(FLAG_CONTENT_VIEW_CONTRACTED, result.newContentView);
- } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_CONTRACTED) != null) {
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
+ result.newContentView);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)) {
// Reinflation case. Only update if it's still cached (i.e. view has not been
// freed while inflating).
- cachedContentViews.put(FLAG_CONTENT_VIEW_CONTRACTED, result.newContentView);
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
+ result.newContentView);
}
}
if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
if (result.inflatedExpandedView != null) {
privateLayout.setExpandedChild(result.inflatedExpandedView);
- cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, result.newExpandedView);
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
+ result.newExpandedView);
} else if (result.newExpandedView == null) {
privateLayout.setExpandedChild(null);
- cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, null);
- } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_EXPANDED) != null) {
- cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, result.newExpandedView);
+ remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)) {
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
+ result.newExpandedView);
}
if (result.newExpandedView != null) {
privateLayout.setExpandedInflatedSmartReplies(
@@ -530,12 +544,14 @@
if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
if (result.inflatedHeadsUpView != null) {
privateLayout.setHeadsUpChild(result.inflatedHeadsUpView);
- cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, result.newHeadsUpView);
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
+ result.newHeadsUpView);
} else if (result.newHeadsUpView == null) {
privateLayout.setHeadsUpChild(null);
- cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, null);
- } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_HEADS_UP) != null) {
- cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, result.newHeadsUpView);
+ remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)) {
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
+ result.newHeadsUpView);
}
if (result.newHeadsUpView != null) {
privateLayout.setHeadsUpInflatedSmartReplies(
@@ -548,16 +564,18 @@
if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
if (result.inflatedPublicView != null) {
publicLayout.setContractedChild(result.inflatedPublicView);
- cachedContentViews.put(FLAG_CONTENT_VIEW_PUBLIC, result.newPublicView);
- } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_PUBLIC) != null) {
- cachedContentViews.put(FLAG_CONTENT_VIEW_PUBLIC, result.newPublicView);
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
+ result.newPublicView);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)) {
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
+ result.newPublicView);
}
}
entry.headsUpStatusBarText = result.headsUpStatusBarText;
entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic;
if (endListener != null) {
- endListener.onAsyncInflationFinished(row.getEntry(), reInflateFlags);
+ endListener.onAsyncInflationFinished(entry, reInflateFlags);
}
return true;
}
@@ -615,7 +633,7 @@
public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress>
implements InflationCallback, InflationTask {
- private final StatusBarNotification mSbn;
+ private final NotificationEntry mEntry;
private final Context mContext;
private final boolean mInflateSynchronously;
private final boolean mIsLowPriority;
@@ -624,17 +642,17 @@
private final InflationCallback mCallback;
private final boolean mUsesIncreasedHeadsUpHeight;
private @InflationFlag int mReInflateFlags;
- private final ArrayMap<Integer, RemoteViews> mCachedContentViews;
+ private final NotifRemoteViewCache mRemoteViewCache;
private ExpandableNotificationRow mRow;
private Exception mError;
private RemoteViews.OnClickHandler mRemoteViewClickHandler;
private CancellationSignal mCancellationSignal;
private AsyncInflationTask(
- StatusBarNotification notification,
boolean inflateSynchronously,
@InflationFlag int reInflateFlags,
- ArrayMap<Integer, RemoteViews> cachedContentViews,
+ NotifRemoteViewCache cache,
+ NotificationEntry entry,
ExpandableNotificationRow row,
boolean isLowPriority,
boolean isChildInGroup,
@@ -642,11 +660,11 @@
boolean usesIncreasedHeadsUpHeight,
InflationCallback callback,
RemoteViews.OnClickHandler remoteViewClickHandler) {
+ mEntry = entry;
mRow = row;
- mSbn = notification;
mInflateSynchronously = inflateSynchronously;
mReInflateFlags = reInflateFlags;
- mCachedContentViews = cachedContentViews;
+ mRemoteViewCache = cache;
mContext = mRow.getContext();
mIsLowPriority = isLowPriority;
mIsChildInGroup = isChildInGroup;
@@ -654,7 +672,6 @@
mUsesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight;
mRemoteViewClickHandler = remoteViewClickHandler;
mCallback = callback;
- NotificationEntry entry = row.getEntry();
entry.setInflationTask(this);
}
@@ -667,12 +684,13 @@
@Override
protected InflationProgress doInBackground(Void... params) {
try {
+ final StatusBarNotification sbn = mEntry.getSbn();
final Notification.Builder recoveredBuilder
= Notification.Builder.recoverBuilder(mContext,
- mSbn.getNotification());
+ sbn.getNotification());
- Context packageContext = mSbn.getPackageContext(mContext);
- Notification notification = mSbn.getNotification();
+ Context packageContext = sbn.getPackageContext(mContext);
+ Notification notification = sbn.getNotification();
if (notification.isMediaNotification()) {
MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext,
packageContext);
@@ -681,7 +699,7 @@
InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
recoveredBuilder, mIsLowPriority, mIsChildInGroup, mUsesIncreasedHeight,
mUsesIncreasedHeadsUpHeight, packageContext);
- return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mRow.getEntry(),
+ return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mEntry,
mRow.getContext(), packageContext, mRow.getHeadsUpManager(),
mRow.getExistingSmartRepliesAndActions());
} catch (Exception e) {
@@ -694,15 +712,15 @@
protected void onPostExecute(InflationProgress result) {
if (mError == null) {
mCancellationSignal = apply(mInflateSynchronously, result, mReInflateFlags,
- mCachedContentViews, mRow, mRemoteViewClickHandler, this);
+ mRemoteViewCache, mEntry, mRow, mRemoteViewClickHandler, this);
} else {
handleError(mError);
}
}
private void handleError(Exception e) {
- mRow.getEntry().onInflationTaskFinished();
- StatusBarNotification sbn = mRow.getEntry().getSbn();
+ mEntry.onInflationTaskFinished();
+ StatusBarNotification sbn = mEntry.getSbn();
final String ident = sbn.getPackageName() + "/0x"
+ Integer.toHexString(sbn.getId());
Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e);
@@ -736,10 +754,10 @@
@Override
public void onAsyncInflationFinished(NotificationEntry entry,
@InflationFlag int inflatedFlags) {
- mRow.getEntry().onInflationTaskFinished();
+ mEntry.onInflationTaskFinished();
mRow.onNotificationUpdated();
if (mCallback != null) {
- mCallback.onAsyncInflationFinished(mRow.getEntry(), inflatedFlags);
+ mCallback.onAsyncInflationFinished(mEntry, inflatedFlags);
}
// Notify the resolver that the inflation task has finished,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
new file mode 100644
index 0000000..df8653c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import javax.inject.Singleton;
+
+import dagger.Binds;
+import dagger.Module;
+
+/**
+ * Dagger Module containing notification row and view inflation implementations.
+ */
+@Module
+public abstract class NotificationRowModule {
+ /**
+ * Provides notification row content binder instance.
+ */
+ @Binds
+ @Singleton
+ public abstract NotificationRowContentBinder provideNotificationRowContentBinder(
+ NotificationContentInflater contentBinderImpl);
+
+ /**
+ * Provides notification remote view cache instance.
+ */
+ @Binds
+ @Singleton
+ public abstract NotifRemoteViewCache provideNotifRemoteViewCache(
+ NotifRemoteViewCacheImpl cacheImpl);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
index fe0739f..896b6e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
@@ -32,7 +32,6 @@
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater.AsyncInflationTask;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup;
import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener;
@@ -428,7 +427,7 @@
* The notification is still pending inflation but we've decided that we no longer need
* the content view (e.g. suppression might have changed and we decided we need to transfer
* back). However, there is no way to abort just this inflation if other inflation requests
- * have started (see {@link AsyncInflationTask#supersedeTask(InflationTask)}). So instead
+ * have started (see {@link InflationTask#supersedeTask(InflationTask)}). So instead
* we just flag it as aborted and free when it's inflated.
*/
boolean mAbortOnInflation;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index af0ef82..e5ae1aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -25,11 +25,11 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+
+import static java.lang.Thread.sleep;
import android.app.AppOpsManager;
import android.content.pm.PackageManager;
@@ -57,7 +57,6 @@
private static final String TEST_PACKAGE_NAME = "test";
private static final int TEST_UID = UserHandle.getUid(0, 0);
private static final int TEST_UID_OTHER = UserHandle.getUid(1, 0);
- private static final int TEST_UID_NON_USER_SENSITIVE = UserHandle.getUid(2, 0);
@Mock
private AppOpsManager mAppOpsManager;
@@ -68,8 +67,6 @@
@Mock
private AppOpsControllerImpl.H mMockHandler;
@Mock
- private PermissionFlagsCache mFlagsCache;
- @Mock
private DumpController mDumpController;
private AppOpsControllerImpl mController;
@@ -85,17 +82,9 @@
// All permissions of TEST_UID and TEST_UID_OTHER are user sensitive. None of
// TEST_UID_NON_USER_SENSITIVE are user sensitive.
getContext().setMockPackageManager(mPackageManager);
- when(mFlagsCache.getPermissionFlags(anyString(), anyString(),
- eq(UserHandle.getUserHandleForUid(TEST_UID)))).thenReturn(
- PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED);
- when(mFlagsCache.getPermissionFlags(anyString(), anyString(),
- eq(UserHandle.getUserHandleForUid(TEST_UID_OTHER)))).thenReturn(
- PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED);
- when(mFlagsCache.getPermissionFlags(anyString(), anyString(),
- eq(UserHandle.getUserHandleForUid(TEST_UID_NON_USER_SENSITIVE)))).thenReturn(0);
- mController = new AppOpsControllerImpl(mContext, mTestableLooper.getLooper(), mFlagsCache,
- mDumpController);
+ mController =
+ new AppOpsControllerImpl(mContext, mTestableLooper.getLooper(), mDumpController);
}
@Test
@@ -191,14 +180,6 @@
}
@Test
- public void nonUserSensitiveOpsAreIgnored() {
- mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO,
- TEST_UID_NON_USER_SENSITIVE, TEST_PACKAGE_NAME, true);
- assertEquals(0, mController.getActiveAppOpsForUser(
- UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size());
- }
-
- @Test
public void opNotedScheduledForRemoval() {
mController.setBGHandler(mMockHandler);
mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
@@ -251,4 +232,100 @@
// Only one post to notify subscribers
verify(mMockHandler, times(2)).scheduleRemoval(any(), anyLong());
}
+
+ @Test
+ public void testActiveOpNotRemovedAfterNoted() throws InterruptedException {
+ // Replaces the timeout delay with 5 ms
+ AppOpsControllerImpl.H testHandler = mController.new H(mTestableLooper.getLooper()) {
+ @Override
+ public void scheduleRemoval(AppOpItem item, long timeToRemoval) {
+ super.scheduleRemoval(item, 5L);
+ }
+ };
+
+ mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+ mController.setBGHandler(testHandler);
+
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+ mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
+ AppOpsManager.MODE_ALLOWED);
+
+ mTestableLooper.processAllMessages();
+ List<AppOpItem> list = mController.getActiveAppOps();
+ verify(mCallback).onActiveStateChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+ // Duplicates are not removed between active and noted
+ assertEquals(2, list.size());
+
+ sleep(10L);
+
+ mTestableLooper.processAllMessages();
+
+ verify(mCallback, never()).onActiveStateChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, false);
+ list = mController.getActiveAppOps();
+ assertEquals(1, list.size());
+ }
+
+ @Test
+ public void testNotedNotRemovedAfterActive() {
+ mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+
+ mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
+ AppOpsManager.MODE_ALLOWED);
+
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+ mTestableLooper.processAllMessages();
+ List<AppOpItem> list = mController.getActiveAppOps();
+ verify(mCallback).onActiveStateChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+ // Duplicates are not removed between active and noted
+ assertEquals(2, list.size());
+
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, false);
+
+ mTestableLooper.processAllMessages();
+
+ verify(mCallback, never()).onActiveStateChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, false);
+ list = mController.getActiveAppOps();
+ assertEquals(1, list.size());
+ }
+
+ @Test
+ public void testNotedAndActiveOnlyOneCall() {
+ mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+
+ mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
+ AppOpsManager.MODE_ALLOWED);
+
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+ mTestableLooper.processAllMessages();
+ verify(mCallback).onActiveStateChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+ }
+
+ @Test
+ public void testActiveAndNotedOnlyOneCall() {
+ mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+ mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
+ AppOpsManager.MODE_ALLOWED);
+
+ mTestableLooper.processAllMessages();
+ verify(mCallback).onActiveStateChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt
deleted file mode 100644
index dc070de..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.appops
-
-import android.content.Context
-import android.content.pm.PackageManager
-import android.os.UserHandle
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyString
-import org.mockito.Mock
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class PermissionFlagsCacheTest : SysuiTestCase() {
-
- companion object {
- const val TEST_PERMISSION = "test_permission"
- const val TEST_PACKAGE = "test_package"
- }
-
- @Mock
- private lateinit var mPackageManager: PackageManager
- @Mock
- private lateinit var mUserHandle: UserHandle
- private lateinit var flagsCache: TestPermissionFlagsCache
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- mContext.setMockPackageManager(mPackageManager)
- flagsCache = TestPermissionFlagsCache(mContext)
- }
-
- @Test
- fun testCallsPackageManager_exactlyOnce() {
- flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
- flagsCache.time = CACHE_EXPIRATION - 1
- verify(mPackageManager).getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
- }
-
- @Test
- fun testCallsPackageManager_cacheExpired() {
- flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
- flagsCache.time = CACHE_EXPIRATION + 1
- flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
- verify(mPackageManager, times(2))
- .getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
- }
-
- @Test
- fun testCallsPackageMaanger_multipleKeys() {
- flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
- flagsCache.getPermissionFlags(TEST_PERMISSION, "", mUserHandle)
- verify(mPackageManager, times(2))
- .getPermissionFlags(anyString(), anyString(), any())
- }
-
- private class TestPermissionFlagsCache(context: Context) : PermissionFlagsCache(context) {
- var time = 0L
-
- override fun getCurrentTime(): Long {
- return time
- }
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index 77659df..3fdbd3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -47,6 +47,9 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
+import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache;
import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
@@ -72,6 +75,7 @@
public static final UserHandle USER_HANDLE = UserHandle.of(ActivityManager.getCurrentUser());
private static final String GROUP_KEY = "gruKey";
+ private static final String APP_NAME = "appName";
private final Context mContext;
private int mId;
@@ -303,9 +307,6 @@
null /* root */,
false /* attachToRoot */);
ExpandableNotificationRow row = mRow;
- row.setGroupManager(mGroupManager);
- row.setHeadsUpManager(mHeadsUpManager);
- row.setAboveShelfChangedListener(aboveShelf -> {});
final NotificationChannel channel =
new NotificationChannel(
@@ -329,6 +330,23 @@
entry.setRow(row);
entry.createIcons(mContext, entry.getSbn());
row.setEntry(entry);
+
+ NotificationContentInflater contentBinder = new NotificationContentInflater(
+ mock(NotifRemoteViewCache.class),
+ mock(NotificationRemoteInputManager.class));
+ contentBinder.setInflateSynchronously(true);
+
+ row.initialize(
+ APP_NAME,
+ entry.getKey(),
+ mock(ExpansionLogger.class),
+ mock(KeyguardBypassController.class),
+ mGroupManager,
+ mHeadsUpManager,
+ contentBinder,
+ mock(OnExpandClickListener.class));
+ row.setAboveShelfChangedListener(aboveShelf -> { });
+
row.setInflationFlags(extraInflationFlags);
inflateAndWait(row);
@@ -341,7 +359,6 @@
private static void inflateAndWait(ExpandableNotificationRow row) throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(1);
- row.getNotificationInflater().setInflateSynchronously(true);
NotificationContentInflater.InflationCallback callback =
new NotificationContentInflater.InflationCallback() {
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index a60fd52..1e0179d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -84,7 +84,10 @@
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache;
+import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
import com.android.systemui.statusbar.notification.row.RowInflaterTask;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -200,9 +203,15 @@
mEntry.expandedIcon = mock(StatusBarIconView.class);
+ NotificationRowContentBinder contentBinder = new NotificationContentInflater(
+ mock(NotifRemoteViewCache.class),
+ mRemoteInputManager);
+
NotificationRowBinderImpl notificationRowBinder =
new NotificationRowBinderImpl(mContext,
- mRemoteInputManager, mLockscreenUserManager,
+ mRemoteInputManager,
+ mLockscreenUserManager,
+ contentBinder,
true, /* allowLongPress */
mock(KeyguardBypassController.class),
mock(StatusBarStateController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
new file mode 100644
index 0000000..d7214f3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.widget.RemoteViews;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class NotifRemoteViewCacheImplTest extends SysuiTestCase {
+
+ private NotifRemoteViewCacheImpl mNotifRemoteViewCache;
+ private NotificationEntry mEntry;
+ private NotificationEntryListener mEntryListener;
+ @Mock private RemoteViews mRemoteViews;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mEntry = new NotificationEntryBuilder().build();
+
+ NotificationEntryManager entryManager = mock(NotificationEntryManager.class);
+ mNotifRemoteViewCache = new NotifRemoteViewCacheImpl(entryManager);
+ ArgumentCaptor<NotificationEntryListener> entryListenerCaptor =
+ ArgumentCaptor.forClass(NotificationEntryListener.class);
+ verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture());
+ mEntryListener = entryListenerCaptor.getValue();
+ }
+
+ @Test
+ public void testPutCachedView() {
+ // GIVEN an initialized cache for an entry.
+ mEntryListener.onPendingEntryAdded(mEntry);
+
+ // WHEN a notification's cached remote views is put in.
+ mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
+
+ // THEN the remote view is cached.
+ assertTrue(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+ assertEquals(
+ "Cached remote view is not the one we put in.",
+ mRemoteViews,
+ mNotifRemoteViewCache.getCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+ }
+
+ @Test
+ public void testRemoveCachedView() {
+ // GIVEN a cache with a cached view.
+ mEntryListener.onPendingEntryAdded(mEntry);
+ mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
+
+ // WHEN we remove the cached view.
+ mNotifRemoteViewCache.removeCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED);
+
+ // THEN the remote view is not cached.
+ assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+ }
+
+ @Test
+ public void testClearCache() {
+ // GIVEN a non-empty cache.
+ mEntryListener.onPendingEntryAdded(mEntry);
+ mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
+ mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED, mRemoteViews);
+
+ // WHEN we clear the cache.
+ mNotifRemoteViewCache.clearCache(mEntry);
+
+ // THEN the cache is empty.
+ assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+ assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index f916fe5..cb9da6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -22,11 +22,16 @@
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.Notification;
import android.content.Context;
@@ -35,16 +40,17 @@
import android.os.Looper;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
-import android.util.ArrayMap;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RemoteViews;
+import android.widget.TextView;
import androidx.test.filters.SmallTest;
import androidx.test.filters.Suppress;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.InflationTask;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationTestHelper;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
@@ -57,6 +63,8 @@
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.util.HashMap;
import java.util.concurrent.CountDownLatch;
@@ -73,8 +81,11 @@
private Notification.Builder mBuilder;
private ExpandableNotificationRow mRow;
+ @Mock private NotifRemoteViewCache mCache;
+
@Before
public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
mBuilder = new Notification.Builder(mContext).setSmallIcon(
R.drawable.ic_person)
.setContentTitle("Title")
@@ -83,7 +94,9 @@
ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow(
mBuilder.build());
mRow = spy(row);
- mNotificationInflater = new NotificationContentInflater();
+ mNotificationInflater = new NotificationContentInflater(
+ mCache,
+ mock(NotificationRemoteInputManager.class));
}
@Test
@@ -174,7 +187,9 @@
result,
FLAG_CONTENT_VIEW_EXPANDED,
0,
- new ArrayMap() /* cachedContentViews */, mRow,
+ mock(NotifRemoteViewCache.class),
+ mRow.getEntry(),
+ mRow,
true /* isNewView */, (v, p, r) -> true,
new InflationCallback() {
@Override
@@ -244,6 +259,71 @@
NotificationContentInflater.canReapplyRemoteView(mediaView, decoratedMediaView));
}
+ @Test
+ public void testUsesSameViewWhenCachedPossibleToReuse() throws Exception {
+ // GIVEN a cached view.
+ RemoteViews contractedRemoteView = mBuilder.createContentView();
+ when(mCache.hasCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+ .thenReturn(true);
+ when(mCache.getCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+ .thenReturn(contractedRemoteView);
+
+ // GIVEN existing bound view with same layout id.
+ View view = contractedRemoteView.apply(mContext, null /* parent */);
+ mRow.getPrivateLayout().setContractedChild(view);
+
+ // WHEN inflater inflates
+ inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow);
+
+ // THEN the view should be re-used
+ assertEquals("Binder inflated a new view even though the old one was cached and usable.",
+ view, mRow.getPrivateLayout().getContractedChild());
+ }
+
+ @Test
+ public void testInflatesNewViewWhenCachedNotPossibleToReuse() throws Exception {
+ // GIVEN a cached remote view.
+ RemoteViews contractedRemoteView = mBuilder.createHeadsUpContentView();
+ when(mCache.hasCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+ .thenReturn(true);
+ when(mCache.getCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+ .thenReturn(contractedRemoteView);
+
+ // GIVEN existing bound view with different layout id.
+ View view = new TextView(mContext);
+ mRow.getPrivateLayout().setContractedChild(view);
+
+ // WHEN inflater inflates
+ inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow);
+
+ // THEN the view should be a new view
+ assertNotEquals("Binder (somehow) used the same view when inflating.",
+ view, mRow.getPrivateLayout().getContractedChild());
+ }
+
+ @Test
+ public void testInflationCachesCreatedRemoteView() throws Exception {
+ // WHEN inflater inflates
+ inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow);
+
+ // THEN inflater informs cache of the new remote view
+ verify(mCache).putCachedView(
+ eq(mRow.getEntry()),
+ eq(FLAG_CONTENT_VIEW_CONTRACTED),
+ any());
+ }
+
+ @Test
+ public void testUnbindRemovesCachedRemoteView() {
+ // WHEN inflated unbinds content
+ mNotificationInflater.unbindContent(mRow.getEntry(), mRow, FLAG_CONTENT_VIEW_HEADS_UP);
+
+ // THEN inflated informs cache to remove remote view
+ verify(mCache).removeCachedView(
+ eq(mRow.getEntry()),
+ eq(FLAG_CONTENT_VIEW_HEADS_UP));
+ }
+
private static void inflateAndWait(NotificationContentInflater inflater,
@InflationFlag int contentToInflate,
ExpandableNotificationRow row)
diff --git a/packages/Tethering/AndroidManifest.xml b/packages/Tethering/AndroidManifest.xml
index e99c2c5..5a71eb2 100644
--- a/packages/Tethering/AndroidManifest.xml
+++ b/packages/Tethering/AndroidManifest.xml
@@ -32,6 +32,7 @@
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.MANAGE_USB" />
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
+ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
<uses-permission android:name="android.permission.TETHER_PRIVILEGED" />
<uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml
index 37e679d..ca290c6 100644
--- a/packages/Tethering/res/values/config.xml
+++ b/packages/Tethering/res/values/config.xml
@@ -1,7 +1,152 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
<resources>
<!--
OEMs that wish to change the below settings must do so via a runtime resource overlay package
and *NOT* by changing this file. This file is part of the tethering mainline module.
+ TODO: define two resources for each config item: a default_* resource and a config_* resource,
+ config_* is empty by default but may be overridden by RROs.
-->
+ <!-- List of regexpressions describing the interface (if any) that represent tetherable
+ USB interfaces. If the device doesn't want to support tethering over USB this should
+ be empty. An example would be "usb.*" -->
+ <string-array translatable="false" name="config_tether_usb_regexs">
+ <item>"usb\\d"</item>
+ <item>"rndis\\d"</item>
+ </string-array>
+
+ <!-- List of regexpressions describing the interface (if any) that represent tetherable
+ Wifi interfaces. If the device doesn't want to support tethering over Wifi this
+ should be empty. An example would be "softap.*" -->
+ <string-array translatable="false" name="config_tether_wifi_regexs">
+ <item>"wlan\\d"</item>
+ <item>"softap\\d"</item>
+ </string-array>
+
+ <!-- List of regexpressions describing the interface (if any) that represent tetherable
+ Wifi P2P interfaces. If the device doesn't want to support tethering over Wifi P2p this
+ should be empty. An example would be "p2p-p2p.*" -->
+ <string-array translatable="false" name="config_tether_wifi_p2p_regexs">
+ </string-array>
+
+ <!-- List of regexpressions describing the interface (if any) that represent tetherable
+ bluetooth interfaces. If the device doesn't want to support tethering over bluetooth this
+ should be empty. -->
+ <string-array translatable="false" name="config_tether_bluetooth_regexs">
+ <item>"bt-pan"</item>
+ </string-array>
+
+ <!-- Use the old dnsmasq DHCP server for tethering instead of the framework implementation. -->
+ <bool translatable="false" name="config_tether_enable_legacy_dhcp_server">false</bool>
+
+ <!-- Dhcp range (min, max) to use for tethering purposes -->
+ <string-array translatable="false" name="config_tether_dhcp_range">
+ </string-array>
+
+ <!-- Array of ConnectivityManager.TYPE_{BLUETOOTH, ETHERNET, MOBILE, MOBILE_DUN, MOBILE_HIPRI,
+ WIFI} values allowable for tethering.
+
+ Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
+ [1,7,0] for TYPE_WIFI, TYPE_BLUETOOTH, and TYPE_MOBILE.
+
+ This list is also modified by code within the framework, including:
+
+ - TYPE_ETHERNET (9) is prepended to this list, and
+
+ - the return value of TelephonyManager.isTetheringApnRequired()
+ determines how the array is further modified:
+
+ * TRUE (DUN REQUIRED).
+ TYPE_MOBILE is removed (if present).
+ TYPE_MOBILE_HIPRI is removed (if present).
+ TYPE_MOBILE_DUN is appended (if not already present).
+
+ * FALSE (DUN NOT REQUIRED).
+ TYPE_MOBILE_DUN is removed (if present).
+ If both of TYPE_MOBILE{,_HIPRI} are not present:
+ TYPE_MOBILE is appended.
+ TYPE_MOBILE_HIPRI is appended.
+
+ For other changes applied to this list, now and in the future, see
+ com.android.server.connectivity.tethering.TetheringConfiguration.
+
+ Note also: the order of this is important. The first upstream type
+ for which a satisfying network exists is used.
+ -->
+ <integer-array translatable="false" name="config_tether_upstream_types">
+ </integer-array>
+
+ <!-- When true, the tethering upstream network follows the current default
+ Internet network (except when the current default network is mobile,
+ in which case a DUN network will be used if required).
+
+ When true, overrides the config_tether_upstream_types setting above.
+ -->
+ <bool translatable="false" name="config_tether_upstream_automatic">true</bool>
+
+
+ <!-- If the mobile hotspot feature requires provisioning, a package name and class name
+ can be provided to launch a supported application that provisions the devices.
+ EntitlementManager will send an inent to Settings with the specified package name and
+ class name in extras to launch provision app.
+ TODO: note what extras here.
+
+ See EntitlementManager#runUiTetherProvisioning and
+ packages/apps/Settings/src/com/android/settings/network/TetherProvisioningActivity.java
+ for more details.
+
+ For ui-less/periodic recheck support see config_mobile_hotspot_provision_app_no_ui
+ -->
+ <!-- The first element is the package name and the second element is the class name
+ of the provisioning app -->
+ <string-array translatable="false" name="config_mobile_hotspot_provision_app">
+ <!--
+ <item>com.example.provisioning</item>
+ <item>com.example.provisioning.Activity</item>
+ -->
+ </string-array>
+
+ <!-- If the mobile hotspot feature requires provisioning, an action can be provided
+ that will be broadcast in non-ui cases for checking the provisioning status.
+ EntitlementManager will pass the specified name to Settings and Settings would
+ launch provisioning app by sending an intent with the package name.
+
+ A second broadcast, action defined by config_mobile_hotspot_provision_response,
+ will be sent back to notify if provisioning succeeded or not. The response will
+ match that of the activity in config_mobile_hotspot_provision_app, but instead
+ contained within the int extra "EntitlementResult".
+ TODO: provide the system api for "EntitlementResult" extra and note it here.
+
+ See EntitlementManager#runSilentTetherProvisioning and
+ packages/apps/Settings/src/com/android/settings/wifi/tether/TetherService.java for more
+ details.
+ -->
+ <string translatable="false" name="config_mobile_hotspot_provision_app_no_ui"></string>
+
+ <!-- Sent in response to a provisioning check. The caller must hold the
+ permission android.permission.TETHER_PRIVILEGED for Settings to
+ receive this response.
+
+ See config_mobile_hotspot_provision_response
+ -->
+ <string translatable="false" name="config_mobile_hotspot_provision_response"></string>
+
+ <!-- Number of hours between each background provisioning call -->
+ <integer translatable="false" name="config_mobile_hotspot_provision_check_period">24</integer>
+
+ <!-- ComponentName of the service used to run no ui tether provisioning. -->
+ <string translatable="false" name="config_wifi_tether_enable">com.android.settings/.wifi.tether.TetherService</string>
</resources>
diff --git a/packages/Tethering/res/values/overlayable.xml b/packages/Tethering/res/values/overlayable.xml
new file mode 100644
index 0000000..e089d9d
--- /dev/null
+++ b/packages/Tethering/res/values/overlayable.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <overlayable name="TetheringConfig">
+ <policy type="product|system|vendor">
+ <item type="array" name="config_tether_usb_regexs"/>
+ <item type="array" name="config_tether_wifi_regexs"/>
+ <item type="array" name="config_tether_wifi_p2p_regexs"/>
+ <item type="array" name="config_tether_bluetooth_regexs"/>
+ <item type="array" name="config_tether_dhcp_range"/>
+ <item type="bool" name="config_tether_enable_legacy_dhcp_server"/>
+ <item type="array" name="config_tether_upstream_types"/>
+ <item type="bool" name="config_tether_upstream_automatic"/>
+ <!-- Configuration values for tethering entitlement check -->
+ <item type="array" name="config_mobile_hotspot_provision_app"/>
+ <item type="string" name="config_mobile_hotspot_provision_app_no_ui"/>
+ <item type="string" name="config_mobile_hotspot_provision_response"/>
+ <item type="integer" name="config_mobile_hotspot_provision_check_period"/>
+ <item type="string" name="config_wifi_tether_enable"/>
+ </policy>
+ </overlayable>
+</resources>
diff --git a/packages/Tethering/res/values/strings.xml b/packages/Tethering/res/values/strings.xml
index ca866a9..792bce9 100644
--- a/packages/Tethering/res/values/strings.xml
+++ b/packages/Tethering/res/values/strings.xml
@@ -1,4 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
<resources>
<!-- Shown when the device is tethered -->
<!-- Strings for tethered notification title [CHAR LIMIT=200] -->
@@ -9,8 +23,11 @@
<!-- This notification is shown when tethering has been disabled on a user's device.
The device is managed by the user's employer. Tethering can't be turned on unless the
IT administrator allows it. The noun "admin" is another reference for "IT administrator." -->
- <!-- Strings for tether disabling notification title [CHAR LIMIT=200] -->
+ <!-- Strings for tether disabling notification title [CHAR LIMIT=200] -->
<string name="disable_tether_notification_title">Tethering is disabled</string>
- <!-- Strings for tether disabling notification message [CHAR LIMIT=200] -->
+ <!-- Strings for tether disabling notification message [CHAR LIMIT=200] -->
<string name="disable_tether_notification_message">Contact your admin for details</string>
+
+ <!-- Strings for tether notification channel name [CHAR LIMIT=200] -->
+ <string name="notification_channel_tethering_status">Hotspot & tethering status</string>
</resources>
\ No newline at end of file
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
index d6abfb9..038d7ae 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
@@ -50,6 +50,7 @@
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import android.app.Notification;
+import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
@@ -106,8 +107,6 @@
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.MessageUtils;
import com.android.internal.util.State;
@@ -663,19 +662,19 @@
if (usbTethered) {
if (wifiTethered || bluetoothTethered) {
- showTetheredNotification(SystemMessage.NOTE_TETHER_GENERAL);
+ showTetheredNotification(R.drawable.stat_sys_tether_general);
} else {
- showTetheredNotification(SystemMessage.NOTE_TETHER_USB);
+ showTetheredNotification(R.drawable.stat_sys_tether_usb);
}
} else if (wifiTethered) {
if (bluetoothTethered) {
- showTetheredNotification(SystemMessage.NOTE_TETHER_GENERAL);
+ showTetheredNotification(R.drawable.stat_sys_tether_general);
} else {
/* We now have a status bar icon for WifiTethering, so drop the notification */
clearTetheredNotification();
}
} else if (bluetoothTethered) {
- showTetheredNotification(SystemMessage.NOTE_TETHER_BLUETOOTH);
+ showTetheredNotification(R.drawable.stat_sys_tether_bluetooth);
} else {
clearTetheredNotification();
}
@@ -688,30 +687,22 @@
@VisibleForTesting
protected void showTetheredNotification(int id, boolean tetheringOn) {
NotificationManager notificationManager =
- (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ (NotificationManager) mContext.createContextAsUser(UserHandle.ALL, 0)
+ .getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager == null) {
return;
}
- int icon = 0;
- switch(id) {
- case SystemMessage.NOTE_TETHER_USB:
- icon = R.drawable.stat_sys_tether_usb;
- break;
- case SystemMessage.NOTE_TETHER_BLUETOOTH:
- icon = R.drawable.stat_sys_tether_bluetooth;
- break;
- case SystemMessage.NOTE_TETHER_GENERAL:
- default:
- icon = R.drawable.stat_sys_tether_general;
- break;
- }
+ final NotificationChannel channel = new NotificationChannel(
+ "TETHERING_STATUS",
+ mContext.getResources().getString(R.string.notification_channel_tethering_status),
+ NotificationManager.IMPORTANCE_LOW);
+ notificationManager.createNotificationChannel(channel);
if (mLastNotificationId != 0) {
- if (mLastNotificationId == icon) {
+ if (mLastNotificationId == id) {
return;
}
- notificationManager.cancelAsUser(null, mLastNotificationId,
- UserHandle.ALL);
+ notificationManager.cancel(null, mLastNotificationId);
mLastNotificationId = 0;
}
@@ -719,8 +710,8 @@
intent.setClassName("com.android.settings", "com.android.settings.TetherSettings");
intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
- PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0, intent, 0,
- null, UserHandle.CURRENT);
+ PendingIntent pi = PendingIntent.getActivity(
+ mContext.createContextAsUser(UserHandle.CURRENT, 0), 0, intent, 0, null);
Resources r = mContext.getResources();
final CharSequence title;
@@ -735,32 +726,31 @@
}
if (mTetheredNotificationBuilder == null) {
- mTetheredNotificationBuilder = new Notification.Builder(mContext,
- SystemNotificationChannels.NETWORK_STATUS);
+ mTetheredNotificationBuilder = new Notification.Builder(mContext, channel.getId());
mTetheredNotificationBuilder.setWhen(0)
.setOngoing(true)
.setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color))
+ android.R.color.system_notification_accent_color))
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setCategory(Notification.CATEGORY_STATUS);
}
- mTetheredNotificationBuilder.setSmallIcon(icon)
+ mTetheredNotificationBuilder.setSmallIcon(id)
.setContentTitle(title)
.setContentText(message)
.setContentIntent(pi);
mLastNotificationId = id;
- notificationManager.notifyAsUser(null, mLastNotificationId,
- mTetheredNotificationBuilder.buildInto(new Notification()), UserHandle.ALL);
+ notificationManager.notify(null, mLastNotificationId,
+ mTetheredNotificationBuilder.buildInto(new Notification()));
}
@VisibleForTesting
protected void clearTetheredNotification() {
NotificationManager notificationManager =
- (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ (NotificationManager) mContext.createContextAsUser(UserHandle.ALL, 0)
+ .getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager != null && mLastNotificationId != 0) {
- notificationManager.cancelAsUser(null, mLastNotificationId,
- UserHandle.ALL);
+ notificationManager.cancel(null, mLastNotificationId);
mLastNotificationId = 0;
}
}
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
index 397ba8a..dbe7892 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -21,7 +21,7 @@
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
-import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import static com.android.internal.R.array.config_mobile_hotspot_provision_app;
import static com.android.internal.R.array.config_tether_bluetooth_regexs;
@@ -33,13 +33,13 @@
import static com.android.internal.R.bool.config_tether_upstream_automatic;
import static com.android.internal.R.integer.config_mobile_hotspot_provision_check_period;
import static com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui;
+import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.net.TetheringConfigurationParcel;
import android.net.util.SharedLog;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -84,6 +84,12 @@
private static final String[] DEFAULT_IPV4_DNS = {"8.8.4.4", "8.8.8.8"};
+ /**
+ * Use the old dnsmasq DHCP server for tethering instead of the framework implementation.
+ */
+ public static final String TETHER_ENABLE_LEGACY_DHCP_SERVER =
+ "tether_enable_legacy_dhcp_server";
+
public final String[] tetherableUsbRegexs;
public final String[] tetherableWifiRegexs;
public final String[] tetherableWifiP2pRegexs;
@@ -122,7 +128,7 @@
legacyDhcpRanges = getLegacyDhcpRanges(res);
defaultIPv4DNS = copy(DEFAULT_IPV4_DNS);
- enableLegacyDhcpServer = getEnableLegacyDhcpServer(ctx);
+ enableLegacyDhcpServer = getEnableLegacyDhcpServer(res);
provisioningApp = getResourceStringArray(res, config_mobile_hotspot_provision_app);
provisioningAppNoUi = getProvisioningAppNoUi(res);
@@ -332,10 +338,14 @@
}
}
- private static boolean getEnableLegacyDhcpServer(Context ctx) {
- final ContentResolver cr = ctx.getContentResolver();
- final int intVal = Settings.Global.getInt(cr, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0);
- return intVal != 0;
+ private boolean getEnableLegacyDhcpServer(final Resources res) {
+ return getResourceBoolean(res, config_tether_enable_legacy_dhcp_server)
+ || getDeviceConfigBoolean(TETHER_ENABLE_LEGACY_DHCP_SERVER);
+ }
+
+ @VisibleForTesting
+ protected boolean getDeviceConfigBoolean(final String name) {
+ return DeviceConfig.getBoolean(NAMESPACE_CONNECTIVITY, name, false /** defaultValue */);
}
private Resources getResources(Context ctx, int subId) {
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index 5692a6f..2875f71 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -23,6 +23,8 @@
import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -215,19 +217,28 @@
mLog.e("registerMobileNetworkRequest() already registered");
return;
}
- // The following use of the legacy type system cannot be removed until
- // after upstream selection no longer finds networks by legacy type.
- // See also http://b/34364553 .
- final int legacyType = mDunRequired ? TYPE_MOBILE_DUN : TYPE_MOBILE_HIPRI;
- final NetworkRequest mobileUpstreamRequest = new NetworkRequest.Builder()
- .setCapabilities(networkCapabilitiesForType(legacyType))
- .build();
+ final NetworkRequest mobileUpstreamRequest;
+ if (mDunRequired) {
+ mobileUpstreamRequest = new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_DUN)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .addTransportType(TRANSPORT_CELLULAR).build();
+ } else {
+ mobileUpstreamRequest = new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addTransportType(TRANSPORT_CELLULAR).build();
+ }
// The existing default network and DUN callbacks will be notified.
// Therefore, to avoid duplicate notifications, we only register a no-op.
mMobileNetworkCallback = new UpstreamNetworkCallback(CALLBACK_MOBILE_REQUEST);
+ // The following use of the legacy type system cannot be removed until
+ // upstream selection no longer finds networks by legacy type.
+ // See also http://b/34364553 .
+ final int legacyType = mDunRequired ? TYPE_MOBILE_DUN : TYPE_MOBILE_HIPRI;
+
// TODO: Change the timeout from 0 (no onUnavailable callback) to some
// moderate callback timeout. This might be useful for updating some UI.
// Additionally, we log a message to aid in any subsequent debugging.
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
index 66eba9a..79bba7f 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
@@ -22,10 +22,12 @@
import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKONWN;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_PROVISION_FAILED;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -39,7 +41,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.net.util.SharedLog;
@@ -49,9 +50,8 @@
import android.os.ResultReceiver;
import android.os.SystemProperties;
import android.os.test.TestLooper;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
import android.telephony.CarrierConfigManager;
-import android.test.mock.MockContentResolver;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -60,7 +60,6 @@
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.internal.util.test.BroadcastInterceptingContext;
-import com.android.internal.util.test.FakeSettingsProvider;
import org.junit.After;
import org.junit.Before;
@@ -94,7 +93,6 @@
private final PersistableBundle mCarrierConfig = new PersistableBundle();
private final TestLooper mLooper = new TestLooper();
private Context mMockContext;
- private MockContentResolver mContentResolver;
private TestStateMachine mSM;
private WrappedEntitlementManager mEnMgr;
@@ -110,11 +108,6 @@
public Resources getResources() {
return mResources;
}
-
- @Override
- public ContentResolver getContentResolver() {
- return mContentResolver;
- }
}
public class WrappedEntitlementManager extends EntitlementManager {
@@ -151,13 +144,17 @@
MockitoAnnotations.initMocks(this);
mMockingSession = mockitoSession()
.initMocks(this)
- .spyStatic(SystemProperties.class)
+ .mockStatic(SystemProperties.class)
+ .mockStatic(DeviceConfig.class)
.strictness(Strictness.WARN)
.startMocking();
// Don't disable tethering provisioning unless requested.
doReturn(false).when(
() -> SystemProperties.getBoolean(
eq(EntitlementManager.DISABLE_PROVISIONING_SYSPROP_KEY), anyBoolean()));
+ doReturn(false).when(
+ () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
+ eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
when(mResources.getStringArray(R.array.config_tether_dhcp_range))
.thenReturn(new String[0]);
@@ -169,10 +166,9 @@
.thenReturn(new String[0]);
when(mResources.getIntArray(R.array.config_tether_upstream_types))
.thenReturn(new int[0]);
+ when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false);
when(mLog.forSubComponent(anyString())).thenReturn(mLog);
- mContentResolver = new MockContentResolver();
- mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
mMockContext = new MockContext(mContext);
mSM = new TestStateMachine();
mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, EVENT_EM_UPDATE);
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
index 7799da4..ef97ad4 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
@@ -21,40 +21,44 @@
import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
import static android.net.ConnectivityManager.TYPE_WIFI;
-import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.internal.R.array.config_mobile_hotspot_provision_app;
import static com.android.internal.R.array.config_tether_bluetooth_regexs;
import static com.android.internal.R.array.config_tether_dhcp_range;
import static com.android.internal.R.array.config_tether_upstream_types;
import static com.android.internal.R.array.config_tether_usb_regexs;
import static com.android.internal.R.array.config_tether_wifi_regexs;
+import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.when;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.net.util.SharedLog;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
import android.telephony.TelephonyManager;
-import android.test.mock.MockContentResolver;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.test.BroadcastInterceptingContext;
-import com.android.internal.util.test.FakeSettingsProvider;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
import java.util.Arrays;
import java.util.Iterator;
@@ -69,9 +73,10 @@
@Mock private TelephonyManager mTelephonyManager;
@Mock private Resources mResources;
@Mock private Resources mResourcesForSubId;
- private MockContentResolver mContentResolver;
private Context mMockContext;
private boolean mHasTelephonyManager;
+ private boolean mEnableLegacyDhcpServer;
+ private MockitoSession mMockingSession;
private class MockTetheringConfiguration extends TetheringConfiguration {
MockTetheringConfiguration(Context ctx, SharedLog log, int id) {
@@ -101,16 +106,20 @@
}
return super.getSystemService(name);
}
-
- @Override
- public ContentResolver getContentResolver() {
- return mContentResolver;
- }
}
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
+ // TODO: use a dependencies class instead of mock statics.
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .mockStatic(DeviceConfig.class)
+ .strictness(Strictness.WARN)
+ .startMocking();
+ doReturn(false).when(
+ () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
+ eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
+
when(mResources.getStringArray(config_tether_dhcp_range)).thenReturn(new String[0]);
when(mResources.getStringArray(config_tether_usb_regexs)).thenReturn(new String[0]);
when(mResources.getStringArray(config_tether_wifi_regexs))
@@ -119,10 +128,15 @@
when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(new int[0]);
when(mResources.getStringArray(config_mobile_hotspot_provision_app))
.thenReturn(new String[0]);
- mContentResolver = new MockContentResolver();
- mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false);
mHasTelephonyManager = true;
mMockContext = new MockContext(mContext);
+ mEnableLegacyDhcpServer = false;
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mMockingSession.finishMocking();
}
private TetheringConfiguration getTetheringConfiguration(int... legacyTetherUpstreamTypes) {
@@ -268,19 +282,35 @@
@Test
public void testNewDhcpServerDisabled() {
- Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 1);
+ when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(true);
+ doReturn(false).when(
+ () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
+ eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
- final TetheringConfiguration cfg = new TetheringConfiguration(
- mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
- assertTrue(cfg.enableLegacyDhcpServer);
+ final TetheringConfiguration enableByRes =
+ new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ assertTrue(enableByRes.enableLegacyDhcpServer);
+
+ when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false);
+ doReturn(true).when(
+ () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
+ eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
+
+ final TetheringConfiguration enableByDevConfig =
+ new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ assertTrue(enableByDevConfig.enableLegacyDhcpServer);
}
@Test
public void testNewDhcpServerEnabled() {
- Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0);
+ when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false);
+ doReturn(false).when(
+ () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
+ eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
- final TetheringConfiguration cfg = new TetheringConfiguration(
- mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ final TetheringConfiguration cfg =
+ new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+
assertFalse(cfg.enableLegacyDhcpServer);
}
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
index 7af48a8..e1fe3bf 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
@@ -35,9 +35,10 @@
import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY;
import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
-import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -224,6 +225,11 @@
if (TelephonyManager.class.equals(serviceClass)) return Context.TELEPHONY_SERVICE;
return super.getSystemServiceName(serviceClass);
}
+
+ @Override
+ public Context createContextAsUser(UserHandle user, int flags) {
+ return mContext;
+ }
}
public class MockIpServerDependencies extends IpServer.Dependencies {
@@ -265,6 +271,11 @@
}
@Override
+ protected boolean getDeviceConfigBoolean(final String name) {
+ return false;
+ }
+
+ @Override
protected Resources getResourcesForSubIdWrapper(Context ctx, int subId) {
return mResources;
}
@@ -423,6 +434,7 @@
.thenReturn(new int[0]);
when(mResources.getBoolean(com.android.internal.R.bool.config_tether_upstream_automatic))
.thenReturn(false);
+ when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false);
when(mNetd.interfaceGetList())
.thenReturn(new String[] {
TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME, TEST_P2P_IFNAME});
@@ -432,9 +444,9 @@
.thenReturn(true);
mServiceContext = new TestContext(mContext);
+ when(mContext.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(null);
mContentResolver = new MockContentResolver(mServiceContext);
mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
- Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0);
mIntents = new Vector<>();
mBroadcastReceiver = new BroadcastReceiver() {
@Override
@@ -686,7 +698,7 @@
@Test
public void workingMobileUsbTethering_IPv4LegacyDhcp() {
- Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 1);
+ when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(true);
sendConfigurationChanged();
final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
runUsbTethering(upstreamState);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index cbff6bd..37ac3ec 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -35,6 +35,7 @@
import android.util.Slog;
import android.view.Display;
+import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
@@ -264,6 +265,24 @@
}
@Override
+ public boolean switchToInputMethod(String imeId) {
+ synchronized (mLock) {
+ if (!hasRightsToCurrentUserLocked()) {
+ return false;
+ }
+ }
+ final boolean result;
+ final int callingUserId = UserHandle.getCallingUserId();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ result = InputMethodManagerInternal.get().switchToInputMethod(imeId, callingUserId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return result;
+ }
+
+ @Override
public boolean isAccessibilityButtonAvailable() {
synchronized (mLock) {
if (!hasRightsToCurrentUserLocked()) {
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index 7dd4a70..7a8a112 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -297,6 +297,11 @@
}
@Override
+ public boolean switchToInputMethod(String imeId) {
+ return false;
+ }
+
+ @Override
public boolean isAccessibilityButtonAvailable() {
return false;
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 03d9626..5405fc7 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -331,8 +331,8 @@
}
@Override // from SystemService
- public boolean isSupported(UserInfo userInfo) {
- return userInfo.isFull() || userInfo.isManagedProfile();
+ public boolean isSupportedUser(TargetUser user) {
+ return user.getUserInfo().isFull() || user.getUserInfo().isManagedProfile();
}
@Override // from SystemService
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index d45a54e..b13bef2 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -1406,10 +1406,7 @@
long oldId = Binder.clearCallingIdentity();
final int[] userIds;
try {
- userIds =
- mContext
- .getSystemService(UserManager.class)
- .getProfileIds(callingUserId, false);
+ userIds = getUserManager().getProfileIds(callingUserId, false);
} finally {
Binder.restoreCallingIdentity(oldId);
}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 27824af..8eca62a 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -175,8 +175,8 @@
}
@Override // from SystemService
- public boolean isSupported(UserInfo userInfo) {
- return userInfo.isFull() || userInfo.isManagedProfile();
+ public boolean isSupportedUser(TargetUser user) {
+ return user.getUserInfo().isFull() || user.getUserInfo().isManagedProfile();
}
@Override // from SystemService
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 76572d3..368416b 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -41,6 +41,7 @@
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
@@ -478,10 +479,12 @@
* will be disabled. Pass in null or an empty list to disable
* all overlays. The order of the items is significant if several
* overlays modify the same resource.
+ * @param outUpdatedPackageNames An output list that contains the package names of packages
+ * affected by the update of enabled overlays.
* @return true if all packages names were known by the package manager, false otherwise
*/
public abstract boolean setEnabledOverlayPackages(int userId, String targetPackageName,
- List<String> overlayPackageNames);
+ List<String> overlayPackageNames, Collection<String> outUpdatedPackageNames);
/**
* Resolves an activity intent, allowing instant apps to be resolved.
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 73b6c7a..7be3d11 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -2079,8 +2079,9 @@
@Override
public long currentNetworkTimeMillis() {
final NtpTrustedTime time = NtpTrustedTime.getInstance(getContext());
- if (time.hasCache()) {
- return time.currentTimeMillis();
+ NtpTrustedTime.TimeResult ntpResult = time.getCachedTimeResult();
+ if (ntpResult != null) {
+ return ntpResult.currentTimeMillis();
} else {
throw new ParcelableException(new DateTimeException("Missing NTP fix"));
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index c2e32d3..26c12c1 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -84,12 +84,12 @@
import android.net.NattSocketKeepalive;
import android.net.Network;
import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkConfig;
import android.net.NetworkFactory;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkMisc;
import android.net.NetworkMonitorManager;
import android.net.NetworkPolicyManager;
import android.net.NetworkProvider;
@@ -364,10 +364,10 @@
private static final int EVENT_PROXY_HAS_CHANGED = 16;
/**
- * used internally when registering NetworkFactories
- * obj = NetworkFactoryInfo
+ * used internally when registering NetworkProviders
+ * obj = NetworkProviderInfo
*/
- private static final int EVENT_REGISTER_NETWORK_FACTORY = 17;
+ private static final int EVENT_REGISTER_NETWORK_PROVIDER = 17;
/**
* used internally when registering NetworkAgents
@@ -403,10 +403,10 @@
private static final int EVENT_RELEASE_NETWORK_REQUEST = 22;
/**
- * used internally when registering NetworkFactories
+ * used internally when registering NetworkProviders
* obj = Messenger
*/
- private static final int EVENT_UNREGISTER_NETWORK_FACTORY = 23;
+ private static final int EVENT_UNREGISTER_NETWORK_PROVIDER = 23;
/**
* used internally to expire a wakelock when transitioning
@@ -2394,9 +2394,9 @@
return;
}
- pw.print("NetworkFactories for:");
- for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
- pw.print(" " + nfi.name);
+ pw.print("NetworkProviders for:");
+ for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
+ pw.print(" " + npi.name);
}
pw.println();
pw.println();
@@ -2631,8 +2631,8 @@
if (nai.everConnected) {
loge("ERROR: cannot call explicitlySelected on already-connected network");
}
- nai.networkMisc.explicitlySelected = toBool(msg.arg1);
- nai.networkMisc.acceptUnvalidated = toBool(msg.arg1) && toBool(msg.arg2);
+ nai.networkAgentConfig.explicitlySelected = toBool(msg.arg1);
+ nai.networkAgentConfig.acceptUnvalidated = toBool(msg.arg1) && toBool(msg.arg2);
// Mark the network as temporarily accepting partial connectivity so that it
// will be validated (and possibly become default) even if it only provides
// partial internet access. Note that if user connects to partial connectivity
@@ -2640,7 +2640,7 @@
// out of wifi coverage) and if the same wifi is available again, the device
// will auto connect to this wifi even though the wifi has "no internet".
// TODO: Evaluate using a separate setting in IpMemoryStore.
- nai.networkMisc.acceptPartialConnectivity = toBool(msg.arg2);
+ nai.networkAgentConfig.acceptPartialConnectivity = toBool(msg.arg2);
break;
}
case NetworkAgent.EVENT_SOCKET_KEEPALIVE: {
@@ -2672,10 +2672,10 @@
}
// Only show the notification when the private DNS is broken and the
// PRIVATE_DNS_BROKEN notification hasn't shown since last valid.
- if (privateDnsBroken && !nai.networkMisc.hasShownBroken) {
+ if (privateDnsBroken && !nai.networkAgentConfig.hasShownBroken) {
showNetworkNotification(nai, NotificationType.PRIVATE_DNS_BROKEN);
}
- nai.networkMisc.hasShownBroken = privateDnsBroken;
+ nai.networkAgentConfig.hasShownBroken = privateDnsBroken;
} else if (nai.networkCapabilities.isPrivateDnsBroken()) {
// If probePrivateDnsCompleted is false but nai.networkCapabilities says
// private DNS is broken, it means this network is being reevaluated.
@@ -2685,7 +2685,7 @@
nai.networkCapabilities.setPrivateDnsBroken(false);
final int oldScore = nai.getCurrentScore();
updateCapabilities(oldScore, nai, nai.networkCapabilities);
- nai.networkMisc.hasShownBroken = false;
+ nai.networkAgentConfig.hasShownBroken = false;
}
break;
}
@@ -2727,7 +2727,7 @@
nai.lastValidated = valid;
nai.everValidated |= valid;
updateCapabilities(oldScore, nai, nai.networkCapabilities);
- // If score has changed, rebroadcast to NetworkFactories. b/17726566
+ // If score has changed, rebroadcast to NetworkProviders. b/17726566
if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
if (valid) {
handleFreshlyValidatedNetwork(nai);
@@ -2744,7 +2744,7 @@
// If network becomes valid, the hasShownBroken should be reset for
// that network so that the notification will be fired when the private
// DNS is broken again.
- nai.networkMisc.hasShownBroken = false;
+ nai.networkAgentConfig.hasShownBroken = false;
}
} else if (partialConnectivityChanged) {
updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities);
@@ -2803,9 +2803,10 @@
loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor");
break;
}
- if (!nai.networkMisc.provisioningNotificationDisabled) {
+ if (!nai.networkAgentConfig.provisioningNotificationDisabled) {
mNotifier.showNotification(netId, NotificationType.SIGN_IN, nai, null,
- (PendingIntent) msg.obj, nai.networkMisc.explicitlySelected);
+ (PendingIntent) msg.obj,
+ nai.networkAgentConfig.explicitlySelected);
}
}
break;
@@ -2842,6 +2843,7 @@
return true;
}
+ // TODO: delete when direct use of registerNetworkFactory is no longer supported.
private boolean maybeHandleNetworkFactoryMessage(Message msg) {
switch (msg.what) {
default:
@@ -3031,16 +3033,16 @@
private void handleAsyncChannelHalfConnect(Message msg) {
ensureRunningOnConnectivityServiceThread();
final AsyncChannel ac = (AsyncChannel) msg.obj;
- if (mNetworkFactoryInfos.containsKey(msg.replyTo)) {
+ if (mNetworkProviderInfos.containsKey(msg.replyTo)) {
if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
if (VDBG) log("NetworkFactory connected");
// Finish setting up the full connection
- NetworkFactoryInfo nfi = mNetworkFactoryInfos.get(msg.replyTo);
- nfi.completeConnection();
- sendAllRequestsToFactory(nfi);
+ NetworkProviderInfo npi = mNetworkProviderInfos.get(msg.replyTo);
+ npi.completeConnection();
+ sendAllRequestsToProvider(npi);
} else {
loge("Error connecting NetworkFactory");
- mNetworkFactoryInfos.remove(msg.obj);
+ mNetworkProviderInfos.remove(msg.obj);
}
} else if (mNetworkAgentInfos.containsKey(msg.replyTo)) {
if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
@@ -3072,8 +3074,8 @@
if (nai != null) {
disconnectAndDestroyNetwork(nai);
} else {
- NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(msg.replyTo);
- if (DBG && nfi != null) log("unregisterNetworkFactory for " + nfi.name);
+ NetworkProviderInfo npi = mNetworkProviderInfos.remove(msg.replyTo);
+ if (DBG && npi != null) log("unregisterNetworkFactory for " + npi.name);
}
}
@@ -3156,7 +3158,7 @@
// ip[6]tables to flush routes and remove the incoming packet mark rule, so do it
// after we've rematched networks with requests which should make a potential
// fallback network the default or requested a new network from the
- // NetworkFactories, so network traffic isn't interrupted for an unnecessarily
+ // NetworkProviders, so network traffic isn't interrupted for an unnecessarily
// long time.
destroyNativeNetwork(nai);
mDnsManager.removeNetwork(nai.network);
@@ -3169,8 +3171,8 @@
// This should never fail. Specifying an already in use NetID will cause failure.
if (networkAgent.isVPN()) {
mNetd.networkCreateVpn(networkAgent.network.netId,
- (networkAgent.networkMisc == null
- || !networkAgent.networkMisc.allowBypass));
+ (networkAgent.networkAgentConfig == null
+ || !networkAgent.networkAgentConfig.allowBypass));
} else {
mNetd.networkCreatePhysical(networkAgent.network.netId,
getNetworkPermission(networkAgent.networkCapabilities));
@@ -3419,8 +3421,8 @@
}
}
- for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
- nfi.cancelRequest(nri.request);
+ for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
+ npi.cancelRequest(nri.request);
}
} else {
// listens don't have a singular affectedNetwork. Check all networks to see
@@ -3470,16 +3472,16 @@
return;
}
- if (!nai.networkMisc.explicitlySelected) {
+ if (!nai.networkAgentConfig.explicitlySelected) {
Slog.wtf(TAG, "BUG: setAcceptUnvalidated non non-explicitly selected network");
}
- if (accept != nai.networkMisc.acceptUnvalidated) {
- nai.networkMisc.acceptUnvalidated = accept;
+ if (accept != nai.networkAgentConfig.acceptUnvalidated) {
+ nai.networkAgentConfig.acceptUnvalidated = accept;
// If network becomes partial connectivity and user already accepted to use this
// network, we should respect the user's option and don't need to popup the
// PARTIAL_CONNECTIVITY notification to user again.
- nai.networkMisc.acceptPartialConnectivity = accept;
+ nai.networkAgentConfig.acceptPartialConnectivity = accept;
rematchAllNetworksAndRequests();
sendUpdatedScoreToFactories(nai);
}
@@ -3516,8 +3518,8 @@
return;
}
- if (accept != nai.networkMisc.acceptPartialConnectivity) {
- nai.networkMisc.acceptPartialConnectivity = accept;
+ if (accept != nai.networkAgentConfig.acceptPartialConnectivity) {
+ nai.networkAgentConfig.acceptPartialConnectivity = accept;
}
// TODO: Use the current design or save the user choice into IpMemoryStore.
@@ -3727,7 +3729,7 @@
action = ConnectivityManager.ACTION_PROMPT_PARTIAL_CONNECTIVITY;
// Don't bother the user with a high-priority notification if the network was not
// explicitly selected by the user.
- highPriority = nai.networkMisc.explicitlySelected;
+ highPriority = nai.networkAgentConfig.explicitlySelected;
break;
default:
Slog.wtf(TAG, "Unknown notification type " + type);
@@ -3760,14 +3762,15 @@
// automatically connects to a network that has partial Internet access, the user will
// always be able to use it, either because they've already chosen "don't ask again" or
// because we have prompt them.
- if (nai.partialConnectivity && !nai.networkMisc.acceptPartialConnectivity) {
+ if (nai.partialConnectivity && !nai.networkAgentConfig.acceptPartialConnectivity) {
return true;
}
// If a network has no Internet access, only prompt if the network was explicitly selected
// and if the user has not already told us to use the network regardless of whether it
// validated or not.
- if (nai.networkMisc.explicitlySelected && !nai.networkMisc.acceptUnvalidated) {
+ if (nai.networkAgentConfig.explicitlySelected
+ && !nai.networkAgentConfig.acceptUnvalidated) {
return true;
}
@@ -3855,12 +3858,12 @@
handleApplyDefaultProxy((ProxyInfo)msg.obj);
break;
}
- case EVENT_REGISTER_NETWORK_FACTORY: {
- handleRegisterNetworkFactory((NetworkFactoryInfo)msg.obj);
+ case EVENT_REGISTER_NETWORK_PROVIDER: {
+ handleRegisterNetworkProvider((NetworkProviderInfo) msg.obj);
break;
}
- case EVENT_UNREGISTER_NETWORK_FACTORY: {
- handleUnregisterNetworkFactory((Messenger)msg.obj);
+ case EVENT_UNREGISTER_NETWORK_PROVIDER: {
+ handleUnregisterNetworkProvider((Messenger) msg.obj);
break;
}
case EVENT_REGISTER_NETWORK_AGENT: {
@@ -4905,7 +4908,7 @@
}
};
- private final HashMap<Messenger, NetworkFactoryInfo> mNetworkFactoryInfos = new HashMap<>();
+ private final HashMap<Messenger, NetworkProviderInfo> mNetworkProviderInfos = new HashMap<>();
private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests = new HashMap<>();
private static final int MAX_NETWORK_REQUESTS_PER_UID = 100;
@@ -4913,18 +4916,18 @@
@GuardedBy("mUidToNetworkRequestCount")
private final SparseIntArray mUidToNetworkRequestCount = new SparseIntArray();
- private static class NetworkFactoryInfo {
+ private static class NetworkProviderInfo {
public final String name;
public final Messenger messenger;
private final AsyncChannel mAsyncChannel;
private final IBinder.DeathRecipient mDeathRecipient;
- public final int factorySerialNumber;
+ public final int providerId;
- NetworkFactoryInfo(String name, Messenger messenger, AsyncChannel asyncChannel,
- int factorySerialNumber, IBinder.DeathRecipient deathRecipient) {
+ NetworkProviderInfo(String name, Messenger messenger, AsyncChannel asyncChannel,
+ int providerId, IBinder.DeathRecipient deathRecipient) {
this.name = name;
this.messenger = messenger;
- this.factorySerialNumber = factorySerialNumber;
+ this.providerId = providerId;
mAsyncChannel = asyncChannel;
mDeathRecipient = deathRecipient;
@@ -4942,17 +4945,17 @@
messenger.send(Message.obtain(null /* handler */, what, arg1, arg2, obj));
} catch (RemoteException e) {
// Remote process died. Ignore; the death recipient will remove this
- // NetworkFactoryInfo from mNetworkFactoryInfos.
+ // NetworkProviderInfo from mNetworkProviderInfos.
}
}
- void requestNetwork(NetworkRequest request, int score, int servingSerialNumber) {
+ void requestNetwork(NetworkRequest request, int score, int servingProviderId) {
if (isLegacyNetworkFactory()) {
mAsyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score,
- servingSerialNumber, request);
+ servingProviderId, request);
} else {
sendMessageToNetworkProvider(NetworkProvider.CMD_REQUEST_NETWORK, score,
- servingSerialNumber, request);
+ servingProviderId, request);
}
}
@@ -5365,45 +5368,45 @@
@Override
public int registerNetworkFactory(Messenger messenger, String name) {
enforceNetworkFactoryPermission();
- NetworkFactoryInfo nfi = new NetworkFactoryInfo(name, messenger, new AsyncChannel(),
+ NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger, new AsyncChannel(),
nextNetworkProviderId(), null /* deathRecipient */);
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_FACTORY, nfi));
- return nfi.factorySerialNumber;
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_PROVIDER, npi));
+ return npi.providerId;
}
- private void handleRegisterNetworkFactory(NetworkFactoryInfo nfi) {
- if (mNetworkFactoryInfos.containsKey(nfi.messenger)) {
+ private void handleRegisterNetworkProvider(NetworkProviderInfo npi) {
+ if (mNetworkProviderInfos.containsKey(npi.messenger)) {
// Avoid creating duplicates. even if an app makes a direct AIDL call.
// This will never happen if an app calls ConnectivityManager#registerNetworkProvider,
// as that will throw if a duplicate provider is registered.
- Slog.e(TAG, "Attempt to register existing NetworkFactoryInfo "
- + mNetworkFactoryInfos.get(nfi.messenger).name);
+ Slog.e(TAG, "Attempt to register existing NetworkProviderInfo "
+ + mNetworkProviderInfos.get(npi.messenger).name);
return;
}
- if (DBG) log("Got NetworkFactory Messenger for " + nfi.name);
- mNetworkFactoryInfos.put(nfi.messenger, nfi);
- nfi.connect(mContext, mTrackerHandler);
- if (!nfi.isLegacyNetworkFactory()) {
+ if (DBG) log("Got NetworkProvider Messenger for " + npi.name);
+ mNetworkProviderInfos.put(npi.messenger, npi);
+ npi.connect(mContext, mTrackerHandler);
+ if (!npi.isLegacyNetworkFactory()) {
// Legacy NetworkFactories get their requests when their AsyncChannel connects.
- sendAllRequestsToFactory(nfi);
+ sendAllRequestsToProvider(npi);
}
}
@Override
public int registerNetworkProvider(Messenger messenger, String name) {
enforceNetworkFactoryPermission();
- NetworkFactoryInfo nfi = new NetworkFactoryInfo(name, messenger,
+ NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger,
null /* asyncChannel */, nextNetworkProviderId(),
() -> unregisterNetworkProvider(messenger));
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_FACTORY, nfi));
- return nfi.factorySerialNumber;
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_PROVIDER, npi));
+ return npi.providerId;
}
@Override
public void unregisterNetworkProvider(Messenger messenger) {
enforceNetworkFactoryPermission();
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_FACTORY, messenger));
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_PROVIDER, messenger));
}
@Override
@@ -5411,13 +5414,13 @@
unregisterNetworkProvider(messenger);
}
- private void handleUnregisterNetworkFactory(Messenger messenger) {
- NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(messenger);
- if (nfi == null) {
- loge("Failed to find Messenger in unregisterNetworkFactory");
+ private void handleUnregisterNetworkProvider(Messenger messenger) {
+ NetworkProviderInfo npi = mNetworkProviderInfos.remove(messenger);
+ if (npi == null) {
+ loge("Failed to find Messenger in unregisterNetworkProvider");
return;
}
- if (DBG) log("unregisterNetworkFactory for " + nfi.name);
+ if (DBG) log("unregisterNetworkProvider for " + npi.name);
}
@Override
@@ -5489,9 +5492,9 @@
// tree.
public int registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
- int currentScore, NetworkMisc networkMisc) {
+ int currentScore, NetworkAgentConfig networkAgentConfig) {
return registerNetworkAgent(messenger, networkInfo, linkProperties, networkCapabilities,
- currentScore, networkMisc, NetworkFactory.SerialNumber.NONE);
+ currentScore, networkAgentConfig, NetworkProvider.ID_NONE);
}
/**
@@ -5506,12 +5509,12 @@
* later : see {@link #updateCapabilities}.
* @param currentScore the initial score of the network. See
* {@link NetworkAgentInfo#getCurrentScore}.
- * @param networkMisc metadata about the network. This is never updated.
- * @param factorySerialNumber the serial number of the factory owning this NetworkAgent.
+ * @param networkAgentConfig metadata about the network. This is never updated.
+ * @param providerId the ID of the provider owning this NetworkAgent.
*/
public int registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
- int currentScore, NetworkMisc networkMisc, int factorySerialNumber) {
+ int currentScore, NetworkAgentConfig networkAgentConfig, int providerId) {
enforceNetworkFactoryPermission();
LinkProperties lp = new LinkProperties(linkProperties);
@@ -5523,8 +5526,8 @@
ns.putIntExtension(NetworkScore.LEGACY_SCORE, currentScore);
final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc,
- ns, mContext, mTrackerHandler, new NetworkMisc(networkMisc), this, mNetd,
- mDnsResolver, mNMS, factorySerialNumber);
+ ns, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig), this,
+ mNetd, mDnsResolver, mNMS, providerId);
// Make sure the network capabilities reflect what the agent info says.
nai.getAndSetNetworkCapabilities(mixInCapabilities(nai, nc));
final String extraInfo = networkInfo.getExtraInfo();
@@ -5803,7 +5806,7 @@
// Once a NetworkAgent is connected, complain if some immutable capabilities are removed.
// Don't complain for VPNs since they're not driven by requests and there is no risk of
// causing a connect/teardown loop.
- // TODO: remove this altogether and make it the responsibility of the NetworkFactories to
+ // TODO: remove this altogether and make it the responsibility of the NetworkProviders to
// avoid connect/teardown loops.
if (nai.everConnected &&
!nai.isVPN() &&
@@ -5945,7 +5948,7 @@
LinkProperties lp) {
if (nc == null || lp == null) return false;
return nai.isVPN()
- && !nai.networkMisc.allowBypass
+ && !nai.networkAgentConfig.allowBypass
&& nc.getEstablishingVpnAppUid() != Process.SYSTEM_UID
&& lp.getInterfaceName() != null
&& (lp.hasIPv4DefaultRoute() || lp.hasIPv6DefaultRoute());
@@ -6043,13 +6046,13 @@
if (VDBG || DDBG){
log("sending new Min Network Score(" + score + "): " + networkRequest.toString());
}
- for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
- nfi.requestNetwork(networkRequest, score, serial);
+ for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
+ npi.requestNetwork(networkRequest, score, serial);
}
}
/** Sends all current NetworkRequests to the specified factory. */
- private void sendAllRequestsToFactory(NetworkFactoryInfo nfi) {
+ private void sendAllRequestsToProvider(NetworkProviderInfo npi) {
ensureRunningOnConnectivityServiceThread();
for (NetworkRequestInfo nri : mNetworkRequests.values()) {
if (nri.request.isListen()) continue;
@@ -6061,9 +6064,9 @@
serial = nai.factorySerialNumber;
} else {
score = 0;
- serial = NetworkFactory.SerialNumber.NONE;
+ serial = NetworkProvider.ID_NONE;
}
- nfi.requestNetwork(nri.request, score, serial);
+ npi.requestNetwork(nri.request, score, serial);
}
}
@@ -6344,11 +6347,11 @@
Slog.wtf(TAG, "BUG: " + newSatisfier.name() + " already has " + nri.request);
}
addedRequests.add(nri);
- // Tell NetworkFactories about the new score, so they can stop
+ // Tell NetworkProviders about the new score, so they can stop
// trying to connect if they know they cannot match it.
// TODO - this could get expensive if we have a lot of requests for this
// network. Think about if there is a way to reduce this. Push
- // netid->request mapping to each factory?
+ // netid->request mapping to each provider?
sendUpdatedScoreToFactories(nri.request, newSatisfier);
if (isDefaultRequest(nri)) {
isNewDefault = true;
@@ -6377,7 +6380,7 @@
} else {
Slog.wtf(TAG, "BUG: Removing request " + nri.request.requestId + " from " +
newNetwork.name() +
- " without updating mSatisfier or factories!");
+ " without updating mSatisfier or providers!");
}
// TODO: Technically, sending CALLBACK_LOST here is
// incorrect if there is a replacement network currently
@@ -6636,7 +6639,7 @@
// command must be sent after updating LinkProperties to maximize chances of
// NetworkMonitor seeing the correct LinkProperties when starting.
// TODO: pass LinkProperties to the NetworkMonitor in the notifyNetworkConnected call.
- if (networkAgent.networkMisc.acceptPartialConnectivity) {
+ if (networkAgent.networkAgentConfig.acceptPartialConnectivity) {
networkAgent.networkMonitor().setAcceptPartialConnectivity();
}
networkAgent.networkMonitor().notifyNetworkConnected(
diff --git a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
index d20936c..7894788 100644
--- a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
@@ -154,17 +154,20 @@
private void onPollNetworkTimeUnderWakeLock(int event) {
// Force an NTP fix when outdated
- if (mTime.getCacheAge() >= mPollingIntervalMs) {
+ NtpTrustedTime.TimeResult cachedNtpResult = mTime.getCachedTimeResult();
+ if (cachedNtpResult == null || cachedNtpResult.getAgeMillis() >= mPollingIntervalMs) {
if (DBG) Log.d(TAG, "Stale NTP fix; forcing refresh");
mTime.forceRefresh();
+ cachedNtpResult = mTime.getCachedTimeResult();
}
- if (mTime.getCacheAge() < mPollingIntervalMs) {
+ if (cachedNtpResult != null && cachedNtpResult.getAgeMillis() < mPollingIntervalMs) {
// Obtained fresh fix; schedule next normal update
resetAlarm(mPollingIntervalMs);
// Suggest the time to the time detector. It may choose use it to set the system clock.
- TimestampedValue<Long> timeSignal = mTime.getCachedNtpTimeSignal();
+ TimestampedValue<Long> timeSignal = new TimestampedValue<>(
+ cachedNtpResult.getElapsedRealtimeMillis(), cachedNtpResult.getTimeMillis());
NetworkTimeSuggestion timeSuggestion = new NetworkTimeSuggestion(timeSignal);
timeSuggestion.addDebugInfo("Origin: NetworkTimeUpdateServiceImpl. event=" + event);
mTimeDetector.suggestNetworkTime(timeSuggestion);
@@ -275,8 +278,11 @@
TimeUtils.formatDuration(mPollingIntervalShorterMs, pw);
pw.println("\nTryAgainTimesMax: " + mTryAgainTimesMax);
pw.println("\nTryAgainCounter: " + mTryAgainCounter);
- pw.println("NTP cache age: " + mTime.getCacheAge());
- pw.println("NTP cache certainty: " + mTime.getCacheCertainty());
+ NtpTrustedTime.TimeResult ntpResult = mTime.getCachedTimeResult();
+ pw.println("NTP cache result: " + ntpResult);
+ if (ntpResult != null) {
+ pw.println("NTP result age: " + ntpResult.getAgeMillis());
+ }
pw.println();
}
}
diff --git a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
index 190fff1..21fa9f9 100644
--- a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
+++ b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
@@ -46,4 +46,7 @@
/** Update the OEM unlock enabled bit, bypassing user restriction checks. */
void forceOemUnlockEnabled(boolean enabled);
+
+ /** Retrieves the UID that can access the persistent data partition. */
+ int getAllowedUid();
}
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 73c8520..00d8b0f 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -680,6 +680,11 @@
writeDataBuffer(getTestHarnessModeDataOffset(), ByteBuffer.allocate(size));
}
+ @Override
+ public int getAllowedUid() {
+ return mAllowedUid;
+ }
+
private void writeInternal(byte[] data, long offset, int dataLength) {
checkArgument(data == null || data.length > 0, "data must be null or non-empty");
checkArgument(
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index c5409f85..f46b9ae 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -18,18 +18,23 @@
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT;
+import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityThread;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.IBinder;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.os.UserManager;
import com.android.server.pm.UserManagerService;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
@@ -57,15 +62,17 @@
*
* {@hide}
*/
+//@SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER)
public abstract class SystemService {
+ /** @hide */
// TODO(b/133242016) STOPSHIP: change to false before R ships
protected static final boolean DEBUG_USER = true;
/*
- * Boot Phases
+ * The earliest boot phase the system send to system services on boot.
*/
- public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100; // maybe should be a dependency?
+ public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100;
/**
* After receiving this boot phase, services can obtain lock settings data.
@@ -98,13 +105,70 @@
* After receiving this boot phase, services can allow user interaction with the device.
* This phase occurs when boot has completed and the home application has started.
* System services may prefer to listen to this phase rather than registering a
- * broadcast receiver for ACTION_BOOT_COMPLETED to reduce overall latency.
+ * broadcast receiver for {@link android.content.Intent#ACTION_LOCKED_BOOT_COMPLETED}
+ * to reduce overall latency.
*/
public static final int PHASE_BOOT_COMPLETED = 1000;
+ /** @hide */
+ @IntDef(flag = true, prefix = { "PHASE_" }, value = {
+ PHASE_WAIT_FOR_DEFAULT_DISPLAY,
+ PHASE_LOCK_SETTINGS_READY,
+ PHASE_SYSTEM_SERVICES_READY,
+ PHASE_DEVICE_SPECIFIC_SERVICES_READY,
+ PHASE_ACTIVITY_MANAGER_READY,
+ PHASE_THIRD_PARTY_APPS_CAN_START,
+ PHASE_BOOT_COMPLETED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BootPhase {}
+
private final Context mContext;
/**
+ * Class representing user in question in the lifecycle callbacks.
+ * @hide
+ */
+ //@SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER)
+ public static final class TargetUser {
+ @NonNull
+ private final UserInfo mUserInfo;
+
+ /** @hide */
+ public TargetUser(@NonNull UserInfo userInfo) {
+ mUserInfo = userInfo;
+ }
+
+ /**
+ * @return The information about the user. <b>NOTE: </b> this is a "live" object
+ * referenced by {@link UserManagerService} and hence should not be modified.
+ *
+ * @hide
+ */
+ @NonNull
+ public UserInfo getUserInfo() {
+ return mUserInfo;
+ }
+
+ /**
+ * @return the target {@link UserHandle}.
+ */
+ @NonNull
+ public UserHandle getUserHandle() {
+ return mUserInfo.getUserHandle();
+ }
+
+ /**
+ * @return the integer user id
+ *
+ * @hide
+ */
+ public int getUserIdentifier() {
+ return mUserInfo.id;
+ }
+ }
+
+ /**
* Initializes the system service.
* <p>
* Subclasses must define a single argument constructor that accepts the context
@@ -113,13 +177,14 @@
*
* @param context The system server context.
*/
- public SystemService(Context context) {
+ public SystemService(@NonNull Context context) {
mContext = context;
}
/**
* Gets the system context.
*/
+ @NonNull
public final Context getContext() {
return mContext;
}
@@ -128,6 +193,8 @@
* Get the system UI context. This context is to be used for displaying UI. It is themable,
* which means resources can be overridden at runtime. Do not use to retrieve properties that
* configure the behavior of the device that is not UX related.
+ *
+ * @hide
*/
public final Context getUiContext() {
// This has already been set up by the time any SystemServices are created.
@@ -137,15 +204,16 @@
/**
* Returns true if the system is running in safe mode.
* TODO: we should define in which phase this becomes valid
+ *
+ * @hide
*/
public final boolean isSafeMode() {
return getManager().isSafeMode();
}
/**
- * Called when the dependencies listed in the @Service class-annotation are available
- * and after the chosen start phase.
- * When this method returns, the service should be published.
+ * Called when the system service should publish a binder service using
+ * {@link #publishBinderService(String, IBinder).}
*/
public abstract void onStart();
@@ -155,7 +223,7 @@
*
* @param phase The current boot phase.
*/
- public void onBootPhase(int phase) {}
+ public void onBootPhase(@BootPhase int phase) {}
/**
* Checks if the service should be available for the given user.
@@ -163,12 +231,14 @@
* <p>By default returns {@code true}, but subclasses should extend for optimization, if they
* don't support some types (like headless system user).
*/
- public boolean isSupported(@NonNull UserInfo userInfo) {
+ public boolean isSupportedUser(@NonNull TargetUser user) {
return true;
}
/**
- * Helper method used to dump which users are {@link #onStartUser(UserInfo) supported}.
+ * Helper method used to dump which users are {@link #onStartUser(TargetUser) supported}.
+ *
+ * @hide
*/
protected void dumpSupportedUsers(@NonNull PrintWriter pw, @NonNull String prefix) {
final List<UserInfo> allUsers = UserManager.get(mContext).getUsers();
@@ -187,34 +257,59 @@
}
/**
- * @deprecated subclasses should extend {@link #onStartUser(UserInfo)} instead (which by default
- * calls this method).
+ * @deprecated subclasses should extend {@link #onStartUser(TargetUser)} instead
+ * (which by default calls this method).
+ *
+ * @hide
*/
@Deprecated
public void onStartUser(@UserIdInt int userId) {}
/**
- * Called when a new user is starting, for system services to initialize any per-user
- * state they maintain for running users.
+ * @deprecated subclasses should extend {@link #onStartUser(TargetUser)} instead
+ * (which by default calls this method).
*
- * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this
- * user.
- *
- * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
- * referenced by {@link UserManagerService} and hence should not be modified.
+ * @hide
*/
+ @Deprecated
public void onStartUser(@NonNull UserInfo userInfo) {
onStartUser(userInfo.id);
}
/**
- * @deprecated subclasses should extend {@link #onUnlockUser(UserInfo)} instead (which by
+ * Called when a new user is starting, for system services to initialize any per-user
+ * state they maintain for running users.
+ *
+ * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports}
+ * this user.
+ *
+ * @param user target user
+ */
+ public void onStartUser(@NonNull TargetUser user) {
+ onStartUser(user.getUserInfo());
+ }
+
+ /**
+ * @deprecated subclasses should extend {@link #onUnlockUser(TargetUser)} instead (which by
* default calls this method).
+ *
+ * @hide
*/
@Deprecated
public void onUnlockUser(@UserIdInt int userId) {}
/**
+ * @deprecated subclasses should extend {@link #onUnlockUser(TargetUser)} instead (which by
+ * default calls this method).
+ *
+ * @hide
+ */
+ @Deprecated
+ public void onUnlockUser(@NonNull UserInfo userInfo) {
+ onUnlockUser(userInfo.id);
+ }
+
+ /**
* Called when an existing user is in the process of being unlocked. This
* means the credential-encrypted storage for that user is now available,
* and encryption-aware component filtering is no longer in effect.
@@ -226,90 +321,127 @@
* {@link UserManager#isUserUnlockingOrUnlocked(int)} to handle both of
* these states.
* <p>
- * This method is only called when the service {@link #isSupported(UserInfo) supports} this
- * user.
+ * This method is only called when the service {@link #isSupportedUser(TargetUser) supports}
+ * this user.
*
- * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
- * referenced by {@link UserManagerService} and hence should not be modified.
+ * @param user target user
*/
- public void onUnlockUser(@NonNull UserInfo userInfo) {
- onUnlockUser(userInfo.id);
+ public void onUnlockUser(@NonNull TargetUser user) {
+ onUnlockUser(user.getUserInfo());
}
/**
- * @deprecated subclasses should extend {@link #onSwitchUser(UserInfo, UserInfo)} instead
+ * @deprecated subclasses should extend {@link #onSwitchUser(TargetUser, TargetUser)} instead
* (which by default calls this method).
+ *
+ * @hide
*/
@Deprecated
- public void onSwitchUser(@UserIdInt int userId) {}
+ public void onSwitchUser(@UserIdInt int toUserId) {}
+
+ /**
+ * @deprecated subclasses should extend {@link #onSwitchUser(TargetUser, TargetUser)} instead
+ * (which by default calls this method).
+ *
+ * @hide
+ */
+ @Deprecated
+ public void onSwitchUser(@Nullable UserInfo from, @NonNull UserInfo to) {
+ onSwitchUser(to.id);
+ }
/**
* Called when switching to a different foreground user, for system services that have
* special behavior for whichever user is currently in the foreground. This is called
* before any application processes are aware of the new user.
*
- * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} either
- * of the users ({@code from} or {@code to}).
+ * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports}
+ * either of the users ({@code from} or {@code to}).
*
* <b>NOTE: </b> both {@code from} and {@code to} are "live" objects
* referenced by {@link UserManagerService} and hence should not be modified.
*
- * @param from The information about the user being switched from.
- * @param to The information about the user being switched from to.
+ * @param from the user switching from
+ * @param to the user switching to
*/
- public void onSwitchUser(@NonNull UserInfo from, @NonNull UserInfo to) {
- onSwitchUser(to.id);
+ public void onSwitchUser(@Nullable TargetUser from, @NonNull TargetUser to) {
+ onSwitchUser((from == null ? null : from.getUserInfo()), to.getUserInfo());
}
/**
- * @deprecated subclasses should extend {@link #onStopUser(UserInfo)} instead (which by default
- * calls this method).
+ * @deprecated subclasses should extend {@link #onStopUser(TargetUser)} instead
+ * (which by default calls this method).
+ *
+ * @hide
*/
@Deprecated
public void onStopUser(@UserIdInt int userId) {}
/**
+ * @deprecated subclasses should extend {@link #onStopUser(TargetUser)} instead
+ * (which by default calls this method).
+ *
+ * @hide
+ */
+ @Deprecated
+ public void onStopUser(@NonNull UserInfo user) {
+ onStopUser(user.id);
+
+ }
+
+ /**
* Called when an existing user is stopping, for system services to finalize any per-user
* state they maintain for running users. This is called prior to sending the SHUTDOWN
* broadcast to the user; it is a good place to stop making use of any resources of that
* user (such as binding to a service running in the user).
*
- * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this
- * user.
+ * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports}
+ * this user.
*
* <p>NOTE: This is the last callback where the callee may access the target user's CE storage.
*
- * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
- * referenced by {@link UserManagerService} and hence should not be modified.
+ * @param user target user
*/
- public void onStopUser(@NonNull UserInfo userInfo) {
- onStopUser(userInfo.id);
+ public void onStopUser(@NonNull TargetUser user) {
+ onStopUser(user.getUserInfo());
}
/**
- * @deprecated subclasses should extend {@link #onCleanupUser(UserInfo)} instead (which by
+ * @deprecated subclasses should extend {@link #onCleanupUser(TargetUser)} instead (which by
* default calls this method).
+ *
+ * @hide
*/
@Deprecated
public void onCleanupUser(@UserIdInt int userId) {}
/**
+ * @deprecated subclasses should extend {@link #onCleanupUser(TargetUser)} instead (which by
+ * default calls this method).
+ *
+ * @hide
+ */
+ @Deprecated
+ public void onCleanupUser(@NonNull UserInfo user) {
+ onCleanupUser(user.id);
+ }
+
+ /**
* Called when an existing user is stopping, for system services to finalize any per-user
* state they maintain for running users. This is called after all application process
* teardown of the user is complete.
*
- * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this
- * user.
+ * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports}
+ * this user.
*
* <p>NOTE: When this callback is called, the CE storage for the target user may not be
- * accessible already. Use {@link #onStopUser(UserInfo)} instead if you need to access the CE
+ * accessible already. Use {@link #onStopUser(TargetUser)} instead if you need to access the CE
* storage.
*
- * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
- * referenced by {@link UserManagerService} and hence should not be modified.
+ * @param user target user
*/
- public void onCleanupUser(@NonNull UserInfo userInfo) {
- onCleanupUser(userInfo.id);
+ public void onCleanupUser(@NonNull TargetUser user) {
+ onCleanupUser(user.getUserInfo());
}
/**
@@ -318,7 +450,7 @@
* @param name the name of the new service
* @param service the service object
*/
- protected final void publishBinderService(String name, IBinder service) {
+ protected final void publishBinderService(@NonNull String name, @NonNull IBinder service) {
publishBinderService(name, service, false);
}
@@ -330,7 +462,7 @@
* @param allowIsolated set to true to allow isolated sandboxed processes
* to access this service
*/
- protected final void publishBinderService(String name, IBinder service,
+ protected final void publishBinderService(@NonNull String name, @NonNull IBinder service,
boolean allowIsolated) {
publishBinderService(name, service, allowIsolated, DUMP_FLAG_PRIORITY_DEFAULT);
}
@@ -343,6 +475,8 @@
* @param allowIsolated set to true to allow isolated sandboxed processes
* to access this service
* @param dumpPriority supported dump priority levels as a bitmask
+ *
+ * @hide
*/
protected final void publishBinderService(String name, IBinder service,
boolean allowIsolated, int dumpPriority) {
@@ -351,6 +485,8 @@
/**
* Get a binder service by its name.
+ *
+ * @hide
*/
protected final IBinder getBinderService(String name) {
return ServiceManager.getService(name);
@@ -358,6 +494,8 @@
/**
* Publish the service so it is only accessible to the system process.
+ *
+ * @hide
*/
protected final <T> void publishLocalService(Class<T> type, T service) {
LocalServices.addService(type, service);
@@ -365,6 +503,8 @@
/**
* Get a local service by interface.
+ *
+ * @hide
*/
protected final <T> T getLocalService(Class<T> type) {
return LocalServices.getService(type);
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index c715798..c0f43a8 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -27,6 +27,7 @@
import android.os.UserManagerInternal;
import android.util.Slog;
+import com.android.server.SystemService.TargetUser;
import com.android.server.utils.TimingsTraceAndSlog;
import java.io.File;
@@ -264,26 +265,26 @@
@UserIdInt int curUserId, @UserIdInt int prevUserId) {
t.traceBegin("ssm." + onWhat + "User-" + curUserId);
Slog.i(TAG, "Calling on" + onWhat + "User " + curUserId);
- final UserInfo curUserInfo = getUserInfo(curUserId);
- final UserInfo prevUserInfo = prevUserId == UserHandle.USER_NULL ? null
- : getUserInfo(prevUserId);
+ final TargetUser curUser = new TargetUser(getUserInfo(curUserId));
+ final TargetUser prevUser = prevUserId == UserHandle.USER_NULL ? null
+ : new TargetUser(getUserInfo(prevUserId));
final int serviceLen = mServices.size();
for (int i = 0; i < serviceLen; i++) {
final SystemService service = mServices.get(i);
final String serviceName = service.getClass().getName();
- boolean supported = service.isSupported(curUserInfo);
+ boolean supported = service.isSupportedUser(curUser);
// Must check if either curUser or prevUser is supported (for example, if switching from
// unsupported to supported, we still need to notify the services)
- if (!supported && prevUserInfo != null) {
- supported = service.isSupported(prevUserInfo);
+ if (!supported && prevUser != null) {
+ supported = service.isSupportedUser(prevUser);
}
if (!supported) {
if (DEBUG) {
Slog.d(TAG, "Skipping " + onWhat + "User-" + curUserId + " on service "
+ serviceName + " because it's not supported (curUser: "
- + curUserInfo + ", prevUser:" + prevUserInfo + ")");
+ + curUser + ", prevUser:" + prevUser + ")");
} else {
Slog.i(TAG, "Skipping " + onWhat + "User-" + curUserId + " on "
+ serviceName);
@@ -295,25 +296,25 @@
try {
switch (onWhat) {
case SWITCH:
- service.onSwitchUser(prevUserInfo, curUserInfo);
+ service.onSwitchUser(prevUser, curUser);
break;
case START:
- service.onStartUser(curUserInfo);
+ service.onStartUser(curUser);
break;
case UNLOCK:
- service.onUnlockUser(curUserInfo);
+ service.onUnlockUser(curUser);
break;
case STOP:
- service.onStopUser(curUserInfo);
+ service.onStopUser(curUser);
break;
case CLEANUP:
- service.onCleanupUser(curUserInfo);
+ service.onCleanupUser(curUser);
break;
default:
throw new IllegalArgumentException(onWhat + " what?");
}
} catch (Exception ex) {
- Slog.wtf(TAG, "Failure reporting " + onWhat + " of user " + curUserInfo
+ Slog.wtf(TAG, "Failure reporting " + onWhat + " of user " + curUser
+ " to service " + serviceName, ex);
}
warnIfTooLong(SystemClock.elapsedRealtime() - time, service,
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index b9f2e76..e11008c 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -1562,8 +1562,7 @@
throw e.rethrowAsRuntimeException();
}
int numGids = 3;
- if (mountExternal == Zygote.MOUNT_EXTERNAL_INSTALLER
- || mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE) {
+ if (mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE) {
numGids++;
}
/*
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index a7593c7..e3b761e 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -1759,8 +1759,9 @@
? opNames.toArray(new String[opNames.size()]) : null;
// Must not hold the appops lock
- mHistoricalRegistry.getHistoricalOps(uid, packageName, featureId, opNamesArray, filter,
- beginTimeMillis, endTimeMillis, flags, callback);
+ mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps,
+ mHistoricalRegistry, uid, packageName, featureId, opNamesArray, filter,
+ beginTimeMillis, endTimeMillis, flags, callback).recycleOnUse());
}
@Override
@@ -1778,8 +1779,9 @@
? opNames.toArray(new String[opNames.size()]) : null;
// Must not hold the appops lock
- mHistoricalRegistry.getHistoricalOpsFromDiskRaw(uid, packageName, featureId, opNamesArray,
- filter, beginTimeMillis, endTimeMillis, flags, callback);
+ mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw,
+ mHistoricalRegistry, uid, packageName, featureId, opNamesArray,
+ filter, beginTimeMillis, endTimeMillis, flags, callback).recycleOnUse());
}
@Override
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index aea6d8d..f636d67 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -116,7 +116,8 @@
&& !lp.hasIpv4Address();
// If the network tells us it doesn't use clat, respect that.
- final boolean skip464xlat = (nai.netMisc() != null) && nai.netMisc().skip464xlat;
+ final boolean skip464xlat = (nai.netAgentConfig() != null)
+ && nai.netAgentConfig().skip464xlat;
return supported && connected && isIpv6OnlyNetwork && !skip464xlat;
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 5e085ca..c1ab551 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -23,9 +23,9 @@
import android.net.INetworkMonitor;
import android.net.LinkProperties;
import android.net.Network;
+import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
-import android.net.NetworkMisc;
import android.net.NetworkMonitorManager;
import android.net.NetworkRequest;
import android.net.NetworkScore;
@@ -127,7 +127,7 @@
// This should only be modified by ConnectivityService, via setNetworkCapabilities().
// TODO: make this private with a getter.
public NetworkCapabilities networkCapabilities;
- public final NetworkMisc networkMisc;
+ public final NetworkAgentConfig networkAgentConfig;
// Indicates if netd has been told to create this Network. From this point on the appropriate
// routing rules are setup and routes are added so packets can begin flowing over the Network.
// This is a sticky bit; once set it is never cleared.
@@ -261,7 +261,7 @@
public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info,
LinkProperties lp, NetworkCapabilities nc, @NonNull NetworkScore ns, Context context,
- Handler handler, NetworkMisc misc, ConnectivityService connService, INetd netd,
+ Handler handler, NetworkAgentConfig config, ConnectivityService connService, INetd netd,
IDnsResolver dnsResolver, INetworkManagementService nms, int factorySerialNumber) {
this.messenger = messenger;
asyncChannel = ac;
@@ -274,7 +274,7 @@
mConnService = connService;
mContext = context;
mHandler = handler;
- networkMisc = misc;
+ networkAgentConfig = config;
this.factorySerialNumber = factorySerialNumber;
}
@@ -309,8 +309,8 @@
return mConnService;
}
- public NetworkMisc netMisc() {
- return networkMisc;
+ public NetworkAgentConfig netAgentConfig() {
+ return networkAgentConfig;
}
public Handler handler() {
@@ -487,7 +487,8 @@
// selected and we're trying to see what its score could be. This ensures that we don't tear
// down an explicitly selected network before the user gets a chance to prefer it when
// a higher-scoring network (e.g., Ethernet) is available.
- if (networkMisc.explicitlySelected && (networkMisc.acceptUnvalidated || pretendValidated)) {
+ if (networkAgentConfig.explicitlySelected
+ && (networkAgentConfig.acceptUnvalidated || pretendValidated)) {
return ConnectivityConstants.EXPLICITLY_SELECTED_NETWORK_SCORE;
}
@@ -533,7 +534,8 @@
synchronized (this) {
// Network objects are outwardly immutable so there is no point in duplicating.
// Duplicating also precludes sharing socket factories and connection pools.
- final String subscriberId = (networkMisc != null) ? networkMisc.subscriberId : null;
+ final String subscriberId = (networkAgentConfig != null)
+ ? networkAgentConfig.subscriberId : null;
return new NetworkState(new NetworkInfo(networkInfo),
new LinkProperties(linkProperties),
new NetworkCapabilities(networkCapabilities), network, subscriberId, null);
@@ -641,13 +643,13 @@
+ "nc{" + networkCapabilities + "} Score{" + getCurrentScore() + "} "
+ "everValidated{" + everValidated + "} lastValidated{" + lastValidated + "} "
+ "created{" + created + "} lingering{" + isLingering() + "} "
- + "explicitlySelected{" + networkMisc.explicitlySelected + "} "
- + "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} "
+ + "explicitlySelected{" + networkAgentConfig.explicitlySelected + "} "
+ + "acceptUnvalidated{" + networkAgentConfig.acceptUnvalidated + "} "
+ "everCaptivePortalDetected{" + everCaptivePortalDetected + "} "
+ "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} "
+ "captivePortalValidationPending{" + captivePortalValidationPending + "} "
+ "partialConnectivity{" + partialConnectivity + "} "
- + "acceptPartialConnectivity{" + networkMisc.acceptPartialConnectivity + "} "
+ + "acceptPartialConnectivity{" + networkAgentConfig.acceptPartialConnectivity + "} "
+ "clat{" + clatd + "} "
+ "}";
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 6b70e5f..476f3f8 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -53,11 +53,11 @@
import android.net.LocalSocketAddress;
import android.net.Network;
import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
-import android.net.NetworkFactory;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkMisc;
+import android.net.NetworkProvider;
import android.net.RouteInfo;
import android.net.UidRange;
import android.net.VpnService;
@@ -914,7 +914,7 @@
* has certain changes, in which case this method would just return {@code false}.
*/
private boolean updateLinkPropertiesInPlaceIfPossible(NetworkAgent agent, VpnConfig oldConfig) {
- // NetworkMisc cannot be updated without registering a new NetworkAgent.
+ // NetworkAgentConfig cannot be updated without registering a new NetworkAgent.
if (oldConfig.allowBypass != mConfig.allowBypass) {
Log.i(TAG, "Handover not possible due to changes to allowBypass");
return false;
@@ -947,8 +947,8 @@
mNetworkInfo.setDetailedState(DetailedState.CONNECTING, null, null);
- NetworkMisc networkMisc = new NetworkMisc();
- networkMisc.allowBypass = mConfig.allowBypass && !mLockdown;
+ NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig();
+ networkAgentConfig.allowBypass = mConfig.allowBypass && !mLockdown;
mNetworkCapabilities.setEstablishingVpnAppUid(Binder.getCallingUid());
mNetworkCapabilities.setUids(createUserAndRestrictedProfilesRanges(mUserHandle,
@@ -957,8 +957,8 @@
try {
mNetworkAgent = new NetworkAgent(mLooper, mContext, NETWORKTYPE /* logtag */,
mNetworkInfo, mNetworkCapabilities, lp,
- ConnectivityConstants.VPN_DEFAULT_SCORE, networkMisc,
- NetworkFactory.SerialNumber.VPN) {
+ ConnectivityConstants.VPN_DEFAULT_SCORE, networkAgentConfig,
+ NetworkProvider.ID_VPN) {
@Override
public void unwanted() {
// We are user controlled, not driven by NetworkRequest.
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index c58000f..8206fef 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -41,7 +41,6 @@
import com.android.internal.content.PackageMonitor;
import com.android.internal.infra.AbstractRemoteService;
import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -713,8 +712,8 @@
}
/**
- * Gets a list of all supported users (i.e., those that pass the {@link #isSupported(UserInfo)}
- * check).
+ * Gets a list of all supported users (i.e., those that pass the
+ * {@link #isSupportedUser(TargetUser)}check).
*/
@NonNull
protected List<UserInfo> getSupportedUsers() {
@@ -723,7 +722,7 @@
final List<UserInfo> supportedUsers = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
final UserInfo userInfo = allUsers[i];
- if (isSupported(userInfo)) {
+ if (isSupportedUser(new TargetUser(userInfo))) {
supportedUsers.add(userInfo);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index c40e8af..2c3c70f 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -72,6 +72,18 @@
AutofillId autofillId, IInlineSuggestionsRequestCallback cb);
/**
+ * Force switch to the enabled input method by {@code imeId} for current user. If the input
+ * method with {@code imeId} is not enabled or not installed, do nothing.
+ *
+ * @param imeId The input method ID to be switched to.
+ * @param userId The user ID to be queried.
+ * @return {@code true} if the current input method was successfully switched to the input
+ * method by {@code imeId}; {@code false} the input method with {@code imeId} is not available
+ * to be switched.
+ */
+ public abstract boolean switchToInputMethod(String imeId, @UserIdInt int userId);
+
+ /**
* Fake implementation of {@link InputMethodManagerInternal}. All the methods do nothing.
*/
private static final InputMethodManagerInternal NOP =
@@ -98,6 +110,11 @@
public void onCreateInlineSuggestionsRequest(ComponentName componentName,
AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
}
+
+ @Override
+ public boolean switchToInputMethod(String imeId, int userId) {
+ return false;
+ }
};
/**
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 8b6b614..d4b13fa 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4478,6 +4478,38 @@
}
}
+ private boolean switchToInputMethod(String imeId, @UserIdInt int userId) {
+ synchronized (mMethodMap) {
+ if (userId == mSettings.getCurrentUserId()) {
+ if (!mMethodMap.containsKey(imeId)
+ || !mSettings.getEnabledInputMethodListLocked()
+ .contains(mMethodMap.get(imeId))) {
+ return false; // IME is not is found or not enabled.
+ }
+ setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
+ return true;
+ }
+ final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
+ final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
+ new ArrayMap<>();
+ AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
+ queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
+ methodMap, methodList);
+ final InputMethodSettings settings = new InputMethodSettings(
+ mContext.getResources(), mContext.getContentResolver(), methodMap,
+ userId, false);
+ if (!methodMap.containsKey(imeId)
+ || !settings.getEnabledInputMethodListLocked()
+ .contains(methodMap.get(imeId))) {
+ return false; // IME is not is found or not enabled.
+ }
+ settings.putSelectedInputMethod(imeId);
+ settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
+ return true;
+ }
+ }
+
private static final class LocalServiceImpl extends InputMethodManagerInternal {
@NonNull
private final InputMethodManagerService mService;
@@ -4514,6 +4546,11 @@
AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
mService.onCreateInlineSuggestionsRequest(componentName, autofillId, cb);
}
+
+ @Override
+ public boolean switchToInputMethod(String imeId, int userId) {
+ return mService.switchToInputMethod(imeId, userId);
+ }
}
@BinderThread
@@ -5065,31 +5102,7 @@
if (!userHasDebugPriv(userId, shellCommand)) {
continue;
}
- boolean failedToSelectUnknownIme = false;
- if (userId == mSettings.getCurrentUserId()) {
- if (mMethodMap.containsKey(imeId)) {
- setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
- } else {
- failedToSelectUnknownIme = true;
- }
- } else {
- final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
- final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
- final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
- new ArrayMap<>();
- AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
- queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
- methodMap, methodList);
- final InputMethodSettings settings = new InputMethodSettings(
- mContext.getResources(), mContext.getContentResolver(), methodMap,
- userId, false);
- if (methodMap.containsKey(imeId)) {
- settings.putSelectedInputMethod(imeId);
- settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
- } else {
- failedToSelectUnknownIme = true;
- }
- }
+ boolean failedToSelectUnknownIme = !switchToInputMethod(imeId, userId);
if (failedToSelectUnknownIme) {
error.print("Unknown input method ");
error.print(imeId);
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 1f9379c..20d7955 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -200,6 +200,12 @@
Slog.w(TAG, "Failed to call onInlineSuggestionsUnsupported.", e);
}
}
+
+ @Override
+ public boolean switchToInputMethod(String imeId, @UserIdInt int userId) {
+ reportNotSupported();
+ return false;
+ }
});
}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
index f964d4c..d3588d3 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
@@ -48,6 +48,7 @@
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -82,28 +83,28 @@
Map<Integer, TreeMap<String, List<Rule>>> indexedRules =
RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules);
+ // Serialize the rules.
ByteTrackedOutputStream ruleFileByteTrackedOutputStream =
new ByteTrackedOutputStream(rulesFileOutputStream);
-
serializeRuleFileMetadata(formatVersion, ruleFileByteTrackedOutputStream);
-
Map<String, Integer> packageNameIndexes =
serializeRuleList(indexedRules.get(PACKAGE_NAME_INDEXED),
ruleFileByteTrackedOutputStream);
- indexingFileOutputStream.write(
- serializeIndexes(packageNameIndexes, /* isIndexed= */true));
-
Map<String, Integer> appCertificateIndexes =
serializeRuleList(indexedRules.get(APP_CERTIFICATE_INDEXED),
ruleFileByteTrackedOutputStream);
- indexingFileOutputStream.write(
- serializeIndexes(appCertificateIndexes, /* isIndexed= */true));
-
Map<String, Integer> unindexedRulesIndexes =
serializeRuleList(indexedRules.get(NOT_INDEXED),
ruleFileByteTrackedOutputStream);
- indexingFileOutputStream.write(
- serializeIndexes(unindexedRulesIndexes, /* isIndexed= */false));
+
+ // Serialize their indexes.
+ BitOutputStream indexingBitOutputStream = new BitOutputStream();
+ serializeIndexGroup(packageNameIndexes, indexingBitOutputStream, /* isIndexed= */true);
+ serializeIndexGroup(appCertificateIndexes, indexingBitOutputStream, /* isIndexed= */
+ true);
+ serializeIndexGroup(unindexedRulesIndexes, indexingBitOutputStream, /* isIndexed= */
+ false);
+ indexingFileOutputStream.write(indexingBitOutputStream.toByteArray());
} catch (Exception e) {
throw new RuleSerializeException(e.getMessage(), e);
}
@@ -126,7 +127,7 @@
"serializeRuleList should never be called with null rule list.");
BitOutputStream bitOutputStream = new BitOutputStream();
- Map<String, Integer> indexMapping = new TreeMap();
+ Map<String, Integer> indexMapping = new LinkedHashMap();
int indexTracker = 0;
indexMapping.put(START_INDEXING_KEY, outputStream.getWrittenBytesCount());
@@ -220,8 +221,8 @@
}
}
- private byte[] serializeIndexes(Map<String, Integer> indexes, boolean isIndexed) {
- BitOutputStream bitOutputStream = new BitOutputStream();
+ private void serializeIndexGroup(
+ Map<String, Integer> indexes, BitOutputStream bitOutputStream, boolean isIndexed) {
// Output the starting location of this indexing group.
serializeStringValue(START_INDEXING_KEY, /* isHashedValue= */false,
@@ -244,7 +245,8 @@
serializeStringValue(END_INDEXING_KEY, /*isHashedValue= */ false, bitOutputStream);
serializeIntValue(indexes.get(END_INDEXING_KEY), bitOutputStream);
- return bitOutputStream.toByteArray();
+ // This dummy bit is set for fixing the padding issue. songpan@ will fix it and remove it.
+ bitOutputStream.setNext();
}
private void serializeStringValue(
diff --git a/services/core/java/com/android/server/location/NtpTimeHelper.java b/services/core/java/com/android/server/location/NtpTimeHelper.java
index 67841ac..d2296ea 100644
--- a/services/core/java/com/android/server/location/NtpTimeHelper.java
+++ b/services/core/java/com/android/server/location/NtpTimeHelper.java
@@ -130,7 +130,8 @@
// force refresh NTP cache when outdated
boolean refreshSuccess = true;
- if (mNtpTime.getCacheAge() >= NTP_INTERVAL) {
+ NtpTrustedTime.TimeResult ntpResult = mNtpTime.getCachedTimeResult();
+ if (ntpResult == null || ntpResult.getAgeMillis() >= NTP_INTERVAL) {
// Blocking network operation.
refreshSuccess = mNtpTime.forceRefresh();
}
@@ -140,17 +141,17 @@
// only update when NTP time is fresh
// If refreshSuccess is false, cacheAge does not drop down.
- if (mNtpTime.getCacheAge() < NTP_INTERVAL) {
- long time = mNtpTime.getCachedNtpTime();
- long timeReference = mNtpTime.getCachedNtpTimeReference();
- long certainty = mNtpTime.getCacheCertainty();
+ ntpResult = mNtpTime.getCachedTimeResult();
+ if (ntpResult != null && ntpResult.getAgeMillis() < NTP_INTERVAL) {
+ long time = ntpResult.getTimeMillis();
+ long timeReference = ntpResult.getElapsedRealtimeMillis();
+ long certainty = ntpResult.getCertaintyMillis();
if (DEBUG) {
long now = System.currentTimeMillis();
Log.d(TAG, "NTP server returned: "
- + time + " (" + new Date(time)
- + ") reference: " + timeReference
- + " certainty: " + certainty
+ + time + " (" + new Date(time) + ")"
+ + " ntpResult: " + ntpResult
+ " system time offset: " + (time - now));
}
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
new file mode 100644
index 0000000..5191833
--- /dev/null
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.annotation.NonNull;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.MediaRoute2Info;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+class BluetoothRouteProvider {
+ private static final String TAG = "BTRouteProvider";
+ private static BluetoothRouteProvider sInstance;
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ final Map<String, BluetoothRouteInfo> mBluetoothRoutes = new HashMap<>();
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ BluetoothA2dp mA2dpProfile;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ BluetoothHearingAid mHearingAidProfile;
+
+ private final Context mContext;
+ private final BluetoothAdapter mBluetoothAdapter;
+ private final BluetoothRoutesUpdatedListener mListener;
+ private final Map<String, BluetoothEventReceiver> mEventReceiverMap = new HashMap<>();
+ private final IntentFilter mIntentFilter = new IntentFilter();
+ private final BroadcastReceiver mBroadcastReceiver = new BluetoothBroadcastReceiver();
+ private final BluetoothProfileListener mProfileListener = new BluetoothProfileListener();
+
+ private BluetoothDevice mActiveDevice = null;
+
+ static synchronized BluetoothRouteProvider getInstance(@NonNull Context context,
+ @NonNull BluetoothRoutesUpdatedListener listener) {
+ Objects.requireNonNull(context);
+ Objects.requireNonNull(listener);
+
+ if (sInstance == null) {
+ BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (btAdapter == null) {
+ return null;
+ }
+ sInstance = new BluetoothRouteProvider(context, btAdapter, listener);
+ }
+ return sInstance;
+ }
+
+ private BluetoothRouteProvider(Context context, BluetoothAdapter btAdapter,
+ BluetoothRoutesUpdatedListener listener) {
+ mContext = context;
+ mBluetoothAdapter = btAdapter;
+ mListener = listener;
+ buildBluetoothRoutes();
+
+ mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
+ mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEARING_AID);
+
+ // Bluetooth on/off broadcasts
+ addEventReceiver(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedReceiver());
+
+ // Pairing broadcasts
+ addEventReceiver(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedReceiver());
+
+ DeviceStateChangedRecevier deviceStateChangedReceiver = new DeviceStateChangedRecevier();
+ addEventReceiver(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED, deviceStateChangedReceiver);
+ addEventReceiver(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED, deviceStateChangedReceiver);
+ addEventReceiver(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED,
+ deviceStateChangedReceiver);
+ addEventReceiver(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED,
+ deviceStateChangedReceiver);
+
+ mContext.registerReceiver(mBroadcastReceiver, mIntentFilter, null, null);
+ }
+
+ private void addEventReceiver(String action, BluetoothEventReceiver eventReceiver) {
+ mEventReceiverMap.put(action, eventReceiver);
+ mIntentFilter.addAction(action);
+ }
+
+ private void buildBluetoothRoutes() {
+ mBluetoothRoutes.clear();
+ for (BluetoothDevice device : mBluetoothAdapter.getBondedDevices()) {
+ if (device.isConnected()) {
+ BluetoothRouteInfo newBtRoute = createBluetoothRoute(device);
+ mBluetoothRoutes.put(device.getAddress(), newBtRoute);
+ }
+ }
+ }
+
+ @NonNull List<MediaRoute2Info> getBluetoothRoutes() {
+ ArrayList<MediaRoute2Info> routes = new ArrayList<>();
+ for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) {
+ routes.add(btRoute.route);
+ }
+ return routes;
+ }
+
+ private void notifyBluetoothRoutesUpdated() {
+ if (mListener != null) {
+ mListener.onBluetoothRoutesUpdated(getBluetoothRoutes());
+ }
+ }
+
+ private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) {
+ BluetoothRouteInfo newBtRoute = new BluetoothRouteInfo();
+ newBtRoute.btDevice = device;
+ newBtRoute.route = new MediaRoute2Info.Builder(device.getAddress(), device.getName())
+ .addFeature(SystemMediaRoute2Provider.TYPE_LIVE_AUDIO)
+ .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED)
+ .setDescription(mContext.getResources().getText(
+ R.string.bluetooth_a2dp_audio_route_name).toString())
+ .build();
+ newBtRoute.connectedProfiles = new SparseBooleanArray();
+ return newBtRoute;
+ }
+
+ private void setRouteConnectionStateForDevice(BluetoothDevice device,
+ @MediaRoute2Info.ConnectionState int state) {
+ if (device == null) {
+ Log.w(TAG, "setRouteConnectionStateForDevice: device shouldn't be null");
+ return;
+ }
+ BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+ if (btRoute == null) {
+ Log.w(TAG, "setRouteConnectionStateForDevice: route shouldn't be null");
+ return;
+ }
+ if (btRoute.route.getConnectionState() != state) {
+ btRoute.route = new MediaRoute2Info.Builder(btRoute.route)
+ .setConnectionState(state).build();
+ }
+ }
+
+ interface BluetoothRoutesUpdatedListener {
+ void onBluetoothRoutesUpdated(@NonNull List<MediaRoute2Info> routes);
+ }
+
+ private class BluetoothRouteInfo {
+ public BluetoothDevice btDevice;
+ public MediaRoute2Info route;
+ public SparseBooleanArray connectedProfiles;
+ }
+
+ // These callbacks run on the main thread.
+ private final class BluetoothProfileListener implements BluetoothProfile.ServiceListener {
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ switch (profile) {
+ case BluetoothProfile.A2DP:
+ mA2dpProfile = (BluetoothA2dp) proxy;
+ break;
+ case BluetoothProfile.HEARING_AID:
+ mHearingAidProfile = (BluetoothHearingAid) proxy;
+ break;
+ default:
+ return;
+ }
+ for (BluetoothDevice device : proxy.getConnectedDevices()) {
+ BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+ if (btRoute == null) {
+ btRoute = createBluetoothRoute(device);
+ mBluetoothRoutes.put(device.getAddress(), btRoute);
+ }
+ btRoute.connectedProfiles.put(profile, true);
+ }
+ }
+
+ public void onServiceDisconnected(int profile) {
+ switch (profile) {
+ case BluetoothProfile.A2DP:
+ mA2dpProfile = null;
+ break;
+ case BluetoothProfile.HEARING_AID:
+ mHearingAidProfile = null;
+ break;
+ default:
+ return;
+ }
+ }
+ }
+ private class BluetoothBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+
+ BluetoothEventReceiver receiver = mEventReceiverMap.get(action);
+ if (receiver != null) {
+ receiver.onReceive(context, intent, device);
+ }
+ }
+ }
+
+ private interface BluetoothEventReceiver {
+ void onReceive(Context context, Intent intent, BluetoothDevice device);
+ }
+
+ private class AdapterStateChangedReceiver implements BluetoothEventReceiver {
+ public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+ int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
+ if (state == BluetoothAdapter.STATE_OFF
+ || state == BluetoothAdapter.STATE_TURNING_OFF) {
+ mBluetoothRoutes.clear();
+ notifyBluetoothRoutesUpdated();
+ } else if (state == BluetoothAdapter.STATE_ON) {
+ buildBluetoothRoutes();
+ if (!mBluetoothRoutes.isEmpty()) {
+ notifyBluetoothRoutesUpdated();
+ }
+ }
+ }
+ }
+
+ private class BondStateChangedReceiver implements BluetoothEventReceiver {
+ public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+ int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
+ BluetoothDevice.ERROR);
+ BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+ if (bondState == BluetoothDevice.BOND_BONDED && btRoute == null) {
+ btRoute = createBluetoothRoute(device);
+ if (mA2dpProfile != null && mA2dpProfile.getConnectedDevices().contains(device)) {
+ btRoute.connectedProfiles.put(BluetoothProfile.A2DP, true);
+ }
+ if (mHearingAidProfile != null
+ && mHearingAidProfile.getConnectedDevices().contains(device)) {
+ btRoute.connectedProfiles.put(BluetoothProfile.HEARING_AID, true);
+ }
+ mBluetoothRoutes.put(device.getAddress(), btRoute);
+ notifyBluetoothRoutesUpdated();
+ } else if (bondState == BluetoothDevice.BOND_NONE
+ && mBluetoothRoutes.remove(device.getAddress()) != null) {
+ notifyBluetoothRoutesUpdated();
+ }
+ }
+ }
+
+ private class DeviceStateChangedRecevier implements BluetoothEventReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+ switch (intent.getAction()) {
+ case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
+ String prevActiveDeviceAddress =
+ (mActiveDevice == null) ? null : mActiveDevice.getAddress();
+ String curActiveDeviceAddress =
+ (device == null) ? null : device.getAddress();
+ if (!TextUtils.equals(prevActiveDeviceAddress, curActiveDeviceAddress)) {
+ if (mActiveDevice != null) {
+ setRouteConnectionStateForDevice(mActiveDevice,
+ MediaRoute2Info.CONNECTION_STATE_DISCONNECTED);
+ }
+ if (device != null) {
+ setRouteConnectionStateForDevice(device,
+ MediaRoute2Info.CONNECTION_STATE_CONNECTED);
+ }
+ notifyBluetoothRoutesUpdated();
+ mActiveDevice = device;
+ }
+ break;
+ case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
+ handleConnectionStateChanged(BluetoothProfile.A2DP, intent, device);
+ break;
+ }
+ }
+
+ private void handleConnectionStateChanged(int profile, Intent intent,
+ BluetoothDevice device) {
+ int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+ BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+ if (state == BluetoothProfile.STATE_CONNECTED) {
+ if (btRoute == null) {
+ btRoute = createBluetoothRoute(device);
+ mBluetoothRoutes.put(device.getAddress(), btRoute);
+ btRoute.connectedProfiles.put(profile, true);
+ notifyBluetoothRoutesUpdated();
+ } else {
+ btRoute.connectedProfiles.put(profile, true);
+ }
+ } else if (state == BluetoothProfile.STATE_DISCONNECTING
+ || state == BluetoothProfile.STATE_DISCONNECTED) {
+ btRoute.connectedProfiles.delete(profile);
+ if (btRoute.connectedProfiles.size() == 0) {
+ mBluetoothRoutes.remove(device.getAddress());
+ if (mActiveDevice != null
+ && TextUtils.equals(mActiveDevice.getAddress(), device.getAddress())) {
+ mActiveDevice = null;
+ }
+ notifyBluetoothRoutesUpdated();
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 9c9a412..0d315cd 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -21,7 +21,7 @@
import android.content.ComponentName;
import android.content.Intent;
import android.media.MediaRoute2ProviderInfo;
-import android.media.RouteSessionInfo;
+import android.media.RoutingSessionInfo;
import java.util.ArrayList;
import java.util.Collections;
@@ -34,7 +34,7 @@
Callback mCallback;
private volatile MediaRoute2ProviderInfo mProviderInfo;
- private volatile List<RouteSessionInfo> mSessionInfos = Collections.emptyList();
+ private volatile List<RoutingSessionInfo> mSessionInfos = Collections.emptyList();
MediaRoute2Provider(@NonNull ComponentName componentName) {
mComponentName = Objects.requireNonNull(componentName, "Component name must not be null.");
@@ -68,12 +68,12 @@
}
@NonNull
- public List<RouteSessionInfo> getSessionInfos() {
+ public List<RoutingSessionInfo> getSessionInfos() {
return mSessionInfos;
}
- void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo,
- List<RouteSessionInfo> sessionInfos) {
+ void setProviderState(MediaRoute2ProviderInfo providerInfo,
+ List<RoutingSessionInfo> sessionInfos) {
if (providerInfo == null) {
mProviderInfo = null;
} else {
@@ -81,20 +81,28 @@
.setUniqueId(mUniqueId)
.build();
}
- List<RouteSessionInfo> sessionInfoWithProviderId = new ArrayList<RouteSessionInfo>();
- for (RouteSessionInfo sessionInfo : sessionInfos) {
+ List<RoutingSessionInfo> sessionInfoWithProviderId = new ArrayList<RoutingSessionInfo>();
+ for (RoutingSessionInfo sessionInfo : sessionInfos) {
sessionInfoWithProviderId.add(
- new RouteSessionInfo.Builder(sessionInfo)
+ new RoutingSessionInfo.Builder(sessionInfo)
.setProviderId(mUniqueId)
.build());
}
mSessionInfos = sessionInfoWithProviderId;
+ }
+ void notifyProviderState() {
if (mCallback != null) {
mCallback.onProviderStateChanged(this);
}
}
+ void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo,
+ List<RoutingSessionInfo> sessionInfos) {
+ setProviderState(providerInfo, sessionInfos);
+ notifyProviderState();
+ }
+
public boolean hasComponentName(String packageName, String className) {
return mComponentName.getPackageName().equals(packageName)
&& mComponentName.getClassName().equals(className);
@@ -103,12 +111,12 @@
public interface Callback {
void onProviderStateChanged(@Nullable MediaRoute2Provider provider);
void onSessionCreated(@NonNull MediaRoute2Provider provider,
- @Nullable RouteSessionInfo sessionInfo, long requestId);
+ @Nullable RoutingSessionInfo sessionInfo, long requestId);
// TODO: Remove this when MediaRouter2ServiceImpl notifies clients of session changes.
void onSessionInfoChanged(@NonNull MediaRoute2Provider provider,
- @NonNull RouteSessionInfo sessionInfo);
+ @NonNull RoutingSessionInfo sessionInfo);
// TODO: Call this when service actually notifies of session release.
void onSessionReleased(@NonNull MediaRoute2Provider provider,
- @NonNull RouteSessionInfo sessionInfo);
+ @NonNull RoutingSessionInfo sessionInfo);
}
}
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
index 6359835..c0ad46f 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
@@ -26,7 +26,7 @@
import android.media.IMediaRoute2ProviderClient;
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2ProviderService;
-import android.media.RouteSessionInfo;
+import android.media.RoutingSessionInfo;
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
@@ -270,7 +270,7 @@
}
private void onProviderStateUpdated(Connection connection,
- MediaRoute2ProviderInfo providerInfo, List<RouteSessionInfo> sessionInfos) {
+ MediaRoute2ProviderInfo providerInfo, List<RoutingSessionInfo> sessionInfos) {
if (mActiveConnection != connection) {
return;
}
@@ -280,20 +280,20 @@
setAndNotifyProviderState(providerInfo, sessionInfos);
}
- private void onSessionCreated(Connection connection, @Nullable RouteSessionInfo sessionInfo,
+ private void onSessionCreated(Connection connection, @Nullable RoutingSessionInfo sessionInfo,
long requestId) {
if (mActiveConnection != connection) {
return;
}
if (sessionInfo != null) {
- sessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+ sessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
.setProviderId(getUniqueId())
.build();
}
mCallback.onSessionCreated(this, sessionInfo, requestId);
}
- private void onSessionInfoChanged(Connection connection, RouteSessionInfo sessionInfo) {
+ private void onSessionInfoChanged(Connection connection, RoutingSessionInfo sessionInfo) {
if (mActiveConnection != connection) {
return;
}
@@ -303,7 +303,7 @@
return;
}
- sessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+ sessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
.setProviderId(getUniqueId())
.build();
@@ -422,17 +422,17 @@
}
void postProviderStateUpdated(MediaRoute2ProviderInfo providerInfo,
- List<RouteSessionInfo> sessionInfos) {
+ List<RoutingSessionInfo> sessionInfos) {
mHandler.post(() -> onProviderStateUpdated(Connection.this,
providerInfo, sessionInfos));
}
- void postSessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) {
+ void postSessionCreated(@Nullable RoutingSessionInfo sessionInfo, long requestId) {
mHandler.post(() -> onSessionCreated(Connection.this, sessionInfo,
requestId));
}
- void postSessionInfoChanged(RouteSessionInfo sessionInfo) {
+ void postSessionInfoChanged(RoutingSessionInfo sessionInfo) {
mHandler.post(() -> onSessionInfoChanged(Connection.this, sessionInfo));
}
}
@@ -450,7 +450,7 @@
@Override
public void updateState(MediaRoute2ProviderInfo providerInfo,
- List<RouteSessionInfo> sessionInfos) {
+ List<RoutingSessionInfo> sessionInfos) {
Connection connection = mConnectionRef.get();
if (connection != null) {
connection.postProviderStateUpdated(providerInfo, sessionInfos);
@@ -458,7 +458,7 @@
}
@Override
- public void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) {
+ public void notifySessionCreated(@Nullable RoutingSessionInfo sessionInfo, long requestId) {
Connection connection = mConnectionRef.get();
if (connection != null) {
connection.postSessionCreated(sessionInfo, requestId);
@@ -466,7 +466,7 @@
}
@Override
- public void notifySessionInfoChanged(RouteSessionInfo sessionInfo) {
+ public void notifySessionInfoChanged(RoutingSessionInfo sessionInfo) {
Connection connection = mConnectionRef.get();
if (connection != null) {
connection.postSessionInfoChanged(sessionInfo);
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 487ab52..c48c90d 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -31,8 +31,8 @@
import android.media.IMediaRouter2Manager;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
-import android.media.RouteDiscoveryRequest;
-import android.media.RouteSessionInfo;
+import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -178,18 +178,18 @@
}
public void requestCreateSession(IMediaRouter2Client client, MediaRoute2Info route,
- String routeType, int requestId) {
+ String routeFeature, int requestId) {
Objects.requireNonNull(client, "client must not be null");
Objects.requireNonNull(route, "route must not be null");
- if (TextUtils.isEmpty(routeType)) {
- throw new IllegalArgumentException("routeType must not be empty");
+ if (TextUtils.isEmpty(routeFeature)) {
+ throw new IllegalArgumentException("routeFeature must not be empty");
}
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- requestCreateSessionLocked(client, route, routeType, requestId);
+ requestCreateSessionLocked(client, route, routeFeature, requestId);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -284,15 +284,15 @@
}
public void setDiscoveryRequest2(@NonNull IMediaRouter2Client client,
- @NonNull RouteDiscoveryRequest request) {
+ @NonNull RouteDiscoveryPreference preference) {
Objects.requireNonNull(client, "client must not be null");
- Objects.requireNonNull(request, "request must not be null");
+ Objects.requireNonNull(preference, "preference must not be null");
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
Client2Record clientRecord = mAllClientRecords.get(client.asBinder());
- setDiscoveryRequestLocked(clientRecord, request);
+ setDiscoveryRequestLocked(clientRecord, preference);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -370,7 +370,7 @@
}
@NonNull
- public List<RouteSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
+ public List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -450,7 +450,7 @@
}
private void requestCreateSessionLocked(@NonNull IMediaRouter2Client client,
- @NonNull MediaRoute2Info route, @NonNull String routeType, long requestId) {
+ @NonNull MediaRoute2Info route, @NonNull String routeFeature, long requestId) {
final IBinder binder = client.asBinder();
final Client2Record clientRecord = mAllClientRecords.get(binder);
@@ -463,7 +463,7 @@
clientRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::requestCreateSessionOnHandler,
clientRecord.mUserRecord.mHandler,
- clientRecord, route, routeType, requestId));
+ clientRecord, route, routeFeature, requestId));
}
}
@@ -519,13 +519,13 @@
}
private void setDiscoveryRequestLocked(Client2Record clientRecord,
- RouteDiscoveryRequest discoveryRequest) {
+ RouteDiscoveryPreference discoveryRequest) {
if (clientRecord != null) {
- if (clientRecord.mDiscoveryRequest.equals(discoveryRequest)) {
+ if (clientRecord.mDiscoveryPreference.equals(discoveryRequest)) {
return;
}
- clientRecord.mDiscoveryRequest = discoveryRequest;
+ clientRecord.mDiscoveryPreference = discoveryRequest;
clientRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::updateClientUsage,
clientRecord.mUserRecord.mHandler, clientRecord));
@@ -622,9 +622,9 @@
}
long uniqueRequestId = toUniqueRequestId(managerRecord.mClientId, requestId);
if (clientRecord != null && managerRecord.mTrusted) {
- //TODO: select route type properly
+ //TODO: select route feature properly
requestCreateSessionLocked(clientRecord.mClient, route,
- route.getRouteTypes().get(0), uniqueRequestId);
+ route.getFeatures().get(0), uniqueRequestId);
}
}
}
@@ -653,7 +653,7 @@
}
}
- private List<RouteSessionInfo> getActiveSessionsLocked(IMediaRouter2Manager manager) {
+ private List<RoutingSessionInfo> getActiveSessionsLocked(IMediaRouter2Manager manager) {
final IBinder binder = manager.asBinder();
ManagerRecord managerRecord = mAllManagerRecords.get(binder);
@@ -661,7 +661,7 @@
return Collections.emptyList();
}
- List<RouteSessionInfo> sessionInfos = new ArrayList<>();
+ List<RoutingSessionInfo> sessionInfos = new ArrayList<>();
for (MediaRoute2Provider provider : managerRecord.mUserRecord.mHandler.mMediaProviders) {
sessionInfos.addAll(provider.getSessionInfos());
}
@@ -742,7 +742,7 @@
public final boolean mTrusted;
public final int mClientId;
- public RouteDiscoveryRequest mDiscoveryRequest;
+ public RouteDiscoveryPreference mDiscoveryPreference;
public boolean mIsManagerSelecting;
public MediaRoute2Info mSelectingRoute;
public MediaRoute2Info mSelectedRoute;
@@ -752,7 +752,7 @@
mUserRecord = userRecord;
mPackageName = packageName;
mSelectRouteSequenceNumbers = new ArrayList<>();
- mDiscoveryRequest = RouteDiscoveryRequest.EMPTY;
+ mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
mClient = client;
mUid = uid;
mPid = pid;
@@ -876,20 +876,21 @@
@Override
public void onSessionCreated(@NonNull MediaRoute2Provider provider,
- @Nullable RouteSessionInfo sessionInfo, long requestId) {
+ @Nullable RoutingSessionInfo sessionInfo, long requestId) {
sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionCreatedOnHandler,
this, provider, sessionInfo, requestId));
}
@Override
public void onSessionInfoChanged(@NonNull MediaRoute2Provider provider,
- @NonNull RouteSessionInfo sessionInfo) {
+ @NonNull RoutingSessionInfo sessionInfo) {
sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionInfoChangedOnHandler,
this, provider, sessionInfo));
}
@Override
- public void onSessionReleased(MediaRoute2Provider provider, RouteSessionInfo sessionInfo) {
+ public void onSessionReleased(MediaRoute2Provider provider,
+ RoutingSessionInfo sessionInfo) {
sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionReleasedOnHandler,
this, provider, sessionInfo));
}
@@ -932,7 +933,7 @@
Slog.w(TAG, "Ignoring invalid route : " + route);
continue;
}
- MediaRoute2Info prevRoute = prevInfo.getRoute(route.getId());
+ MediaRoute2Info prevRoute = prevInfo.getRoute(route.getOriginalId());
if (prevRoute != null) {
if (!Objects.equals(prevRoute, route)) {
@@ -978,7 +979,7 @@
}
private void requestCreateSessionOnHandler(Client2Record clientRecord,
- MediaRoute2Info route, String routeType, long requestId) {
+ MediaRoute2Info route, String routeFeature, long requestId) {
final MediaRoute2Provider provider = findProvider(route.getProviderId());
if (provider == null) {
@@ -988,20 +989,20 @@
return;
}
- if (!route.getRouteTypes().contains(routeType)) {
+ if (!route.getFeatures().contains(routeFeature)) {
Slog.w(TAG, "Ignoring session creation request since the given route=" + route
- + " doesn't support the given type=" + routeType);
+ + " doesn't support the given feature=" + routeFeature);
notifySessionCreationFailed(clientRecord, toClientRequestId(requestId));
return;
}
// TODO: Apply timeout for each request (How many seconds should we wait?)
SessionCreationRequest request = new SessionCreationRequest(
- clientRecord, route, routeType, requestId);
+ clientRecord, route, routeFeature, requestId);
mSessionCreationRequests.add(request);
provider.requestCreateSession(clientRecord.mPackageName, route.getOriginalId(),
- routeType, requestId);
+ routeFeature, requestId);
}
private void selectRouteOnHandler(@NonNull Client2Record clientRecord,
@@ -1131,7 +1132,7 @@
}
private void onSessionCreatedOnHandler(@NonNull MediaRoute2Provider provider,
- @Nullable RouteSessionInfo sessionInfo, long requestId) {
+ @Nullable RoutingSessionInfo sessionInfo, long requestId) {
SessionCreationRequest matchingRequest = null;
for (SessionCreationRequest request : mSessionCreationRequests) {
@@ -1159,16 +1160,16 @@
}
String originalRouteId = matchingRequest.mRoute.getId();
- String originalRouteType = matchingRequest.mRouteType;
+ String originalRouteFeature = matchingRequest.mRouteFeature;
Client2Record client2Record = matchingRequest.mClientRecord;
if (!sessionInfo.getSelectedRoutes().contains(originalRouteId)
- || !TextUtils.equals(originalRouteType,
- sessionInfo.getRouteType())) {
+ || !TextUtils.equals(originalRouteFeature,
+ sessionInfo.getRouteFeature())) {
Slog.w(TAG, "Created session doesn't match the original request."
+ " originalRouteId=" + originalRouteId
- + ", originalRouteType=" + originalRouteType + ", requestId=" + requestId
- + ", sessionInfo=" + sessionInfo);
+ + ", originalRouteFeature=" + originalRouteFeature
+ + ", requestId=" + requestId + ", sessionInfo=" + sessionInfo);
notifySessionCreationFailed(matchingRequest.mClientRecord,
toClientRequestId(requestId));
return;
@@ -1182,7 +1183,7 @@
}
private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider,
- @NonNull RouteSessionInfo sessionInfo) {
+ @NonNull RoutingSessionInfo sessionInfo) {
Client2Record client2Record = mSessionToClientMap.get(
sessionInfo.getId());
@@ -1196,7 +1197,7 @@
}
private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider,
- @NonNull RouteSessionInfo sessionInfo) {
+ @NonNull RoutingSessionInfo sessionInfo) {
Client2Record client2Record = mSessionToClientMap.get(sessionInfo.getId());
if (client2Record == null) {
@@ -1208,8 +1209,8 @@
// TODO: Tell managers for the session release
}
- private void notifySessionCreated(Client2Record clientRecord, RouteSessionInfo sessionInfo,
- int requestId) {
+ private void notifySessionCreated(Client2Record clientRecord,
+ RoutingSessionInfo sessionInfo, int requestId) {
try {
clientRecord.mClient.notifySessionCreated(sessionInfo, requestId);
} catch (RemoteException ex) {
@@ -1228,7 +1229,7 @@
}
private void notifySessionInfoChanged(Client2Record clientRecord,
- RouteSessionInfo sessionInfo) {
+ RoutingSessionInfo sessionInfo) {
try {
clientRecord.mClient.notifySessionInfoChanged(sessionInfo);
} catch (RemoteException ex) {
@@ -1238,7 +1239,7 @@
}
private void notifySessionReleased(Client2Record clientRecord,
- RouteSessionInfo sessionInfo) {
+ RoutingSessionInfo sessionInfo) {
try {
clientRecord.mClient.notifySessionReleased(sessionInfo);
} catch (RemoteException ex) {
@@ -1412,8 +1413,8 @@
try {
manager.notifyRouteSelected(clientRecord.mPackageName,
clientRecord.mSelectedRoute);
- manager.notifyRouteTypesChanged(clientRecord.mPackageName,
- clientRecord.mDiscoveryRequest.getRouteTypes());
+ manager.notifyPreferredFeaturesChanged(clientRecord.mPackageName,
+ clientRecord.mDiscoveryPreference.getPreferredFeatures());
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to update client usage. Manager probably died.", ex);
}
@@ -1432,15 +1433,15 @@
final class SessionCreationRequest {
public final Client2Record mClientRecord;
public final MediaRoute2Info mRoute;
- public final String mRouteType;
+ public final String mRouteFeature;
public final long mRequestId;
SessionCreationRequest(@NonNull Client2Record clientRecord,
@NonNull MediaRoute2Info route,
- @NonNull String routeType, long requestId) {
+ @NonNull String routeFeature, long requestId) {
mClientRecord = clientRecord;
mRoute = route;
- mRouteType = routeType;
+ mRouteFeature = routeFeature;
mRequestId = requestId;
}
}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index c76555c..b7aa484 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -39,8 +39,8 @@
import android.media.MediaRouterClientState;
import android.media.RemoteDisplayState;
import android.media.RemoteDisplayState.RemoteDisplayInfo;
-import android.media.RouteDiscoveryRequest;
-import android.media.RouteSessionInfo;
+import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -520,7 +520,7 @@
}
// Binder call
@Override
- public void setDiscoveryRequest2(IMediaRouter2Client client, RouteDiscoveryRequest request) {
+ public void setDiscoveryRequest2(IMediaRouter2Client client, RouteDiscoveryPreference request) {
mService2.setDiscoveryRequest2(client, request);
}
@@ -552,7 +552,7 @@
// Binder call
@Override
- public List<RouteSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
+ public List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
return mService2.getActiveSessions(manager);
}
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 0ea4e63..961d3d0 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -30,12 +30,12 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.text.TextUtils;
import android.util.Log;
import com.android.internal.R;
import java.util.Collections;
+import java.util.List;
/**
* Provides routes for local playbacks such as phone speaker, wired headset, or Bluetooth speakers.
@@ -55,6 +55,7 @@
private final IAudioService mAudioService;
private final Handler mHandler;
private final Context mContext;
+ private final BluetoothRouteProvider mBtRouteProvider;
private static ComponentName sComponentName = new ComponentName(
SystemMediaRoute2Provider.class.getPackageName$(),
@@ -62,7 +63,7 @@
//TODO: Clean up these when audio manager support multiple bt devices
MediaRoute2Info mDefaultRoute;
- MediaRoute2Info mBluetoothA2dpRoute;
+ @NonNull List<MediaRoute2Info> mBluetoothRoutes = Collections.EMPTY_LIST;
final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
@@ -87,6 +88,10 @@
mAudioService = IAudioService.Stub.asInterface(
ServiceManager.getService(Context.AUDIO_SERVICE));
+ mBtRouteProvider = BluetoothRouteProvider.getInstance(context, (routes) -> {
+ mBluetoothRoutes = routes;
+ publishRoutes();
+ });
initializeRoutes();
}
@@ -141,8 +146,8 @@
: MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
.setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
.setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC))
- .addRouteType(TYPE_LIVE_AUDIO)
- .addRouteType(TYPE_LIVE_VIDEO)
+ .addFeature(TYPE_LIVE_AUDIO)
+ .addFeature(TYPE_LIVE_VIDEO)
.build();
AudioRoutesInfo newAudioRoutes = null;
@@ -157,7 +162,15 @@
updateAudioRoutes(newAudioRoutes);
}
- publishRoutes();
+ mBluetoothRoutes = mBtRouteProvider.getBluetoothRoutes();
+
+ MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
+ builder.addRoute(mDefaultRoute);
+ for (MediaRoute2Info route : mBluetoothRoutes) {
+ builder.addRoute(route);
+ }
+ setProviderState(builder.build(), Collections.emptyList());
+ mHandler.post(() -> notifyProviderState());
}
void updateAudioRoutes(AudioRoutesInfo newRoutes) {
@@ -181,25 +194,10 @@
: MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
.setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
.setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC))
- .addRouteType(TYPE_LIVE_AUDIO)
- .addRouteType(TYPE_LIVE_VIDEO)
+ .addFeature(TYPE_LIVE_AUDIO)
+ .addFeature(TYPE_LIVE_VIDEO)
.build();
- if (!TextUtils.equals(newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) {
- mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName;
- if (mCurAudioRoutesInfo.bluetoothName != null) {
- //TODO: mark as bluetooth once MediaRoute2Info has device type
- mBluetoothA2dpRoute = new MediaRoute2Info.Builder(BLUETOOTH_ROUTE_ID,
- mCurAudioRoutesInfo.bluetoothName)
- .setDescription(mContext.getResources().getText(
- R.string.bluetooth_a2dp_audio_route_name).toString())
- .addRouteType(TYPE_LIVE_AUDIO)
- .build();
- } else {
- mBluetoothA2dpRoute = null;
- }
- }
-
publishRoutes();
}
@@ -207,15 +205,13 @@
* The first route should be the currently selected system route.
* For example, if there are two system routes (BT and device speaker),
* BT will be the first route in the list.
- *
- * TODO: Support multiple BT devices
*/
void publishRoutes() {
MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
- if (mBluetoothA2dpRoute != null) {
- builder.addRoute(mBluetoothA2dpRoute);
- }
builder.addRoute(mDefaultRoute);
+ for (MediaRoute2Info route : mBluetoothRoutes) {
+ builder.addRoute(route);
+ }
setAndNotifyProviderState(builder.build(), Collections.emptyList());
}
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
index 7f650ee..b24a938 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
@@ -18,8 +18,10 @@
import static com.android.server.net.NetworkPolicyManagerService.isUidNetworkingBlockedInternal;
+import android.annotation.NonNull;
import android.net.Network;
import android.net.NetworkTemplate;
+import android.net.netstats.provider.AbstractNetworkStatsProvider;
import android.telephony.SubscriptionPlan;
import java.util.Set;
@@ -126,4 +128,12 @@
*/
public abstract void setMeteredRestrictedPackagesAsync(
Set<String> packageNames, int userId);
+
+ /**
+ * Notifies that any of the {@link AbstractNetworkStatsProvider} has reached its quota
+ * which was set through {@link AbstractNetworkStatsProvider#setLimit(String, long)}.
+ *
+ * @param tag the human readable identifier of the custom network stats provider.
+ */
+ public abstract void onStatsProviderLimitReached(@NonNull String tag);
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 4a45730..d8a4655 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -75,6 +75,7 @@
import static android.net.NetworkTemplate.MATCH_WIFI;
import static android.net.NetworkTemplate.buildTemplateMobileAll;
import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.net.netstats.provider.AbstractNetworkStatsProvider.QUOTA_UNLIMITED;
import static android.os.Trace.TRACE_TAG_NETWORK;
import static android.provider.Settings.Global.NETPOLICY_OVERRIDE_ENABLED;
import static android.provider.Settings.Global.NETPOLICY_QUOTA_ENABLED;
@@ -391,6 +392,7 @@
private static final int MSG_METERED_RESTRICTED_PACKAGES_CHANGED = 17;
private static final int MSG_SET_NETWORK_TEMPLATE_ENABLED = 18;
private static final int MSG_SUBSCRIPTION_PLANS_CHANGED = 19;
+ private static final int MSG_STATS_PROVIDER_LIMIT_REACHED = 20;
private static final int UID_MSG_STATE_CHANGED = 100;
private static final int UID_MSG_GONE = 101;
@@ -4518,6 +4520,14 @@
mListeners.finishBroadcast();
return true;
}
+ case MSG_STATS_PROVIDER_LIMIT_REACHED: {
+ mNetworkStats.forceUpdate();
+ synchronized (mNetworkPoliciesSecondLock) {
+ updateNetworkEnabledNL();
+ updateNotificationsNL();
+ }
+ return true;
+ }
case MSG_LIMIT_REACHED: {
final String iface = (String) msg.obj;
@@ -4573,14 +4583,18 @@
return true;
}
case MSG_UPDATE_INTERFACE_QUOTA: {
- removeInterfaceQuota((String) msg.obj);
+ final String iface = (String) msg.obj;
// int params need to be stitched back into a long
- setInterfaceQuota((String) msg.obj,
- ((long) msg.arg1 << 32) | (msg.arg2 & 0xFFFFFFFFL));
+ final long quota = ((long) msg.arg1 << 32) | (msg.arg2 & 0xFFFFFFFFL);
+ removeInterfaceQuota(iface);
+ setInterfaceQuota(iface, quota);
+ mNetworkStats.setStatsProviderLimit(iface, quota);
return true;
}
case MSG_REMOVE_INTERFACE_QUOTA: {
- removeInterfaceQuota((String) msg.obj);
+ final String iface = (String) msg.obj;
+ removeInterfaceQuota(iface);
+ mNetworkStats.setStatsProviderLimit(iface, QUOTA_UNLIMITED);
return true;
}
case MSG_RESET_FIREWALL_RULES_BY_UID: {
@@ -5235,6 +5249,12 @@
mHandler.obtainMessage(MSG_METERED_RESTRICTED_PACKAGES_CHANGED,
userId, 0, packageNames).sendToTarget();
}
+
+ @Override
+ public void onStatsProviderLimitReached(@NonNull String tag) {
+ Log.v(TAG, "onStatsProviderLimitReached: " + tag);
+ mHandler.obtainMessage(MSG_STATS_PROVIDER_LIMIT_REACHED).sendToTarget();
+ }
}
private void setMeteredRestrictedPackagesInternal(Set<String> packageNames, int userId) {
diff --git a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
index 4843ede..6d72cb5 100644
--- a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
@@ -16,6 +16,7 @@
package com.android.server.net;
+import android.annotation.NonNull;
import android.net.NetworkStats;
import android.net.NetworkTemplate;
@@ -34,4 +35,10 @@
/** Force update of statistics. */
public abstract void forceUpdate();
+
+ /**
+ * Set the quota limit to all registered custom network stats providers.
+ * Note that invocation of any interface will be sent to all providers.
+ */
+ public abstract void setStatsProviderLimit(@NonNull String iface, long quota);
}
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 7a6f297..1dcff07 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.ACCESS_NETWORK_STATE;
import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
+import static android.Manifest.permission.UPDATE_DEVICE_STATS;
import static android.content.Intent.ACTION_SHUTDOWN;
import static android.content.Intent.ACTION_UID_REMOVED;
import static android.content.Intent.ACTION_USER_REMOVED;
@@ -71,6 +72,7 @@
import static com.android.server.NetworkManagementSocketTagger.setKernelCounterSet;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.usage.NetworkStatsManager;
@@ -97,6 +99,9 @@
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.net.TrafficStats;
+import android.net.netstats.provider.INetworkStatsProvider;
+import android.net.netstats.provider.INetworkStatsProviderCallback;
+import android.net.netstats.provider.NetworkStatsProviderCallback;
import android.os.BestClock;
import android.os.Binder;
import android.os.DropBoxManager;
@@ -109,6 +114,7 @@
import android.os.Message;
import android.os.Messenger;
import android.os.PowerManager;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
@@ -176,7 +182,7 @@
* This avoids firing the global alert too often on devices with high transfer speeds and
* high quota.
*/
- private static final int PERFORM_POLL_DELAY_MS = 1000;
+ private static final int DEFAULT_PERFORM_POLL_DELAY_MS = 1000;
private static final String TAG_NETSTATS_ERROR = "netstats_error";
@@ -220,6 +226,7 @@
*/
public interface NetworkStatsSettings {
public long getPollInterval();
+ public long getPollDelay();
public boolean getSampleEnabled();
public boolean getAugmentEnabled();
@@ -248,6 +255,7 @@
}
private final Object mStatsLock = new Object();
+ private final Object mStatsProviderLock = new Object();
/** Set of currently active ifaces. */
@GuardedBy("mStatsLock")
@@ -272,6 +280,9 @@
private final DropBoxNonMonotonicObserver mNonMonotonicObserver =
new DropBoxNonMonotonicObserver();
+ private final RemoteCallbackList<NetworkStatsProviderCallbackImpl> mStatsProviderCbList =
+ new RemoteCallbackList<>();
+
@GuardedBy("mStatsLock")
private NetworkStatsRecorder mDevRecorder;
@GuardedBy("mStatsLock")
@@ -502,9 +513,9 @@
}
/**
- * Register for a global alert that is delivered through
- * {@link INetworkManagementEventObserver} once a threshold amount of data
- * has been transferred.
+ * Register for a global alert that is delivered through {@link INetworkManagementEventObserver}
+ * or {@link NetworkStatsProviderCallback#onAlertReached()} once a threshold amount of data has
+ * been transferred.
*/
private void registerGlobalAlert() {
try {
@@ -514,6 +525,7 @@
} catch (RemoteException e) {
// ignored; service lives in system_server
}
+ invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.setAlert(mGlobalAlertBytes));
}
@Override
@@ -803,8 +815,7 @@
@Override
public void incrementOperationCount(int uid, int tag, int operationCount) {
if (Binder.getCallingUid() != uid) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.UPDATE_DEVICE_STATS, TAG);
+ mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG);
}
if (operationCount < 0) {
@@ -1095,7 +1106,7 @@
/**
* Observer that watches for {@link INetworkManagementService} alerts.
*/
- private INetworkManagementEventObserver mAlertObserver = new BaseNetworkObserver() {
+ private final INetworkManagementEventObserver mAlertObserver = new BaseNetworkObserver() {
@Override
public void limitReached(String limitName, String iface) {
// only someone like NMS should be calling us
@@ -1106,7 +1117,7 @@
// such a call pending; UID stats are handled during normal polling interval.
if (!mHandler.hasMessages(MSG_PERFORM_POLL_REGISTER_ALERT)) {
mHandler.sendEmptyMessageDelayed(MSG_PERFORM_POLL_REGISTER_ALERT,
- PERFORM_POLL_DELAY_MS);
+ mSettings.getPollDelay());
}
}
}
@@ -1252,6 +1263,14 @@
xtSnapshot.combineAllValues(tetherSnapshot);
devSnapshot.combineAllValues(tetherSnapshot);
+ // Snapshot for dev/xt stats from all custom stats providers. Counts per-interface data
+ // from stats providers that isn't already counted by dev and XT stats.
+ Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotStatsProvider");
+ final NetworkStats providersnapshot = getNetworkStatsFromProviders(STATS_PER_IFACE);
+ Trace.traceEnd(TRACE_TAG_NETWORK);
+ xtSnapshot.combineAllValues(providersnapshot);
+ devSnapshot.combineAllValues(providersnapshot);
+
// For xt/dev, we pass a null VPN array because usage is aggregated by UID, so VPN traffic
// can't be reattributed to responsible apps.
Trace.traceBegin(TRACE_TAG_NETWORK, "recordDev");
@@ -1355,6 +1374,10 @@
performSampleLocked();
}
+ // request asynchronous stats update from all providers for next poll.
+ // TODO: request with a valid token.
+ invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.requestStatsUpdate(0 /* unused */));
+
// finally, dispatch updated event to any listeners
final Intent updatedIntent = new Intent(ACTION_NETWORK_STATS_UPDATED);
updatedIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
@@ -1476,6 +1499,12 @@
public void forceUpdate() {
NetworkStatsService.this.forceUpdate();
}
+
+ @Override
+ public void setStatsProviderLimit(@NonNull String iface, long quota) {
+ Slog.v(TAG, "setStatsProviderLimit(" + iface + "," + quota + ")");
+ invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.setLimit(iface, quota));
+ }
}
@Override
@@ -1690,6 +1719,12 @@
uidSnapshot.combineAllValues(vtStats);
}
+ // get a stale copy of uid stats snapshot provided by providers.
+ final NetworkStats providerStats = getNetworkStatsFromProviders(STATS_PER_UID);
+ providerStats.filter(UID_ALL, ifaces, TAG_ALL);
+ mStatsFactory.apply464xlatAdjustments(uidSnapshot, providerStats, mUseBpfTrafficStats);
+ uidSnapshot.combineAllValues(providerStats);
+
uidSnapshot.combineAllValues(mUidOperations);
return uidSnapshot;
@@ -1726,6 +1761,152 @@
}
}
+ /**
+ * Registers a custom provider of {@link android.net.NetworkStats} to combine the network
+ * statistics that cannot be seen by the kernel to system. To unregister, invoke the
+ * {@code unregister()} of the returned callback.
+ *
+ * @param tag a human readable identifier of the custom network stats provider.
+ * @param provider the binder interface of
+ * {@link android.net.netstats.provider.AbstractNetworkStatsProvider} that
+ * needs to be registered to the system.
+ *
+ * @return a binder interface of
+ * {@link android.net.netstats.provider.NetworkStatsProviderCallback}, which can be
+ * used to report events to the system.
+ */
+ public @NonNull INetworkStatsProviderCallback registerNetworkStatsProvider(
+ @NonNull String tag, @NonNull INetworkStatsProvider provider) {
+ mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG);
+ Objects.requireNonNull(provider, "provider is null");
+ Objects.requireNonNull(tag, "tag is null");
+ try {
+ NetworkStatsProviderCallbackImpl callback = new NetworkStatsProviderCallbackImpl(
+ tag, provider, mAlertObserver, mStatsProviderCbList);
+ mStatsProviderCbList.register(callback);
+ Log.d(TAG, "registerNetworkStatsProvider from " + callback.mTag + " uid/pid="
+ + getCallingUid() + "/" + getCallingPid());
+ return callback;
+ } catch (RemoteException e) {
+ Log.e(TAG, "registerNetworkStatsProvider failed", e);
+ }
+ return null;
+ }
+
+ // Collect stats from local cache of providers.
+ private @NonNull NetworkStats getNetworkStatsFromProviders(int how) {
+ final NetworkStats ret = new NetworkStats(0L, 0);
+ invokeForAllStatsProviderCallbacks((cb) -> ret.combineAllValues(cb.getCachedStats(how)));
+ return ret;
+ }
+
+ @FunctionalInterface
+ private interface ThrowingConsumer<S, T extends Throwable> {
+ void accept(S s) throws T;
+ }
+
+ private void invokeForAllStatsProviderCallbacks(
+ @NonNull ThrowingConsumer<NetworkStatsProviderCallbackImpl, RemoteException> task) {
+ synchronized (mStatsProviderCbList) {
+ final int length = mStatsProviderCbList.beginBroadcast();
+ try {
+ for (int i = 0; i < length; i++) {
+ final NetworkStatsProviderCallbackImpl cb =
+ mStatsProviderCbList.getBroadcastItem(i);
+ try {
+ task.accept(cb);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Fail to broadcast to provider: " + cb.mTag, e);
+ }
+ }
+ } finally {
+ mStatsProviderCbList.finishBroadcast();
+ }
+ }
+ }
+
+ private static class NetworkStatsProviderCallbackImpl extends INetworkStatsProviderCallback.Stub
+ implements IBinder.DeathRecipient {
+ @NonNull final String mTag;
+ @NonNull private final Object mProviderStatsLock = new Object();
+ @NonNull final INetworkStatsProvider mProvider;
+ @NonNull final INetworkManagementEventObserver mAlertObserver;
+ @NonNull final RemoteCallbackList<NetworkStatsProviderCallbackImpl> mStatsProviderCbList;
+
+ @GuardedBy("mProviderStatsLock")
+ // STATS_PER_IFACE and STATS_PER_UID
+ private final NetworkStats mIfaceStats = new NetworkStats(0L, 0);
+ @GuardedBy("mProviderStatsLock")
+ private final NetworkStats mUidStats = new NetworkStats(0L, 0);
+
+ NetworkStatsProviderCallbackImpl(
+ @NonNull String tag, @NonNull INetworkStatsProvider provider,
+ @NonNull INetworkManagementEventObserver alertObserver,
+ @NonNull RemoteCallbackList<NetworkStatsProviderCallbackImpl> cbList)
+ throws RemoteException {
+ mTag = tag;
+ mProvider = provider;
+ mProvider.asBinder().linkToDeath(this, 0);
+ mAlertObserver = alertObserver;
+ mStatsProviderCbList = cbList;
+ }
+
+ @NonNull
+ public NetworkStats getCachedStats(int how) {
+ synchronized (mProviderStatsLock) {
+ NetworkStats stats;
+ switch (how) {
+ case STATS_PER_IFACE:
+ stats = mIfaceStats;
+ break;
+ case STATS_PER_UID:
+ stats = mUidStats;
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid type: " + how);
+ }
+ // Return a defensive copy instead of local reference.
+ return stats.clone();
+ }
+ }
+
+ @Override
+ public void onStatsUpdated(int token, @Nullable NetworkStats ifaceStats,
+ @Nullable NetworkStats uidStats) {
+ // TODO: 1. Use token to map ifaces to correct NetworkIdentity.
+ // 2. Store the difference and store it directly to the recorder.
+ synchronized (mProviderStatsLock) {
+ if (ifaceStats != null) mIfaceStats.combineAllValues(ifaceStats);
+ if (uidStats != null) mUidStats.combineAllValues(uidStats);
+ }
+ }
+
+ @Override
+ public void onAlertReached() throws RemoteException {
+ mAlertObserver.limitReached(LIMIT_GLOBAL_ALERT, null /* unused */);
+ }
+
+ @Override
+ public void onLimitReached() {
+ Log.d(TAG, mTag + ": onLimitReached");
+ LocalServices.getService(NetworkPolicyManagerInternal.class)
+ .onStatsProviderLimitReached(mTag);
+ }
+
+ @Override
+ public void binderDied() {
+ Log.d(TAG, mTag + ": binderDied");
+ mStatsProviderCbList.unregister(this);
+ }
+
+ @Override
+ public void unregister() {
+ Log.d(TAG, mTag + ": unregister");
+ mStatsProviderCbList.unregister(this);
+ }
+
+ }
+
@VisibleForTesting
static class HandlerCallback implements Handler.Callback {
private final NetworkStatsService mService;
@@ -1815,6 +1996,10 @@
return getGlobalLong(NETSTATS_POLL_INTERVAL, 30 * MINUTE_IN_MILLIS);
}
@Override
+ public long getPollDelay() {
+ return DEFAULT_PERFORM_POLL_DELAY_MS;
+ }
+ @Override
public long getGlobalAlertBytes(long def) {
return getGlobalLong(NETSTATS_GLOBAL_ALERT_BYTES, def);
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index b782ca96..3c31f6a 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -83,6 +83,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -925,7 +926,7 @@
/**
* Updates the target packages' set of enabled overlays in PackageManager.
*/
- private void updateOverlayPaths(int userId, List<String> targetPackageNames) {
+ private ArrayList<String> updateOverlayPaths(int userId, List<String> targetPackageNames) {
try {
traceBegin(TRACE_TAG_RRO, "OMS#updateOverlayPaths " + targetPackageNames);
if (DEBUG) {
@@ -955,6 +956,7 @@
}
}
+ final HashSet<String> updatedPackages = new HashSet<>();
final int n = targetPackageNames.size();
for (int i = 0; i < n; i++) {
final String targetPackageName = targetPackageNames.get(i);
@@ -965,11 +967,13 @@
}
if (!pm.setEnabledOverlayPackages(
- userId, targetPackageName, pendingChanges.get(targetPackageName))) {
+ userId, targetPackageName, pendingChanges.get(targetPackageName),
+ updatedPackages)) {
Slog.e(TAG, String.format("Failed to change enabled overlays for %s user %d",
targetPackageName, userId));
}
}
+ return new ArrayList<>(updatedPackages);
} finally {
traceEnd(TRACE_TAG_RRO);
}
@@ -980,10 +984,10 @@
}
private void updateAssets(final int userId, List<String> targetPackageNames) {
- updateOverlayPaths(userId, targetPackageNames);
final IActivityManager am = ActivityManager.getService();
try {
- am.scheduleApplicationInfoChanged(targetPackageNames, userId);
+ final ArrayList<String> updatedPaths = updateOverlayPaths(userId, targetPackageNames);
+ am.scheduleApplicationInfoChanged(updatedPaths, userId);
} catch (RemoteException e) {
// Intentionally left empty.
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 019c952..9623542 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -685,7 +685,7 @@
// Static RROs targeting to "android", ie framework-res.apk, are handled by native layers.
if (targetPackage != null && overlayPackage != null
&& !("android".equals(targetPackageName)
- && overlayPackage.isStaticOverlayPackage())) {
+ && overlayPackage.isStaticOverlayPackage())) {
mIdmapManager.createIdmap(targetPackage, overlayPackage, userId);
}
@@ -703,9 +703,9 @@
if (currentState != newState) {
if (DEBUG) {
Slog.d(TAG, String.format("%s:%d: %s -> %s",
- overlayPackageName, userId,
- OverlayInfo.stateToString(currentState),
- OverlayInfo.stateToString(newState)));
+ overlayPackageName, userId,
+ OverlayInfo.stateToString(currentState),
+ OverlayInfo.stateToString(newState)));
}
modified |= mSettings.setState(overlayPackageName, userId, newState);
}
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index a009183..2807909 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -55,7 +55,6 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -376,6 +375,33 @@
}, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
}
+ private void populatePackageNameToApexModuleNameIfNeeded() {
+ synchronized (mLock) {
+ if (mPackageNameToApexModuleName != null) {
+ return;
+ }
+ try {
+ mPackageNameToApexModuleName = new ArrayMap<>();
+ final ApexInfo[] allPkgs = mApexService.getAllPackages();
+ for (int i = 0; i < allPkgs.length; i++) {
+ ApexInfo ai = allPkgs[i];
+ PackageParser.PackageLite pkgLite;
+ try {
+ File apexFile = new File(ai.modulePath);
+ pkgLite = PackageParser.parsePackageLite(apexFile, 0);
+ } catch (PackageParser.PackageParserException pe) {
+ throw new IllegalStateException("Unable to parse: "
+ + ai.modulePath, pe);
+ }
+ mPackageNameToApexModuleName.put(pkgLite.packageName, ai.moduleName);
+ }
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Unable to retrieve packages from apexservice: ", re);
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
private void populateAllPackagesCacheIfNeeded() {
synchronized (mLock) {
if (mAllPackagesCache != null) {
@@ -383,7 +409,6 @@
}
try {
mAllPackagesCache = new ArrayList<>();
- mPackageNameToApexModuleName = new HashMap<>();
HashSet<String> activePackagesSet = new HashSet<>();
HashSet<String> factoryPackagesSet = new HashSet<>();
final ApexInfo[] allPkgs = mApexService.getAllPackages();
@@ -409,7 +434,6 @@
final PackageInfo packageInfo =
PackageParser.generatePackageInfo(pkg, ai, flags);
mAllPackagesCache.add(packageInfo);
- mPackageNameToApexModuleName.put(packageInfo.packageName, ai.moduleName);
if (ai.isActive) {
if (activePackagesSet.contains(packageInfo.packageName)) {
throw new IllegalStateException(
@@ -612,8 +636,7 @@
@Override
List<String> getApksInApex(String apexPackageName) {
- // TODO(b/142712057): Avoid calling populateAllPackagesCacheIfNeeded during boot.
- populateAllPackagesCacheIfNeeded();
+ populatePackageNameToApexModuleNameIfNeeded();
synchronized (mLock) {
String moduleName = mPackageNameToApexModuleName.get(apexPackageName);
if (moduleName == null) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 17870eb..dad32cd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -11602,6 +11602,23 @@
return false;
}
SharedLibraryInfo libraryInfo = versionedLib.valueAt(libIdx);
+
+ // Remove the shared library overlays from its dependent packages.
+ for (int currentUserId : UserManagerService.getInstance().getUserIds()) {
+ final List<VersionedPackage> dependents = getPackagesUsingSharedLibraryLPr(
+ libraryInfo, 0, currentUserId);
+ if (dependents == null) {
+ continue;
+ }
+ for (VersionedPackage dependentPackage : dependents) {
+ final PackageSetting ps = mSettings.mPackages.get(
+ dependentPackage.getPackageName());
+ if (ps != null) {
+ ps.setOverlayPathsForLibrary(libraryInfo.getName(), null, currentUserId);
+ }
+ }
+ }
+
versionedLib.remove(version);
if (versionedLib.size() <= 0) {
mSharedLibraries.remove(name);
@@ -15210,6 +15227,29 @@
// upcoming call to mSettings.writeLPr().
}
}
+
+ // Retrieve the overlays for shared libraries of the package.
+ if (pkg.getUsesLibraryInfos() != null) {
+ for (SharedLibraryInfo sharedLib : pkg.getUsesLibraryInfos()) {
+ for (int currentUserId : UserManagerService.getInstance().getUserIds()) {
+ if (!sharedLib.isDynamic()) {
+ // TODO(146804378): Support overlaying static shared libraries
+ continue;
+ }
+ final PackageSetting libPs = mSettings.mPackages.get(
+ sharedLib.getPackageName());
+ if (libPs == null) {
+ continue;
+ }
+ final String[] overlayPaths = libPs.getOverlayPaths(currentUserId);
+ if (overlayPaths != null) {
+ ps.setOverlayPathsForLibrary(sharedLib.getName(),
+ Arrays.asList(overlayPaths), currentUserId);
+ }
+ }
+ }
+ }
+
// It's implied that when a user requests installation, they want the app to be
// installed and enabled.
if (userId != UserHandle.USER_ALL) {
@@ -23249,9 +23289,11 @@
@Override
public boolean setEnabledOverlayPackages(int userId, @NonNull String targetPackageName,
- @Nullable List<String> overlayPackageNames) {
+ @Nullable List<String> overlayPackageNames,
+ @NonNull Collection<String> outUpdatedPackageNames) {
synchronized (mLock) {
- if (targetPackageName == null || mPackages.get(targetPackageName) == null) {
+ final AndroidPackage targetPkg = mPackages.get(targetPackageName);
+ if (targetPackageName == null || targetPkg == null) {
Slog.e(TAG, "failed to find package " + targetPackageName);
return false;
}
@@ -23270,8 +23312,41 @@
}
}
+ ArraySet<String> updatedPackageNames = null;
+ if (targetPkg.getLibraryNames() != null) {
+ // Set the overlay paths for dependencies of the shared library.
+ updatedPackageNames = new ArraySet<>();
+ for (String libName : targetPkg.getLibraryNames()) {
+ final SharedLibraryInfo info = getSharedLibraryInfoLPr(libName,
+ SharedLibraryInfo.VERSION_UNDEFINED);
+ if (info == null) {
+ continue;
+ }
+ final List<VersionedPackage> dependents = getPackagesUsingSharedLibraryLPr(
+ info, 0, userId);
+ if (dependents == null) {
+ continue;
+ }
+ for (VersionedPackage dependent : dependents) {
+ final PackageSetting ps = mSettings.mPackages.get(
+ dependent.getPackageName());
+ if (ps == null) {
+ continue;
+ }
+ ps.setOverlayPathsForLibrary(libName, overlayPaths, userId);
+ updatedPackageNames.add(dependent.getPackageName());
+ }
+ }
+ }
+
final PackageSetting ps = mSettings.mPackages.get(targetPackageName);
ps.setOverlayPaths(overlayPaths, userId);
+
+ outUpdatedPackageNames.add(targetPackageName);
+ if (updatedPackageNames != null) {
+ outUpdatedPackageNames.addAll(updatedPackageNames);
+ }
+
return true;
}
}
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 0c0b93b..f1ac0af 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -41,6 +41,7 @@
import java.io.File;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -312,12 +313,21 @@
}
void setOverlayPaths(List<String> overlayPaths, int userId) {
- modifyUserState(userId).overlayPaths = overlayPaths == null ? null :
- overlayPaths.toArray(new String[overlayPaths.size()]);
+ modifyUserState(userId).setOverlayPaths(overlayPaths == null ? null :
+ overlayPaths.toArray(new String[overlayPaths.size()]));
}
String[] getOverlayPaths(int userId) {
- return readUserState(userId).overlayPaths;
+ return readUserState(userId).getOverlayPaths();
+ }
+
+ void setOverlayPathsForLibrary(String libName, List<String> overlayPaths, int userId) {
+ modifyUserState(userId).setSharedLibraryOverlayPaths(libName,
+ overlayPaths == null ? null : overlayPaths.toArray(new String[0]));
+ }
+
+ Map<String, String[]> getOverlayPathsForLibrary(int userId) {
+ return readUserState(userId).getSharedLibraryOverlayPaths();
}
/** Only use for testing. Do NOT use in production code. */
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index ec84b51..3e665c3 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4742,6 +4742,22 @@
}
}
+ Map<String, String[]> sharedLibraryOverlayPaths =
+ ps.getOverlayPathsForLibrary(user.id);
+ if (sharedLibraryOverlayPaths != null) {
+ for (Map.Entry<String, String[]> libOverlayPaths :
+ sharedLibraryOverlayPaths.entrySet()) {
+ if (libOverlayPaths.getValue() == null) {
+ continue;
+ }
+ pw.print(prefix); pw.print(" ");
+ pw.print(libOverlayPaths.getKey()); pw.println(" overlay paths:");
+ for (String path : libOverlayPaths.getValue()) {
+ pw.print(prefix); pw.print(" "); pw.println(path);
+ }
+ }
+ }
+
String lastDisabledAppCaller = ps.getLastDisabledAppCaller(user.id);
if (lastDisabledAppCaller != null) {
pw.print(prefix); pw.print(" lastDisabledCaller: ");
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 6d6ec25..46893b2 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -592,12 +592,6 @@
getDefaultSystemHandlerActivityPackageForCategory(Intent.CATEGORY_APP_MAPS, userId),
userId, ALWAYS_LOCATION_PERMISSIONS);
- // Gallery
- grantPermissionsToSystemPackage(
- getDefaultSystemHandlerActivityPackageForCategory(
- Intent.CATEGORY_APP_GALLERY, userId),
- userId, STORAGE_PERMISSIONS);
-
// Email
grantPermissionsToSystemPackage(
getDefaultSystemHandlerActivityPackageForCategory(
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
index 8385f40..a641f06 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
@@ -24,6 +24,7 @@
import android.hardware.soundtrigger.V2_3.Properties;
import android.media.audio.common.AudioConfig;
import android.media.audio.common.AudioOffloadInfo;
+import android.media.soundtrigger_middleware.AudioCapabilities;
import android.media.soundtrigger_middleware.ConfidenceLevel;
import android.media.soundtrigger_middleware.ModelParameter;
import android.media.soundtrigger_middleware.ModelParameterRange;
@@ -74,6 +75,8 @@
@NonNull Properties hidlProperties) {
SoundTriggerModuleProperties aidlProperties = hidl2aidlProperties(hidlProperties.base);
aidlProperties.supportedModelArch = hidlProperties.supportedModelArch;
+ aidlProperties.audioCapabilities =
+ hidl2aidlAudioCapabilities(hidlProperties.audioCapabilities);
return aidlProperties;
}
@@ -209,16 +212,17 @@
return hidlModel;
}
- static @NonNull
- ISoundTriggerHw.RecognitionConfig aidl2hidlRecognitionConfig(
+ static @NonNull android.hardware.soundtrigger.V2_3.RecognitionConfig aidl2hidlRecognitionConfig(
@NonNull RecognitionConfig aidlConfig) {
- ISoundTriggerHw.RecognitionConfig hidlConfig = new ISoundTriggerHw.RecognitionConfig();
- hidlConfig.header.captureRequested = aidlConfig.captureRequested;
+ android.hardware.soundtrigger.V2_3.RecognitionConfig hidlConfig =
+ new android.hardware.soundtrigger.V2_3.RecognitionConfig();
+ hidlConfig.base.header.captureRequested = aidlConfig.captureRequested;
for (PhraseRecognitionExtra aidlPhraseExtra : aidlConfig.phraseRecognitionExtras) {
- hidlConfig.header.phrases.add(aidl2hidlPhraseRecognitionExtra(aidlPhraseExtra));
+ hidlConfig.base.header.phrases.add(aidl2hidlPhraseRecognitionExtra(aidlPhraseExtra));
}
- hidlConfig.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlConfig.data,
+ hidlConfig.base.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlConfig.data,
"SoundTrigger RecognitionConfig");
+ hidlConfig.audioCapabilities = aidlConfig.audioCapabilities;
return hidlConfig;
}
@@ -395,4 +399,17 @@
return android.hardware.soundtrigger.V2_3.ModelParameter.INVALID;
}
}
+
+ static int hidl2aidlAudioCapabilities(int hidlCapabilities) {
+ int aidlCapabilities = 0;
+ if ((hidlCapabilities
+ & android.hardware.soundtrigger.V2_3.AudioCapabilities.ECHO_CANCELLATION) != 0) {
+ aidlCapabilities |= AudioCapabilities.ECHO_CANCELLATION;
+ }
+ if ((hidlCapabilities
+ & android.hardware.soundtrigger.V2_3.AudioCapabilities.NOISE_SUPPRESSION) != 0) {
+ aidlCapabilities |= AudioCapabilities.NOISE_SUPPRESSION;
+ }
+ return aidlCapabilities;
+ }
}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
index dbf91a9..a42d292 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
@@ -66,12 +66,17 @@
return model_2_0;
}
- static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_1_to_2_0(
- android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config) {
+ static android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_3_to_2_1(
+ android.hardware.soundtrigger.V2_3.RecognitionConfig config) {
+ return config.base;
+ }
+
+ static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_3_to_2_0(
+ android.hardware.soundtrigger.V2_3.RecognitionConfig config) {
android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config_2_0 =
- config.header;
+ config.base.header;
// Note: this mutates the input!
- config_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(config.data);
+ config_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(config.base.data);
return config_2_0;
}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
index 2f024a5..8b434bd 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
@@ -92,12 +92,12 @@
void stopAllRecognitions();
/**
- * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#startRecognition_2_1(int,
- * android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig,
+ * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#startRecognition_2_3(int,
+ * android.hardware.soundtrigger.V2_3.RecognitionConfig,
* android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback, int)
*/
void startRecognition(int modelHandle,
- android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+ android.hardware.soundtrigger.V2_3.RecognitionConfig config,
SoundTriggerHw2Compat.Callback callback, int cookie);
/**
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
index 3354c56..2f087f4 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
@@ -217,16 +217,15 @@
@Override
public void startRecognition(int modelHandle,
- android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+ android.hardware.soundtrigger.V2_3.RecognitionConfig config,
Callback callback, int cookie) {
try {
try {
- int retval = as2_1().startRecognition_2_1(modelHandle, config,
- new SoundTriggerCallback(callback), cookie);
- handleHalStatus(retval, "startRecognition_2_1");
+ int retval = as2_3().startRecognition_2_3(modelHandle, config);
+ handleHalStatus(retval, "startRecognition_2_3");
} catch (NotSupported e) {
// Fall-back to the 2.0 version:
- startRecognition_2_0(modelHandle, config, callback, cookie);
+ startRecognition_2_1(modelHandle, config, callback, cookie);
}
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
@@ -369,13 +368,31 @@
return handle.get();
}
+ private void startRecognition_2_1(int modelHandle,
+ android.hardware.soundtrigger.V2_3.RecognitionConfig config,
+ Callback callback, int cookie) {
+ try {
+ try {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config_2_1 =
+ Hw2CompatUtil.convertRecognitionConfig_2_3_to_2_1(config);
+ int retval = as2_1().startRecognition_2_1(modelHandle, config_2_1,
+ new SoundTriggerCallback(callback), cookie);
+ handleHalStatus(retval, "startRecognition_2_1");
+ } catch (NotSupported e) {
+ // Fall-back to the 2.0 version:
+ startRecognition_2_0(modelHandle, config, callback, cookie);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
private void startRecognition_2_0(int modelHandle,
- android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+ android.hardware.soundtrigger.V2_3.RecognitionConfig config,
Callback callback, int cookie)
throws RemoteException {
-
android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config_2_0 =
- Hw2CompatUtil.convertRecognitionConfig_2_1_to_2_0(config);
+ Hw2CompatUtil.convertRecognitionConfig_2_3_to_2_0(config);
int retval = as2_0().startRecognition(modelHandle, config_2_0,
new SoundTriggerCallback(callback), cookie);
handleHalStatus(retval, "startRecognition");
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index 81789e1..f024ede 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -401,10 +401,10 @@
notifyAbort();
return;
}
- android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig hidlConfig =
+ android.hardware.soundtrigger.V2_3.RecognitionConfig hidlConfig =
ConversionUtil.aidl2hidlRecognitionConfig(config);
- hidlConfig.header.captureDevice = mSession.mDeviceHandle;
- hidlConfig.header.captureHandle = mSession.mIoHandle;
+ hidlConfig.base.header.captureDevice = mSession.mDeviceHandle;
+ hidlConfig.base.header.captureHandle = mSession.mIoHandle;
mHalService.startRecognition(mHandle, hidlConfig, this, 0);
setState(ModelState.ACTIVE);
}
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index f661b5e..c964795 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -21,7 +21,6 @@
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.PhoneTimeSuggestion;
-import android.content.Intent;
import android.os.TimestampedValue;
import java.io.PrintWriter;
@@ -73,9 +72,6 @@
/** Release the wake lock acquired by a call to {@link #acquireWakeLock()}. */
void releaseWakeLock();
-
- /** Send the supplied intent as a stick broadcast. */
- void sendStickyBroadcast(@NonNull Intent intent);
}
/** Initialize the strategy. */
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
index 42d59d5..9b89d94 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
@@ -20,11 +20,9 @@
import android.app.AlarmManager;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.Intent;
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.SystemProperties;
-import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
@@ -112,11 +110,6 @@
mWakeLock.release();
}
- @Override
- public void sendStickyBroadcast(@NonNull Intent intent) {
- mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
- }
-
private void checkWakeLockHeld() {
if (!mWakeLock.isHeld()) {
Slog.wtf(TAG, "WakeLock " + mWakeLock + " not held");
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index da848d8..e95fc4a 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -23,9 +23,7 @@
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.PhoneTimeSuggestion;
-import android.content.Intent;
import android.os.TimestampedValue;
-import android.telephony.TelephonyManager;
import android.util.LocalLog;
import android.util.Slog;
@@ -535,17 +533,6 @@
} else {
mLastAutoSystemClockTimeSet = null;
}
-
- // Historically, Android has sent a TelephonyManager.ACTION_NETWORK_SET_TIME broadcast only
- // when setting the time using NITZ.
- if (origin == ORIGIN_PHONE) {
- // Send a broadcast that telephony code used to send after setting the clock.
- // TODO Remove this broadcast as soon as there are no remaining listeners.
- Intent intent = new Intent(TelephonyManager.ACTION_NETWORK_SET_TIME);
- intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
- intent.putExtra("time", newSystemClockMillis);
- mCallback.sendStickyBroadcast(intent);
- }
}
/**
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d5ff280..6b81fdd 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -129,6 +129,7 @@
import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.DeviceStateCache;
+import android.app.admin.FactoryResetProtectionPolicy;
import android.app.admin.NetworkEvent;
import android.app.admin.PasswordMetrics;
import android.app.admin.PasswordPolicy;
@@ -268,6 +269,7 @@
import com.android.internal.widget.PasswordValidationError;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
+import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo;
@@ -1006,6 +1008,8 @@
private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL =
"cross-profile-calendar-packages-null";
private static final String TAG_CROSS_PROFILE_PACKAGES = "cross-profile-packages";
+ private static final String TAG_FACTORY_RESET_PROTECTION_POLICY =
+ "factory_reset_protection_policy";
DeviceAdminInfo info;
@@ -1016,6 +1020,9 @@
@NonNull
PasswordPolicy mPasswordPolicy = new PasswordPolicy();
+ @Nullable
+ FactoryResetProtectionPolicy mFactoryResetProtectionPolicy = null;
+
static final long DEF_MAXIMUM_TIME_TO_UNLOCK = 0;
long maximumTimeToUnlock = DEF_MAXIMUM_TIME_TO_UNLOCK;
@@ -1351,6 +1358,11 @@
mCrossProfileCalendarPackages);
}
writePackageListToXml(out, TAG_CROSS_PROFILE_PACKAGES, mCrossProfilePackages);
+ if (mFactoryResetProtectionPolicy != null) {
+ out.startTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+ mFactoryResetProtectionPolicy.writeToXml(out);
+ out.endTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+ }
}
void writeTextToXml(XmlSerializer out, String tag, String text) throws IOException {
@@ -1584,6 +1596,9 @@
mCrossProfileCalendarPackages = null;
} else if (TAG_CROSS_PROFILE_PACKAGES.equals(tag)) {
mCrossProfilePackages = readPackageList(parser, tag);
+ } else if (TAG_FACTORY_RESET_PROTECTION_POLICY.equals(tag)) {
+ mFactoryResetProtectionPolicy = FactoryResetProtectionPolicy.readFromXml(
+ parser);
} else {
Slog.w(LOG_TAG, "Unknown admin tag: " + tag);
XmlUtils.skipCurrentTag(parser);
@@ -2036,6 +2051,10 @@
return IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE));
}
+ PersistentDataBlockManagerInternal getPersistentDataBlockManagerInternal() {
+ return LocalServices.getService(PersistentDataBlockManagerInternal.class);
+ }
+
LockSettingsInternal getLockSettingsInternal() {
return LocalServices.getService(LockSettingsInternal.class);
}
@@ -6736,6 +6755,67 @@
}
@Override
+ public void setFactoryResetProtectionPolicy(ComponentName who,
+ @Nullable FactoryResetProtectionPolicy policy) {
+ if (!mHasFeature) {
+ return;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null");
+
+ final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow();
+ final int userId = mInjector.userHandleGetCallingUserId();
+ synchronized (getLockObject()) {
+ ActiveAdmin admin = getActiveAdminForCallerLocked(
+ who, DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
+ admin.mFactoryResetProtectionPolicy = policy;
+ saveSettingsLocked(userId);
+ }
+
+ mInjector.binderWithCleanCallingIdentity(() -> mContext.sendBroadcastAsUser(
+ new Intent(DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
+ UserHandle.getUserHandleForUid(frpManagementAgentUid)));
+
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_FACTORY_RESET_PROTECTION)
+ .setAdmin(who)
+ .write();
+ }
+
+ @Override
+ public FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(
+ @Nullable ComponentName who) {
+ if (!mHasFeature) {
+ return null;
+ }
+
+ final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow();
+ ActiveAdmin admin;
+ synchronized (getLockObject()) {
+ if (who == null) {
+ if ((frpManagementAgentUid != mInjector.binderGetCallingUid())) {
+ throw new SecurityException(
+ "Must be called by the FRP management agent on device");
+ }
+ admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
+ UserHandle.getUserId(frpManagementAgentUid));
+ } else {
+ admin = getActiveAdminForCallerLocked(
+ who, DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
+ }
+ }
+ return admin != null ? admin.mFactoryResetProtectionPolicy : null;
+ }
+
+ private int getFrpManagementAgentUidOrThrow() {
+ PersistentDataBlockManagerInternal pdb = mInjector.getPersistentDataBlockManagerInternal();
+ if ((pdb == null) || (pdb.getAllowedUid() == -1)) {
+ throw new UnsupportedOperationException(
+ "The persistent data block service is not supported on this device");
+ }
+ return pdb.getAllowedUid();
+ }
+
+ @Override
public void getRemoveWarning(ComponentName comp, final RemoteCallback result, int userHandle) {
if (!mHasFeature) {
return;
@@ -8131,6 +8211,14 @@
return null;
}
+ ActiveAdmin getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(int userId) {
+ ActiveAdmin admin = getDeviceOwnerAdminLocked();
+ if (admin == null) {
+ admin = getProfileOwnerOfOrganizationOwnedDeviceLocked(userId);
+ }
+ return admin;
+ }
+
@Override
public void clearDeviceOwner(String packageName) {
Objects.requireNonNull(packageName, "packageName is null");
@@ -12212,6 +12300,7 @@
case DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE:
return checkManagedProfileProvisioningPreCondition(packageName, callingUserId);
case DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE:
+ case DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE:
return checkDeviceOwnerProvisioningPreCondition(callingUserId);
case DevicePolicyManager.ACTION_PROVISION_MANAGED_USER:
return checkManagedUserProvisioningPreCondition(callingUserId);
diff --git a/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java b/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java
index a8a258f..9c5d4ad 100644
--- a/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java
+++ b/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java
@@ -3,6 +3,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import android.os.Looper;
import android.os.SystemClock;
@@ -52,8 +53,10 @@
@Test
public void handleInjectNtpTime_cachedAgeLow_injectTime() throws InterruptedException {
- doReturn(NtpTimeHelper.NTP_INTERVAL - 1).when(mMockNtpTrustedTime).getCacheAge();
- doReturn(MOCK_NTP_TIME).when(mMockNtpTrustedTime).getCachedNtpTime();
+ NtpTrustedTime.TimeResult result = mock(NtpTrustedTime.TimeResult.class);
+ doReturn(NtpTimeHelper.NTP_INTERVAL - 1).when(result).getAgeMillis();
+ doReturn(MOCK_NTP_TIME).when(result).getTimeMillis();
+ doReturn(result).when(mMockNtpTrustedTime).getCachedTimeResult();
mNtpTimeHelper.retrieveAndInjectNtpTime();
@@ -64,7 +67,9 @@
@Test
public void handleInjectNtpTime_injectTimeFailed_injectTimeDelayed()
throws InterruptedException {
- doReturn(NtpTimeHelper.NTP_INTERVAL + 1).when(mMockNtpTrustedTime).getCacheAge();
+ NtpTrustedTime.TimeResult result1 = mock(NtpTrustedTime.TimeResult.class);
+ doReturn(NtpTimeHelper.NTP_INTERVAL + 1).when(result1).getAgeMillis();
+ doReturn(result1).when(mMockNtpTrustedTime).getCachedTimeResult();
doReturn(false).when(mMockNtpTrustedTime).forceRefresh();
mNtpTimeHelper.retrieveAndInjectNtpTime();
@@ -72,8 +77,10 @@
assertThat(mCountDownLatch.await(2, TimeUnit.SECONDS)).isFalse();
doReturn(true).when(mMockNtpTrustedTime).forceRefresh();
- doReturn(1L).when(mMockNtpTrustedTime).getCacheAge();
- doReturn(MOCK_NTP_TIME).when(mMockNtpTrustedTime).getCachedNtpTime();
+ NtpTrustedTime.TimeResult result2 = mock(NtpTrustedTime.TimeResult.class);
+ doReturn(1L).when(result2).getAgeMillis();
+ doReturn(MOCK_NTP_TIME).when(result2).getTimeMillis();
+ doReturn(result2).when(mMockNtpTrustedTime).getCachedTimeResult();
SystemClock.sleep(NtpTimeHelper.RETRY_INTERVAL);
waitForTasksToBePostedOnHandlerAndRunThem();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index 591c3a3..c223f13 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -804,6 +804,11 @@
}
@Override
+ public boolean switchToInputMethod(String imeId) {
+ return false;
+ }
+
+ @Override
public boolean isAccessibilityButtonAvailable() throws RemoteException {
return false;
}
diff --git a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
index d44476e..3de006c 100644
--- a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -57,7 +57,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
@@ -523,7 +522,6 @@
* Test that {@link BackupManagerService#dump()} dumps system user information before non-system
* user information.
*/
-
@Test
public void testDump_systemUserFirst() {
String[] args = new String[0];
@@ -539,16 +537,31 @@
}
@Test
- @Ignore("b/147012496")
- public void testGetUserForAncestralSerialNumber() {
+ public void testGetUserForAncestralSerialNumber_forSystemUser() {
BackupManagerServiceTestable.sBackupDisabled = false;
BackupManagerService backupManagerService =
new BackupManagerServiceTestable(mContextMock, mUserServices);
+ when(mUserManagerMock.getProfileIds(UserHandle.getCallingUserId(), false))
+ .thenReturn(new int[] {UserHandle.USER_SYSTEM, NON_USER_SYSTEM});
when(mUserBackupManagerService.getAncestralSerialNumber()).thenReturn(11L);
UserHandle user = backupManagerService.getUserForAncestralSerialNumber(11L);
- assertThat(user).isEqualTo(UserHandle.of(1));
+ assertThat(user).isEqualTo(UserHandle.of(UserHandle.USER_SYSTEM));
+ }
+
+ @Test
+ public void testGetUserForAncestralSerialNumber_forNonSystemUser() {
+ BackupManagerServiceTestable.sBackupDisabled = false;
+ BackupManagerService backupManagerService =
+ new BackupManagerServiceTestable(mContextMock, mUserServices);
+ when(mUserManagerMock.getProfileIds(UserHandle.getCallingUserId(), false))
+ .thenReturn(new int[] {UserHandle.USER_SYSTEM, NON_USER_SYSTEM});
+ when(mNonSystemUserBackupManagerService.getAncestralSerialNumber()).thenReturn(11L);
+
+ UserHandle user = backupManagerService.getUserForAncestralSerialNumber(11L);
+
+ assertThat(user).isEqualTo(UserHandle.of(NON_USER_SYSTEM));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index ac555fd..3a8258b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -51,6 +51,7 @@
import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
+import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.net.NetworkPolicyManagerInternal;
import java.io.File;
@@ -223,6 +224,11 @@
}
@Override
+ PersistentDataBlockManagerInternal getPersistentDataBlockManagerInternal() {
+ return services.persistentDataBlockManagerInternal;
+ }
+
+ @Override
Looper getMyLooper() {
return Looper.getMainLooper();
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 175c756..21034d3 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -31,6 +31,8 @@
import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
import static com.android.server.testutils.TestUtils.assertExpectException;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
@@ -62,6 +64,7 @@
import android.app.admin.DeviceAdminReceiver;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
+import android.app.admin.FactoryResetProtectionPolicy;
import android.app.admin.PasswordMetrics;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
@@ -2022,6 +2025,116 @@
);
}
+ public void testSetFactoryResetProtectionPolicyWithDO() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+
+ when(getServices().persistentDataBlockManagerInternal.getAllowedUid()).thenReturn(
+ DpmMockContext.CALLER_UID);
+
+ FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+ .setFactoryResetProtectionAccounts(new ArrayList<>())
+ .setFactoryResetProtectionDisabled(true)
+ .build();
+ dpm.setFactoryResetProtectionPolicy(admin1, policy);
+
+ FactoryResetProtectionPolicy result = dpm.getFactoryResetProtectionPolicy(admin1);
+ assertThat(result).isEqualTo(policy);
+ assertPoliciesAreEqual(policy, result);
+
+ verify(mContext.spiedContext).sendBroadcastAsUser(
+ MockUtils.checkIntentAction(
+ DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
+ MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+ }
+
+ public void testSetFactoryResetProtectionPolicyFailWithPO() throws Exception {
+ setupProfileOwner();
+
+ FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+ .setFactoryResetProtectionDisabled(true)
+ .build();
+
+ assertExpectException(SecurityException.class, null,
+ () -> dpm.setFactoryResetProtectionPolicy(admin1, policy));
+ }
+
+ public void testSetFactoryResetProtectionPolicyWithPOOfOrganizationOwnedDevice()
+ throws Exception {
+ setupProfileOwner();
+ configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
+
+ when(getServices().persistentDataBlockManagerInternal.getAllowedUid()).thenReturn(
+ DpmMockContext.CALLER_UID);
+
+ List<String> accounts = new ArrayList<>();
+ accounts.add("Account 1");
+ accounts.add("Account 2");
+
+ FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+ .setFactoryResetProtectionAccounts(accounts)
+ .build();
+
+ dpm.setFactoryResetProtectionPolicy(admin1, policy);
+
+ FactoryResetProtectionPolicy result = dpm.getFactoryResetProtectionPolicy(admin1);
+ assertThat(result).isEqualTo(policy);
+ assertPoliciesAreEqual(policy, result);
+
+ verify(mContext.spiedContext, times(2)).sendBroadcastAsUser(
+ MockUtils.checkIntentAction(
+ DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+ MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+ verify(mContext.spiedContext).sendBroadcastAsUser(
+ MockUtils.checkIntentAction(
+ DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED),
+ MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+ verify(mContext.spiedContext).sendBroadcastAsUser(
+ MockUtils.checkIntentAction(
+ DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
+ MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+ }
+
+ public void testGetFactoryResetProtectionPolicyWithFrpManagementAgent()
+ throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+ when(getServices().persistentDataBlockManagerInternal.getAllowedUid()).thenReturn(
+ DpmMockContext.CALLER_UID);
+
+ FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+ .setFactoryResetProtectionAccounts(new ArrayList<>())
+ .setFactoryResetProtectionDisabled(true)
+ .build();
+
+ dpm.setFactoryResetProtectionPolicy(admin1, policy);
+
+ mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ dpm.setActiveAdmin(admin1, /*replace=*/ false);
+ FactoryResetProtectionPolicy result = dpm.getFactoryResetProtectionPolicy(null);
+ assertThat(result).isEqualTo(policy);
+ assertPoliciesAreEqual(policy, result);
+
+ verify(mContext.spiedContext).sendBroadcastAsUser(
+ MockUtils.checkIntentAction(
+ DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
+ MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+ }
+
+ private void assertPoliciesAreEqual(FactoryResetProtectionPolicy expectedPolicy,
+ FactoryResetProtectionPolicy actualPolicy) {
+ assertThat(actualPolicy.isFactoryResetProtectionDisabled()).isEqualTo(
+ expectedPolicy.isFactoryResetProtectionDisabled());
+ assertAccountsAreEqual(expectedPolicy.getFactoryResetProtectionAccounts(),
+ actualPolicy.getFactoryResetProtectionAccounts());
+ }
+
+ private void assertAccountsAreEqual(List<String> expectedAccounts,
+ List<String> actualAccounts) {
+ assertThat(actualAccounts).containsExactlyElementsIn(expectedAccounts);
+ }
+
public void testGetMacAddress() throws Exception {
mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
@@ -2845,6 +2958,7 @@
mContext.packageName = admin1.getPackageName();
setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, false);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
false);
@@ -2856,6 +2970,8 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED);
assertCheckProvisioningPreCondition(
@@ -2882,6 +2998,7 @@
mContext.packageName = admin1.getPackageName();
setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
false);
@@ -2890,6 +3007,7 @@
// Test again when split user is on
when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
true);
@@ -2901,6 +3019,8 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_OK);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED);
assertCheckProvisioningPreCondition(
@@ -2913,6 +3033,8 @@
when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_OK);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED);
assertCheckProvisioningPreCondition(
@@ -2938,6 +3060,7 @@
mContext.packageName = admin1.getPackageName();
setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
false /* because of non-split user */);
@@ -2951,6 +3074,8 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_OK);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(
@@ -2995,6 +3120,8 @@
setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
false/* because of completed device setup */);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ false/* because of completed device setup */);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
false/* because of non-split user */);
@@ -3008,6 +3135,8 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_USER_SETUP_COMPLETED);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_USER_SETUP_COMPLETED);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(
@@ -3024,7 +3153,10 @@
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_HAS_DEVICE_OWNER);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_HAS_DEVICE_OWNER);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, false);
// COMP mode is allowed.
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
@@ -3153,6 +3285,7 @@
mContext.packageName = admin1.getPackageName();
setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
false /* because canAddMoreManagedProfiles returns false */);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
@@ -3167,6 +3300,8 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_OK);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER);
assertCheckProvisioningPreCondition(
@@ -3193,6 +3328,8 @@
setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
true/* it's undefined behavior. Can be changed into false in the future */);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ true/* it's undefined behavior. Can be changed into false in the future */);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
false /* because canAddMoreManagedProfiles returns false */);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
@@ -3207,6 +3344,8 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_OK);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER);
assertCheckProvisioningPreCondition(
@@ -3232,6 +3371,7 @@
mContext.packageName = admin1.getPackageName();
setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
true);
@@ -3244,6 +3384,8 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_OK);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(
@@ -3271,6 +3413,8 @@
setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
true/* it's undefined behavior. Can be changed into false in the future */);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ true/* it's undefined behavior. Can be changed into false in the future */);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
true/* it's undefined behavior. Can be changed into false in the future */);
@@ -3284,6 +3428,8 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_OK);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java
new file mode 100644
index 0000000..bc853c6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.FactoryResetProtectionPolicy;
+import android.os.Parcel;
+import android.util.Xml;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link android.app.admin.FactoryResetProtectionPolicy}.
+ *
+ * atest com.android.server.devicepolicy.FactoryResetProtectionPolicyTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class FactoryResetProtectionPolicyTest {
+
+ private static final String TAG_FACTORY_RESET_PROTECTION_POLICY =
+ "factory_reset_protection_policy";
+
+ @Test
+ public void testNonDefaultFactoryResetProtectionPolicyObject() throws Exception {
+ List<String> accounts = new ArrayList<>();
+ accounts.add("Account 1");
+ accounts.add("Account 2");
+
+ FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+ .setFactoryResetProtectionAccounts(accounts)
+ .setFactoryResetProtectionDisabled(true)
+ .build();
+
+ testParcelAndUnparcel(policy);
+ testSerializationAndDeserialization(policy);
+ }
+
+ @Test
+ public void testInvalidXmlFactoryResetProtectionPolicyObject() throws Exception {
+ List<String> accounts = new ArrayList<>();
+ accounts.add("Account 1");
+ accounts.add("Account 2");
+
+ FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+ .setFactoryResetProtectionAccounts(accounts)
+ .setFactoryResetProtectionDisabled(true)
+ .build();
+
+ testParcelAndUnparcel(policy);
+ testInvalidXmlSerializationAndDeserialization(policy);
+ }
+
+ private void testParcelAndUnparcel(FactoryResetProtectionPolicy policy) {
+ Parcel parcel = Parcel.obtain();
+ policy.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ FactoryResetProtectionPolicy actualPolicy =
+ FactoryResetProtectionPolicy.CREATOR.createFromParcel(parcel);
+ assertPoliciesAreEqual(policy, actualPolicy);
+ parcel.recycle();
+ }
+
+ private void testSerializationAndDeserialization(FactoryResetProtectionPolicy policy)
+ throws Exception {
+ ByteArrayOutputStream outStream = serialize(policy);
+ ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray());
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new InputStreamReader(inStream));
+ assertEquals(XmlPullParser.START_TAG, parser.next());
+
+ assertPoliciesAreEqual(policy, policy.readFromXml(parser));
+ }
+
+ private void testInvalidXmlSerializationAndDeserialization(FactoryResetProtectionPolicy policy)
+ throws Exception {
+ ByteArrayOutputStream outStream = serialize(policy);
+ ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray());
+ XmlPullParser parser = mock(XmlPullParser.class);
+ when(parser.next()).thenThrow(XmlPullParserException.class);
+ parser.setInput(new InputStreamReader(inStream));
+
+ // If deserialization fails, then null is returned.
+ assertNull(policy.readFromXml(parser));
+ }
+
+ private ByteArrayOutputStream serialize(FactoryResetProtectionPolicy policy)
+ throws IOException {
+ ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+ final XmlSerializer outXml = new FastXmlSerializer();
+ outXml.setOutput(outStream, StandardCharsets.UTF_8.name());
+ outXml.startDocument(null, true);
+ outXml.startTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+ policy.writeToXml(outXml);
+ outXml.endTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+ outXml.endDocument();
+ outXml.flush();
+ return outStream;
+ }
+
+ private void assertPoliciesAreEqual(FactoryResetProtectionPolicy expectedPolicy,
+ FactoryResetProtectionPolicy actualPolicy) {
+ assertEquals(expectedPolicy.isFactoryResetProtectionDisabled(),
+ actualPolicy.isFactoryResetProtectionDisabled());
+ assertAccountsAreEqual(expectedPolicy.getFactoryResetProtectionAccounts(),
+ actualPolicy.getFactoryResetProtectionAccounts());
+ }
+
+ private void assertAccountsAreEqual(List<String> expectedAccounts,
+ List<String> actualAccounts) {
+ assertEquals(expectedAccounts.size(), actualAccounts.size());
+ for (int i = 0; i < expectedAccounts.size(); i++) {
+ assertEquals(expectedAccounts.get(i), actualAccounts.get(i));
+ }
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 919a3f6..6c2c144 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -68,6 +68,7 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
+import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -117,6 +118,7 @@
public final TimeDetector timeDetector;
public final TimeZoneDetector timeZoneDetector;
public final KeyChain.KeyChainConnection keyChainConnection;
+ public final PersistentDataBlockManagerInternal persistentDataBlockManagerInternal;
/** Note this is a partial mock, not a real mock. */
public final PackageManager packageManager;
public final BuildMock buildMock = new BuildMock();
@@ -160,6 +162,7 @@
timeDetector = mock(TimeDetector.class);
timeZoneDetector = mock(TimeZoneDetector.class);
keyChainConnection = mock(KeyChain.KeyChainConnection.class, RETURNS_DEEP_STUBS);
+ persistentDataBlockManagerInternal = mock(PersistentDataBlockManagerInternal.class);
// Package manager is huge, so we use a partial mock instead.
packageManager = spy(realContext.getPackageManager());
diff --git a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
index 63189e7..dd69c66 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
@@ -16,14 +16,7 @@
package com.android.server.integrity;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.hasItems;
-import static org.junit.Assert.assertThat;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import android.content.integrity.AppInstallMetadata;
import android.content.integrity.AtomicFormula;
@@ -33,26 +26,26 @@
import android.content.integrity.Rule;
import android.util.Slog;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.integrity.parser.RuleXmlParser;
-import com.android.server.integrity.serializer.RuleXmlSerializer;
+import com.android.server.integrity.parser.RuleBinaryParser;
+import com.android.server.integrity.serializer.RuleBinarySerializer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/** Unit test for {@link IntegrityFileManager} */
-@RunWith(AndroidJUnit4.class)
+@RunWith(JUnit4.class)
public class IntegrityFileManagerTest {
private static final String TAG = "IntegrityFileManagerTest";
@@ -72,7 +65,7 @@
// Use Xml Parser/Serializer to help with debugging since we can just print the file.
mIntegrityFileManager =
new IntegrityFileManager(
- new RuleXmlParser(), new RuleXmlSerializer(), mTmpDir);
+ new RuleBinaryParser(), new RuleBinarySerializer(), mTmpDir);
Files.walk(mTmpDir.toPath())
.forEach(
path -> {
@@ -97,12 +90,19 @@
@Test
public void testGetMetadata() throws Exception {
- assertNull(mIntegrityFileManager.readMetadata());
+ assertThat(mIntegrityFileManager.readMetadata()).isNull();
mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);
- assertNotNull(mIntegrityFileManager.readMetadata());
- assertEquals(VERSION, mIntegrityFileManager.readMetadata().getVersion());
- assertEquals(RULE_PROVIDER, mIntegrityFileManager.readMetadata().getRuleProvider());
+ assertThat(mIntegrityFileManager.readMetadata()).isNotNull();
+ assertThat(mIntegrityFileManager.readMetadata().getVersion()).isEqualTo(VERSION);
+ assertThat(mIntegrityFileManager.readMetadata().getRuleProvider()).isEqualTo(RULE_PROVIDER);
+ }
+
+ @Test
+ public void testIsInitialized() throws Exception {
+ assertThat(mIntegrityFileManager.initialized()).isFalse();
+ mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);
+ assertThat(mIntegrityFileManager.initialized()).isTrue();
}
@Test
@@ -110,20 +110,8 @@
String packageName = "package";
String packageCert = "cert";
int version = 123;
- Rule packageNameRule =
- new Rule(
- new StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false),
- Rule.DENY);
- Rule packageCertRule =
- new Rule(
- new StringAtomicFormula(
- AtomicFormula.APP_CERTIFICATE,
- packageCert,
- /* isHashedValue= */ false),
- Rule.DENY);
+ Rule packageNameRule = getPackageNameIndexedRule(packageName);
+ Rule packageCertRule = getAppCertificateIndexedRule(packageCert);
Rule versionCodeRule =
new Rule(
new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.LE, version),
@@ -142,9 +130,7 @@
AtomicFormula.LE,
version))),
Rule.DENY);
- // We will test the specifics of indexing in other classes. Here, we just require that all
- // rules that are related to the given AppInstallMetadata are returned and do not assert
- // anything on other rules.
+
List<Rule> rules =
Arrays.asList(packageNameRule, packageCertRule, versionCodeRule, randomRule);
mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, rules);
@@ -159,17 +145,77 @@
.build();
List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata);
- assertThat(rulesFetched, hasItems(
- equalTo(packageNameRule),
- equalTo(packageCertRule),
- equalTo(versionCodeRule)
- ));
+ assertThat(rulesFetched)
+ .containsExactly(packageNameRule, packageCertRule, versionCodeRule, randomRule);
}
@Test
- public void testIsInitialized() throws Exception {
- assertFalse(mIntegrityFileManager.initialized());
- mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);
- assertTrue(mIntegrityFileManager.initialized());
+ public void testGetRules_indexedForManyRules() throws Exception {
+ String packageName = "package";
+ String installerName = "installer";
+ String appCertificate = "cert";
+
+ // Create a rule set with 2500 package name indexed, 2500 app certificate indexed and
+ // 500 unindexed rules.
+ List<Rule> rules = new ArrayList<>();
+
+ for (int i = 0; i < 2500; i++) {
+ rules.add(getPackageNameIndexedRule(String.format("%s%04d", packageName, i)));
+ rules.add(getAppCertificateIndexedRule(String.format("%s%04d", appCertificate, i)));
+ }
+
+ for (int i = 0; i < 70; i++) {
+ rules.add(getInstallerCertificateRule(String.format("%s%04d", installerName, i)));
+ }
+
+ // Write the rules and get them indexed.
+ mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, rules);
+
+ // Read the rules for a specific rule.
+ String installedPackageName = String.format("%s%04d", packageName, 264);
+ String installedAppCertificate = String.format("%s%04d", appCertificate, 1264);
+ AppInstallMetadata appInstallMetadata = new AppInstallMetadata.Builder()
+ .setPackageName(installedPackageName)
+ .setAppCertificate(installedAppCertificate)
+ .setVersionCode(250)
+ .setInstallerName("abc")
+ .setInstallerCertificate("abc")
+ .setIsPreInstalled(true)
+ .build();
+ List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata);
+
+ // Verify that we do not load all the rules and we have the necessary rules to evaluate.
+ assertThat(rulesFetched.size()).isEqualTo(170);
+ assertThat(rulesFetched)
+ .containsAllOf(
+ getPackageNameIndexedRule(installedPackageName),
+ getAppCertificateIndexedRule(installedAppCertificate));
+ }
+
+ private Rule getPackageNameIndexedRule(String packageName) {
+ return new Rule(
+ new StringAtomicFormula(
+ AtomicFormula.PACKAGE_NAME,
+ packageName,
+ /* isHashedValue= */ false),
+ Rule.DENY);
+ }
+
+ private Rule getAppCertificateIndexedRule(String appCertificate) {
+ return new Rule(
+ new StringAtomicFormula(
+ AtomicFormula.APP_CERTIFICATE,
+ appCertificate,
+ /* isHashedValue= */ false),
+ Rule.DENY);
+ }
+
+ private Rule getInstallerCertificateRule(String installerCert) {
+ return new Rule(
+ new StringAtomicFormula(
+ AtomicFormula.INSTALLER_NAME,
+ installerCert,
+ /* isHashedValue= */ false),
+ Rule.DENY);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
index 51f5c75..881b3d6 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
@@ -48,7 +48,6 @@
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -656,65 +655,4 @@
/* expectedExceptionMessageRegex */ "A rule must end with a '1' bit",
() -> binaryParser.parse(rule.array()));
}
-
- @Test
- public void testBinaryStream_multipleRules_indexingIdentifiesParsesIndexRangeCorrectly()
- throws Exception {
- String packageName2 = "com.test.2";
-
- byte[] ruleBytes1 = getBytes(getRulesWithPackageName("com.test.1"));
- byte[] ruleBytes2 = getBytes(getRulesWithPackageName(packageName2));
- byte[] ruleBytes3 = getBytes(getRulesWithPackageName("com.test.3"));
-
- ByteBuffer rule =
- ByteBuffer.allocate(
- DEFAULT_FORMAT_VERSION_BYTES.length
- + ruleBytes1.length
- + ruleBytes2.length
- + ruleBytes3.length);
- rule.put(DEFAULT_FORMAT_VERSION_BYTES);
- rule.put(ruleBytes1);
- rule.put(ruleBytes2);
- rule.put(ruleBytes3);
- InputStream inputStream = new ByteArrayInputStream(rule.array());
-
- RuleParser binaryParser = new RuleBinaryParser();
-
- List<RuleIndexRange> indexRanges = new ArrayList<>();
- indexRanges.add(
- new RuleIndexRange(
- DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes1.length,
- DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes1.length
- + ruleBytes2.length));
- List<Rule> rules = binaryParser.parse(inputStream, indexRanges);
-
- Rule expectedRule =
- new Rule(
- new CompoundFormula(
- CompoundFormula.NOT,
- Collections.singletonList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName2,
- /* isHashedValue= */ false))),
- Rule.DENY);
-
- assertThat(rules).isEqualTo(Collections.singletonList(expectedRule));
- }
-
- private static String getRulesWithPackageName(String packageName) {
- return START_BIT
- + COMPOUND_FORMULA_START_BITS
- + NOT
- + ATOMIC_FORMULA_START_BITS
- + PACKAGE_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(packageName.length(), VALUE_SIZE_BITS)
- + getValueBits(packageName)
- + COMPOUND_FORMULA_END_BITS
- + DENY
- + END_BIT;
-
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index c7dbad8..e1c489e 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -37,6 +37,7 @@
import static android.net.NetworkStats.IFACE_ALL;
import static android.net.NetworkStats.SET_ALL;
import static android.net.NetworkStats.TAG_ALL;
+import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkTemplate.buildTemplateMobileAll;
import static android.net.NetworkTemplate.buildTemplateWifi;
import static android.net.TrafficStats.MB_IN_BYTES;
@@ -1758,6 +1759,57 @@
}
/**
+ * Test that when StatsProvider triggers limit reached, new limit will be calculated and
+ * re-armed.
+ */
+ @Test
+ public void testStatsProviderLimitReached() throws Exception {
+ final int CYCLE_DAY = 15;
+
+ final NetworkStats stats = new NetworkStats(0L, 1);
+ stats.addEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE,
+ 2999, 1, 2000, 1, 0);
+ when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
+ .thenReturn(stats.getTotalBytes());
+ when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
+ .thenReturn(stats);
+
+ // Get active mobile network in place
+ expectMobileDefaults();
+ mService.updateNetworks();
+ verify(mStatsService).setStatsProviderLimit(TEST_IFACE, Long.MAX_VALUE);
+
+ // Set limit to 10KB.
+ setNetworkPolicies(new NetworkPolicy(
+ sTemplateMobileAll, CYCLE_DAY, TIMEZONE_UTC, WARNING_DISABLED, 10000L,
+ true));
+ postMsgAndWaitForCompletion();
+
+ // Verifies that remaining quota is set to providers.
+ verify(mStatsService).setStatsProviderLimit(TEST_IFACE, 10000L - 4999L);
+
+ reset(mStatsService);
+
+ // Increase the usage.
+ stats.addEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE,
+ 1000, 1, 999, 1, 0);
+ when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
+ .thenReturn(stats.getTotalBytes());
+ when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
+ .thenReturn(stats);
+
+ // Simulates that limit reached fires earlier by provider, but actually the quota is not
+ // yet reached.
+ final NetworkPolicyManagerInternal npmi = LocalServices
+ .getService(NetworkPolicyManagerInternal.class);
+ npmi.onStatsProviderLimitReached("TEST");
+
+ // Verifies that the limit reached leads to a force update.
+ postMsgAndWaitForCompletion();
+ verify(mStatsService).forceUpdate();
+ }
+
+ /**
* Exhaustively test isUidNetworkingBlocked to output the expected results based on external
* conditions.
*/
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
index 0f75816..cc170af 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -37,6 +37,7 @@
import android.hardware.soundtrigger.V2_3.OptionalModelParameterRange;
import android.media.audio.common.AudioChannelMask;
import android.media.audio.common.AudioFormat;
+import android.media.soundtrigger_middleware.AudioCapabilities;
import android.media.soundtrigger_middleware.ConfidenceLevel;
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
import android.media.soundtrigger_middleware.ISoundTriggerModule;
@@ -59,6 +60,7 @@
import android.os.IHwBinder;
import android.os.IHwInterface;
import android.os.RemoteException;
+import android.util.Pair;
import org.junit.Before;
import org.junit.Test;
@@ -161,6 +163,9 @@
new android.hardware.soundtrigger.V2_3.Properties();
properties.base = createDefaultProperties(supportConcurrentCapture);
properties.supportedModelArch = "supportedModelArch";
+ properties.audioCapabilities =
+ android.hardware.soundtrigger.V2_3.AudioCapabilities.ECHO_CANCELLATION
+ | android.hardware.soundtrigger.V2_3.AudioCapabilities.NOISE_SUPPRESSION;
return properties;
}
@@ -185,8 +190,11 @@
if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
assertEquals("supportedModelArch", properties.supportedModelArch);
+ assertEquals(AudioCapabilities.ECHO_CANCELLATION | AudioCapabilities.NOISE_SUPPRESSION,
+ properties.audioCapabilities);
} else {
assertEquals("", properties.supportedModelArch);
+ assertEquals(0, properties.audioCapabilities);
}
}
@@ -330,12 +338,17 @@
mService = new SoundTriggerMiddlewareImpl(mHalDriver, mAudioSessionProvider);
}
- private int loadGenericModel_2_0(ISoundTriggerModule module, int hwHandle)
- throws RemoteException {
+ private Pair<Integer, SoundTriggerHwCallback> loadGenericModel_2_0(ISoundTriggerModule module,
+ int hwHandle) throws RemoteException {
SoundModel model = createGenericSoundModel();
ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel> modelCaptor =
ArgumentCaptor.forClass(
android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel.class);
+ ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor =
+ ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class);
+ ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
doAnswer(invocation -> {
android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback =
invocation.getArgument(1);
@@ -354,7 +367,8 @@
modelEvent.model = hwHandle;
callback.soundModelCallback(modelEvent, callbackCookie);
return null;
- }).when(mHalDriver).loadSoundModel(modelCaptor.capture(), any(), anyInt(), any());
+ }).when(mHalDriver).loadSoundModel(modelCaptor.capture(), callbackCaptor.capture(),
+ cookieCaptor.capture(), any());
when(mAudioSessionProvider.acquireSession()).thenReturn(
new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
@@ -371,17 +385,23 @@
assertEquals(model.vendorUuid, ConversionUtil.hidl2aidlUuid(hidlModel.vendorUuid));
assertArrayEquals(new Byte[]{91, 92, 93, 94, 95}, hidlModel.data.toArray());
- return handle;
+ return new Pair<>(handle,
+ new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
}
- private int loadGenericModel_2_1(ISoundTriggerModule module, int hwHandle)
- throws RemoteException {
+ private Pair<Integer, SoundTriggerHwCallback> loadGenericModel_2_1(ISoundTriggerModule module,
+ int hwHandle) throws RemoteException {
android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
(android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
SoundModel model = createGenericSoundModel();
ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel> modelCaptor =
ArgumentCaptor.forClass(
android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel.class);
+ ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor =
+ ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class);
+ ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
doAnswer(invocation -> {
android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback =
invocation.getArgument(1);
@@ -400,7 +420,8 @@
modelEvent.header.model = hwHandle;
callback.soundModelCallback_2_1(modelEvent, callbackCookie);
return null;
- }).when(driver).loadSoundModel_2_1(modelCaptor.capture(), any(), anyInt(), any());
+ }).when(driver).loadSoundModel_2_1(modelCaptor.capture(), callbackCaptor.capture(),
+ cookieCaptor.capture(), any());
when(mAudioSessionProvider.acquireSession()).thenReturn(
new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
@@ -418,10 +439,12 @@
assertArrayEquals(new byte[]{91, 92, 93, 94, 95},
HidlMemoryUtil.hidlMemoryToByteArray(hidlModel.data));
- return handle;
+ return new Pair<>(handle,
+ new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
}
- private int loadGenericModel(ISoundTriggerModule module, int hwHandle) throws RemoteException {
+ private Pair<Integer, SoundTriggerHwCallback> loadGenericModel(ISoundTriggerModule module,
+ int hwHandle) throws RemoteException {
if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
return loadGenericModel_2_1(module, hwHandle);
} else {
@@ -429,12 +452,17 @@
}
}
- private int loadPhraseModel_2_0(ISoundTriggerModule module, int hwHandle)
- throws RemoteException {
+ private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel_2_0(ISoundTriggerModule module,
+ int hwHandle) throws RemoteException {
PhraseSoundModel model = createPhraseSoundModel();
ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel>
modelCaptor = ArgumentCaptor.forClass(
android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel.class);
+ ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor =
+ ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class);
+ ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
doAnswer(invocation -> {
android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback =
invocation.getArgument(
@@ -456,7 +484,8 @@
modelEvent.model = hwHandle;
callback.soundModelCallback(modelEvent, callbackCookie);
return null;
- }).when(mHalDriver).loadPhraseSoundModel(modelCaptor.capture(), any(), anyInt(), any());
+ }).when(mHalDriver).loadPhraseSoundModel(modelCaptor.capture(), callbackCaptor.capture(),
+ cookieCaptor.capture(), any());
when(mAudioSessionProvider.acquireSession()).thenReturn(
new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
@@ -488,11 +517,12 @@
| android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION,
hidlPhrase.recognitionModes);
- return handle;
+ return new Pair<>(handle,
+ new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
}
- private int loadPhraseModel_2_1(ISoundTriggerModule module, int hwHandle)
- throws RemoteException {
+ private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel_2_1(ISoundTriggerModule module,
+ int hwHandle) throws RemoteException {
android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
(android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
@@ -500,6 +530,11 @@
ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel>
modelCaptor = ArgumentCaptor.forClass(
android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel.class);
+ ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor =
+ ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class);
+ ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
doAnswer(invocation -> {
android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback =
invocation.getArgument(
@@ -521,7 +556,8 @@
modelEvent.header.model = hwHandle;
callback.soundModelCallback_2_1(modelEvent, callbackCookie);
return null;
- }).when(driver).loadPhraseSoundModel_2_1(modelCaptor.capture(), any(), anyInt(), any());
+ }).when(driver).loadPhraseSoundModel_2_1(modelCaptor.capture(), callbackCaptor.capture(),
+ cookieCaptor.capture(), any());
when(mAudioSessionProvider.acquireSession()).thenReturn(
new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
@@ -554,10 +590,12 @@
| android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION,
hidlPhrase.recognitionModes);
- return handle;
+ return new Pair<>(handle,
+ new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
}
- private int loadPhraseModel(ISoundTriggerModule module, int hwHandle) throws RemoteException {
+ private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel(
+ ISoundTriggerModule module, int hwHandle) throws RemoteException {
if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
return loadPhraseModel_2_1(module, hwHandle);
} else {
@@ -572,18 +610,14 @@
verify(mAudioSessionProvider).releaseSession(101);
}
- private SoundTriggerHwCallback startRecognition_2_0(ISoundTriggerModule module, int handle,
+ private void startRecognition_2_0(ISoundTriggerModule module, int handle,
int hwHandle) throws RemoteException {
ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig>
configCaptor = ArgumentCaptor.forClass(
android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig.class);
- ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor =
- ArgumentCaptor.forClass(
- android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class);
- ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
- when(mHalDriver.startRecognition(eq(hwHandle), configCaptor.capture(),
- callbackCaptor.capture(), cookieCaptor.capture())).thenReturn(0);
+ when(mHalDriver.startRecognition(eq(hwHandle), configCaptor.capture(), any(), anyInt()))
+ .thenReturn(0);
RecognitionConfig config = createRecognitionConfig();
@@ -606,11 +640,9 @@
assertEquals(234, halLevel.userId);
assertEquals(34, halLevel.levelPercent);
assertArrayEquals(new Byte[]{5, 4, 3, 2, 1}, halConfig.data.toArray());
-
- return new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue());
}
- private SoundTriggerHwCallback startRecognition_2_1(ISoundTriggerModule module, int handle,
+ private void startRecognition_2_1(ISoundTriggerModule module, int handle,
int hwHandle) throws RemoteException {
android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
(android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
@@ -618,13 +650,9 @@
ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig>
configCaptor = ArgumentCaptor.forClass(
android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig.class);
- ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor =
- ArgumentCaptor.forClass(
- android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class);
- ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
- when(driver.startRecognition_2_1(eq(hwHandle), configCaptor.capture(),
- callbackCaptor.capture(), cookieCaptor.capture())).thenReturn(0);
+ when(driver.startRecognition_2_1(eq(hwHandle), configCaptor.capture(), any(), anyInt()))
+ .thenReturn(0);
RecognitionConfig config = createRecognitionConfig();
@@ -648,16 +676,56 @@
assertEquals(34, halLevel.levelPercent);
assertArrayEquals(new byte[]{5, 4, 3, 2, 1},
HidlMemoryUtil.hidlMemoryToByteArray(halConfig.data));
-
- return new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue());
}
- private SoundTriggerHwCallback startRecognition(ISoundTriggerModule module, int handle,
+ private void startRecognition_2_3(ISoundTriggerModule module, int handle,
int hwHandle) throws RemoteException {
- if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
- return startRecognition_2_1(module, handle, hwHandle);
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+ ArgumentCaptor<android.hardware.soundtrigger.V2_3.RecognitionConfig>
+ configCaptor = ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_3.RecognitionConfig.class);
+
+ when(driver.startRecognition_2_3(eq(hwHandle), configCaptor.capture())).thenReturn(0);
+
+ RecognitionConfig config = createRecognitionConfig();
+
+ module.startRecognition(handle, config);
+ verify(driver).startRecognition_2_3(eq(hwHandle), any());
+
+ android.hardware.soundtrigger.V2_3.RecognitionConfig halConfigExtended =
+ configCaptor.getValue();
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig halConfig_2_1 =
+ halConfigExtended.base;
+
+ assertTrue(halConfig_2_1.header.captureRequested);
+ assertEquals(102, halConfig_2_1.header.captureHandle);
+ assertEquals(103, halConfig_2_1.header.captureDevice);
+ assertEquals(1, halConfig_2_1.header.phrases.size());
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halPhraseExtra =
+ halConfig_2_1.header.phrases.get(0);
+ assertEquals(123, halPhraseExtra.id);
+ assertEquals(4, halPhraseExtra.confidenceLevel);
+ assertEquals(5, halPhraseExtra.recognitionModes);
+ assertEquals(1, halPhraseExtra.levels.size());
+ android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel = halPhraseExtra.levels.get(0);
+ assertEquals(234, halLevel.userId);
+ assertEquals(34, halLevel.levelPercent);
+ assertArrayEquals(new byte[]{5, 4, 3, 2, 1},
+ HidlMemoryUtil.hidlMemoryToByteArray(halConfig_2_1.data));
+ assertEquals(AudioCapabilities.ECHO_CANCELLATION
+ | AudioCapabilities.NOISE_SUPPRESSION, halConfigExtended.audioCapabilities);
+ }
+
+ private void startRecognition(ISoundTriggerModule module, int handle,
+ int hwHandle) throws RemoteException {
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+ startRecognition_2_3(module, handle, hwHandle);
+ } else if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+ startRecognition_2_1(module, handle, hwHandle);
} else {
- return startRecognition_2_0(module, handle, hwHandle);
+ startRecognition_2_0(module, handle, hwHandle);
}
}
@@ -672,6 +740,8 @@
config.phraseRecognitionExtras[0].levels[0].userId = 234;
config.phraseRecognitionExtras[0].levels[0].levelPercent = 34;
config.data = new byte[]{5, 4, 3, 2, 1};
+ config.audioCapabilities = AudioCapabilities.ECHO_CANCELLATION
+ | AudioCapabilities.NOISE_SUPPRESSION;
return config;
}
@@ -687,6 +757,9 @@
if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
verify((android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver,
never()).startRecognition_2_1(anyInt(), any(), any(), anyInt());
+ } else if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+ verify((android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver,
+ never()).startRecognition_2_3(anyInt(), any());
}
}
@@ -798,7 +871,7 @@
ISoundTriggerModule module = mService.attach(0, callback);
final int hwHandle = 7;
- int handle = loadGenericModel(module, hwHandle);
+ int handle = loadGenericModel(module, hwHandle).first;
unloadModel(module, handle, hwHandle);
module.detach();
}
@@ -810,7 +883,7 @@
ISoundTriggerModule module = mService.attach(0, callback);
final int hwHandle = 73;
- int handle = loadPhraseModel(module, hwHandle);
+ int handle = loadPhraseModel(module, hwHandle).first;
unloadModel(module, handle, hwHandle);
module.detach();
}
@@ -823,7 +896,7 @@
// Load the model.
final int hwHandle = 7;
- int handle = loadGenericModel(module, hwHandle);
+ int handle = loadGenericModel(module, hwHandle).first;
// Initiate a recognition.
startRecognition(module, handle, hwHandle);
@@ -844,7 +917,7 @@
// Load the model.
final int hwHandle = 67;
- int handle = loadPhraseModel(module, hwHandle);
+ int handle = loadPhraseModel(module, hwHandle).first;
// Initiate a recognition.
startRecognition(module, handle, hwHandle);
@@ -865,10 +938,12 @@
// Load the model.
final int hwHandle = 7;
- int handle = loadGenericModel(module, hwHandle);
+ Pair<Integer, SoundTriggerHwCallback> modelHandles = loadGenericModel(module, hwHandle);
+ int handle = modelHandles.first;
+ SoundTriggerHwCallback hwCallback = modelHandles.second;
// Initiate a recognition.
- SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+ startRecognition(module, handle, hwHandle);
// Signal a capture from the driver.
hwCallback.sendRecognitionEvent(hwHandle,
@@ -894,10 +969,12 @@
// Load the model.
final int hwHandle = 7;
- int handle = loadPhraseModel(module, hwHandle);
+ Pair<Integer, SoundTriggerHwCallback> modelHandles = loadPhraseModel(module, hwHandle);
+ int handle = modelHandles.first;
+ SoundTriggerHwCallback hwCallback = modelHandles.second;
// Initiate a recognition.
- SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+ startRecognition(module, handle, hwHandle);
// Signal a capture from the driver.
hwCallback.sendPhraseRecognitionEvent(hwHandle,
@@ -930,10 +1007,12 @@
// Load the model.
final int hwHandle = 17;
- int handle = loadGenericModel(module, hwHandle);
+ Pair<Integer, SoundTriggerHwCallback> modelHandles = loadGenericModel(module, hwHandle);
+ int handle = modelHandles.first;
+ SoundTriggerHwCallback hwCallback = modelHandles.second;
// Initiate a recognition.
- SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+ startRecognition(module, handle, hwHandle);
// Force a trigger.
module.forceRecognitionEvent(handle);
@@ -973,10 +1052,12 @@
// Load the model.
final int hwHandle = 17;
- int handle = loadPhraseModel(module, hwHandle);
+ Pair<Integer, SoundTriggerHwCallback> modelHandles = loadPhraseModel(module, hwHandle);
+ int handle = modelHandles.first;
+ SoundTriggerHwCallback hwCallback = modelHandles.second;
// Initiate a recognition.
- SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+ startRecognition(module, handle, hwHandle);
// Force a trigger.
module.forceRecognitionEvent(handle);
@@ -1013,7 +1094,7 @@
// Load the model.
final int hwHandle = 11;
- int handle = loadGenericModel(module, hwHandle);
+ int handle = loadGenericModel(module, hwHandle).first;
// Initiate a recognition.
startRecognition(module, handle, hwHandle);
@@ -1061,7 +1142,7 @@
// Load the model.
final int hwHandle = 11;
- int handle = loadPhraseModel(module, hwHandle);
+ int handle = loadPhraseModel(module, hwHandle).first;
// Initiate a recognition.
startRecognition(module, handle, hwHandle);
@@ -1109,7 +1190,7 @@
// Load the model.
final int hwHandle = 13;
- int handle = loadGenericModel(module, hwHandle);
+ int handle = loadGenericModel(module, hwHandle).first;
// Initiate a recognition.
startRecognition(module, handle, hwHandle);
@@ -1145,7 +1226,7 @@
// Load the model.
final int hwHandle = 13;
- int handle = loadPhraseModel(module, hwHandle);
+ int handle = loadPhraseModel(module, hwHandle).first;
// Initiate a recognition.
startRecognition(module, handle, hwHandle);
@@ -1182,7 +1263,7 @@
ISoundTriggerCallback callback = createCallbackMock();
ISoundTriggerModule module = mService.attach(0, callback);
final int hwHandle = 12;
- int modelHandle = loadGenericModel(module, hwHandle);
+ int modelHandle = loadGenericModel(module, hwHandle).first;
doAnswer((Answer<Void>) invocation -> {
android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback
@@ -1218,7 +1299,7 @@
ISoundTriggerCallback callback = createCallbackMock();
ISoundTriggerModule module = mService.attach(0, callback);
final int hwHandle = 13;
- int modelHandle = loadGenericModel(module, hwHandle);
+ int modelHandle = loadGenericModel(module, hwHandle).first;
ModelParameterRange range = module.queryModelParameterSupport(modelHandle,
ModelParameter.THRESHOLD_FACTOR);
@@ -1239,7 +1320,7 @@
ISoundTriggerCallback callback = createCallbackMock();
ISoundTriggerModule module = mService.attach(0, callback);
final int hwHandle = 13;
- int modelHandle = loadGenericModel(module, hwHandle);
+ int modelHandle = loadGenericModel(module, hwHandle).first;
doAnswer(invocation -> {
android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback
@@ -1272,7 +1353,7 @@
ISoundTriggerCallback callback = createCallbackMock();
ISoundTriggerModule module = mService.attach(0, callback);
final int hwHandle = 14;
- int modelHandle = loadGenericModel(module, hwHandle);
+ int modelHandle = loadGenericModel(module, hwHandle).first;
doAnswer(invocation -> {
android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getParameterCallback
@@ -1304,7 +1385,7 @@
ISoundTriggerCallback callback = createCallbackMock();
ISoundTriggerModule module = mService.attach(0, callback);
final int hwHandle = 17;
- int modelHandle = loadGenericModel(module, hwHandle);
+ int modelHandle = loadGenericModel(module, hwHandle).first;
when(driver.setParameter(hwHandle,
android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR,
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index aaf9799..8a3183f 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -18,7 +18,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -26,7 +25,6 @@
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.PhoneTimeSuggestion;
-import android.content.Intent;
import android.icu.util.Calendar;
import android.icu.util.GregorianCalendar;
import android.icu.util.TimeZone;
@@ -78,8 +76,7 @@
long expectedSystemClockMillis =
mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime());
- mScript.verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+ mScript.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
.assertLatestPhoneSuggestion(phoneId, timeSuggestion);
}
@@ -118,8 +115,7 @@
mScript.calculateTimeInMillisForNow(timeSuggestion1.getUtcTime());
mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1)
.assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
}
@@ -146,8 +142,7 @@
mScript.calculateTimeInMillisForNow(timeSuggestion3.getUtcTime());
mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis3, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis3)
.assertLatestPhoneSuggestion(phoneId, timeSuggestion3);
}
}
@@ -175,8 +170,7 @@
mScript.calculateTimeInMillisForNow(phone2TimeSuggestion.getUtcTime());
mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
.assertLatestPhoneSuggestion(phone1Id, null)
.assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion);
}
@@ -193,8 +187,7 @@
mScript.calculateTimeInMillisForNow(phone1TimeSuggestion.getUtcTime());
mScript.simulatePhoneTimeSuggestion(phone1TimeSuggestion)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
.assertLatestPhoneSuggestion(phone1Id, phone1TimeSuggestion);
}
@@ -227,8 +220,7 @@
mScript.calculateTimeInMillisForNow(phone2TimeSuggestion.getUtcTime());
mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
.assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion);
}
}
@@ -265,8 +257,7 @@
mScript.simulateTimePassing();
long expectedSystemClockMillis1 = mScript.calculateTimeInMillisForNow(utcTime1);
mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1)
.assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
// The UTC time increment should be larger than the system clock update threshold so we
@@ -304,8 +295,7 @@
PhoneTimeSuggestion timeSuggestion4 =
createPhoneTimeSuggestion(phoneId, utcTime4);
mScript.simulatePhoneTimeSuggestion(timeSuggestion4)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis4, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis4)
.assertLatestPhoneSuggestion(phoneId, timeSuggestion4);
}
@@ -339,8 +329,7 @@
// Turn on auto time detection.
mScript.simulateAutoTimeDetectionToggle()
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1)
.assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
// Turn off auto time detection.
@@ -367,8 +356,7 @@
// Turn on auto time detection.
mScript.simulateAutoTimeDetectionToggle()
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis2, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis2)
.assertLatestPhoneSuggestion(phoneId, timeSuggestion2);
}
@@ -388,7 +376,7 @@
mScript.calculateTimeInMillisForNow(phoneSuggestion.getUtcTime());
mScript.simulatePhoneTimeSuggestion(phoneSuggestion)
.verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis, true /* expectedNetworkBroadcast */)
+ expectedSystemClockMillis /* expectedNetworkBroadcast */)
.assertLatestPhoneSuggestion(phoneId, phoneSuggestion);
// Look inside and check what the strategy considers the current best phone suggestion.
@@ -416,8 +404,7 @@
long expectedSystemClockMillis =
mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime());
mScript.simulateManualTimeSuggestion(timeSuggestion)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis, false /* expectNetworkBroadcast */);
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
}
@Test
@@ -439,8 +426,7 @@
long expectedAutoClockMillis =
mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime());
mScript.simulatePhoneTimeSuggestion(phoneTimeSuggestion)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedAutoClockMillis, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis)
.assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
// Simulate the passage of time.
@@ -463,8 +449,7 @@
long expectedManualClockMillis =
mScript.calculateTimeInMillisForNow(manualTimeSuggestion.getUtcTime());
mScript.simulateManualTimeSuggestion(manualTimeSuggestion)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedManualClockMillis, false /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedManualClockMillis)
.assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
// Simulate the passage of time.
@@ -475,8 +460,7 @@
expectedAutoClockMillis =
mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime());
- mScript.verifySystemClockWasSetAndResetCallTracking(
- expectedAutoClockMillis, true /* expectNetworkBroadcast */)
+ mScript.verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis)
.assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
// Switch back to manual - nothing should happen to the clock.
@@ -514,8 +498,7 @@
long expectedSystemClockMillis =
mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime());
mScript.simulateNetworkTimeSuggestion(timeSuggestion)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis, false /* expectNetworkBroadcast */);
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
}
@Test
@@ -550,8 +533,7 @@
mScript.simulateTimePassing(smallTimeIncrementMillis)
.simulateNetworkTimeSuggestion(networkTimeSuggestion1)
.verifySystemClockWasSetAndResetCallTracking(
- mScript.calculateTimeInMillisForNow(networkTimeSuggestion1.getUtcTime()),
- false /* expectNetworkBroadcast */);
+ mScript.calculateTimeInMillisForNow(networkTimeSuggestion1.getUtcTime()));
// Check internal state.
mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, null)
@@ -570,8 +552,7 @@
mScript.simulateTimePassing(smallTimeIncrementMillis)
.simulatePhoneTimeSuggestion(phoneTimeSuggestion)
.verifySystemClockWasSetAndResetCallTracking(
- mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime()),
- true /* expectNetworkBroadcast */);
+ mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime()));
// Check internal state.
mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion)
@@ -622,8 +603,7 @@
// Verify the latest network time now wins.
mScript.verifySystemClockWasSetAndResetCallTracking(
- mScript.calculateTimeInMillisForNow(networkTimeSuggestion2.getUtcTime()),
- false /* expectNetworkTimeBroadcast */);
+ mScript.calculateTimeInMillisForNow(networkTimeSuggestion2.getUtcTime()));
// Check internal state.
mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion)
@@ -645,7 +625,6 @@
// Tracking operations.
private boolean mSystemClockWasSet;
- private Intent mBroadcastSent;
@Override
public int systemClockUpdateThresholdMillis() {
@@ -689,12 +668,6 @@
mWakeLockAcquired = false;
}
- @Override
- public void sendStickyBroadcast(Intent intent) {
- assertNotNull(intent);
- mBroadcastSent = intent;
- }
-
// Methods below are for managing the fake's behavior.
void pokeSystemClockUpdateThreshold(int thresholdMillis) {
@@ -739,17 +712,8 @@
assertEquals(expectedSystemClockMillis, mSystemClockMillis);
}
- void verifyIntentWasBroadcast() {
- assertTrue(mBroadcastSent != null);
- }
-
- void verifyIntentWasNotBroadcast() {
- assertNull(mBroadcastSent);
- }
-
void resetCallTracking() {
mSystemClockWasSet = false;
- mBroadcastSent = null;
}
private void assertWakeLockAcquired() {
@@ -832,17 +796,12 @@
Script verifySystemClockWasNotSetAndResetCallTracking() {
mFakeCallback.verifySystemClockNotSet();
- mFakeCallback.verifyIntentWasNotBroadcast();
mFakeCallback.resetCallTracking();
return this;
}
- Script verifySystemClockWasSetAndResetCallTracking(
- long expectedSystemClockMillis, boolean expectNetworkBroadcast) {
+ Script verifySystemClockWasSetAndResetCallTracking(long expectedSystemClockMillis) {
mFakeCallback.verifySystemClockWasSet(expectedSystemClockMillis);
- if (expectNetworkBroadcast) {
- mFakeCallback.verifyIntentWasBroadcast();
- }
mFakeCallback.resetCallTracking();
return this;
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 06c8074..6b95f0f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -159,9 +159,13 @@
}
}
+ private boolean isSupported(UserInfo user) {
+ return user.isFull();
+ }
+
@Override
- public boolean isSupported(UserInfo userInfo) {
- return userInfo.isFull();
+ public boolean isSupportedUser(TargetUser user) {
+ return isSupported(user.getUserInfo());
}
@Override
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 6a62237..8668536 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -848,9 +848,12 @@
"carrier_force_disable_etws_cmas_test_bool";
/**
- * The default flag specifying whether "Turn on Notifications" option will be always shown in
- * Settings->More->Emergency broadcasts menu regardless developer options is turned on or not.
+ * The default flag specifying whether "Allow alerts" option will be always shown in
+ * emergency alerts settings regardless developer options is turned on or not.
+ *
+ * @deprecated The allow alerts option is always shown now. No longer need a config for that.
*/
+ @Deprecated
public static final String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL =
"always_show_emergency_alert_onoff_bool";
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index eb328a7..4f104f4 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -2448,14 +2448,18 @@
* @param sentIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is successfully sent, or failed
* @throws IllegalArgumentException if contentUri is empty
+ * @deprecated use {@link MmsManager#sendMultimediaMessage} instead.
*/
public void sendMultimediaMessage(Context context, Uri contentUri, String locationUrl,
Bundle configOverrides, PendingIntent sentIntent) {
if (contentUri == null) {
throw new IllegalArgumentException("Uri contentUri null");
}
- MmsManager.getInstance().sendMultimediaMessage(getSubscriptionId(), contentUri,
- locationUrl, configOverrides, sentIntent);
+ MmsManager m = (MmsManager) context.getSystemService(Context.MMS_SERVICE);
+ if (m != null) {
+ m.sendMultimediaMessage(getSubscriptionId(), contentUri, locationUrl, configOverrides,
+ sentIntent);
+ }
}
/**
@@ -2479,6 +2483,7 @@
* @param downloadedIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is downloaded, or the download is failed
* @throws IllegalArgumentException if locationUrl or contentUri is empty
+ * @deprecated use {@link MmsManager#downloadMultimediaMessage} instead.
*/
public void downloadMultimediaMessage(Context context, String locationUrl, Uri contentUri,
Bundle configOverrides, PendingIntent downloadedIntent) {
@@ -2488,8 +2493,11 @@
if (contentUri == null) {
throw new IllegalArgumentException("Uri contentUri null");
}
- MmsManager.getInstance().downloadMultimediaMessage(getSubscriptionId(), locationUrl,
- contentUri, configOverrides, downloadedIntent);
+ MmsManager m = (MmsManager) context.getSystemService(Context.MMS_SERVICE);
+ if (m != null) {
+ m.downloadMultimediaMessage(getSubscriptionId(), locationUrl, contentUri,
+ configOverrides, downloadedIntent);
+ }
}
// MMS send/download failure result codes
@@ -2531,9 +2539,9 @@
* </p>
*
* @return the bundle key/values pairs that contains MMS configuration values
+ * or an empty Bundle if they cannot be found.
*/
- @Nullable
- public Bundle getCarrierConfigValues() {
+ @NonNull public Bundle getCarrierConfigValues() {
try {
ISms iSms = getISmsService();
if (iSms != null) {
@@ -2542,7 +2550,7 @@
} catch (RemoteException ex) {
// ignore it
}
- return null;
+ return new Bundle();
}
/**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f321427..157d92d 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1444,24 +1444,6 @@
"android.telephony.extra.SIM_COMBINATION_NAMES";
/**
- * Broadcast Action: The time was set by the carrier (typically by the NITZ string).
- * This is a sticky broadcast.
- * The intent will have the following extra values:</p>
- * <ul>
- * <li><em>time</em> - The time as a long in UTC milliseconds.</li>
- * </ul>
- *
- * <p class="note">
- * Requires the READ_PHONE_STATE permission.
- *
- * <p class="note">This is a protected intent that can only be sent by the system.
- *
- * @hide
- */
- @SystemApi
- public static final String ACTION_NETWORK_SET_TIME = "android.telephony.action.NETWORK_SET_TIME";
-
- /**
* <p>Broadcast Action: The emergency callback mode is changed.
* <ul>
* <li><em>phoneinECMState</em> - A boolean value,true=phone in ECM, false=ECM off</li>
diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
index 1e8d83c..0ab5c97 100644
--- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -35,10 +35,10 @@
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
-import android.net.NetworkFactory;
import android.net.NetworkInfo;
-import android.net.NetworkMisc;
+import android.net.NetworkProvider;
import android.net.NetworkSpecifier;
import android.net.SocketKeepalive;
import android.net.UidRange;
@@ -114,7 +114,7 @@
public InstrumentedNetworkAgent(NetworkAgentWrapper wrapper, LinkProperties lp) {
super(wrapper.mHandlerThread.getLooper(), wrapper.mContext, wrapper.mLogTag,
wrapper.mNetworkInfo, wrapper.mNetworkCapabilities, lp, wrapper.mScore,
- new NetworkMisc(), NetworkFactory.SerialNumber.NONE);
+ new NetworkAgentConfig(), NetworkProvider.ID_NONE);
mWrapper = wrapper;
}
diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
index 535298f..9e915ae 100644
--- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -35,10 +35,10 @@
import android.net.IDnsResolver;
import android.net.INetd;
import android.net.Network;
+import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
-import android.net.NetworkFactory;
import android.net.NetworkInfo;
-import android.net.NetworkMisc;
+import android.net.NetworkProvider;
import android.net.NetworkScore;
import android.os.INetworkManagementService;
import android.text.format.DateUtils;
@@ -75,7 +75,7 @@
@Mock INetd mNetd;
@Mock INetworkManagementService mNMS;
@Mock Context mCtx;
- @Mock NetworkMisc mMisc;
+ @Mock NetworkAgentConfig mAgentConfig;
@Mock NetworkNotificationManager mNotifier;
@Mock Resources mResources;
@@ -358,8 +358,8 @@
NetworkScore ns = new NetworkScore();
ns.putIntExtension(NetworkScore.LEGACY_SCORE, 50);
NetworkAgentInfo nai = new NetworkAgentInfo(null, null, new Network(netId), info, null,
- caps, ns, mCtx, null, mMisc, mConnService, mNetd, mDnsResolver, mNMS,
- NetworkFactory.SerialNumber.NONE);
+ caps, ns, mCtx, null, mAgentConfig, mConnService, mNetd, mDnsResolver, mNMS,
+ NetworkProvider.ID_NONE);
nai.everValidated = true;
return nai;
}
diff --git a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
index b709af1..cf70f5d 100644
--- a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
+++ b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
@@ -33,8 +33,8 @@
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
+import android.net.NetworkAgentConfig;
import android.net.NetworkInfo;
-import android.net.NetworkMisc;
import android.os.Handler;
import android.os.INetworkManagementService;
import android.os.test.TestLooper;
@@ -63,7 +63,7 @@
static final int NETID = 42;
@Mock ConnectivityService mConnectivity;
- @Mock NetworkMisc mMisc;
+ @Mock NetworkAgentConfig mAgentConfig;
@Mock IDnsResolver mDnsResolver;
@Mock INetd mNetd;
@Mock INetworkManagementService mNms;
@@ -93,7 +93,7 @@
mNai.networkInfo = new NetworkInfo(null);
mNai.networkInfo.setType(ConnectivityManager.TYPE_WIFI);
when(mNai.connService()).thenReturn(mConnectivity);
- when(mNai.netMisc()).thenReturn(mMisc);
+ when(mNai.netAgentConfig()).thenReturn(mAgentConfig);
when(mNai.handler()).thenReturn(mHandler);
when(mNms.getInterfaceConfig(eq(STACKED_IFACE))).thenReturn(mConfig);
@@ -104,7 +104,7 @@
String msg = String.format("requiresClat expected %b for type=%d state=%s skip=%b "
+ "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(),
nai.networkInfo.getDetailedState(),
- mMisc.skip464xlat, nai.linkProperties.getNat64Prefix(),
+ mAgentConfig.skip464xlat, nai.linkProperties.getNat64Prefix(),
nai.linkProperties.getLinkAddresses());
assertEquals(msg, expected, Nat464Xlat.requiresClat(nai));
}
@@ -113,7 +113,7 @@
String msg = String.format("shouldStartClat expected %b for type=%d state=%s skip=%b "
+ "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(),
nai.networkInfo.getDetailedState(),
- mMisc.skip464xlat, nai.linkProperties.getNat64Prefix(),
+ mAgentConfig.skip464xlat, nai.linkProperties.getNat64Prefix(),
nai.linkProperties.getLinkAddresses());
assertEquals(msg, expected, Nat464Xlat.shouldStartClat(nai));
}
@@ -151,11 +151,11 @@
assertRequiresClat(true, mNai);
assertShouldStartClat(true, mNai);
- mMisc.skip464xlat = true;
+ mAgentConfig.skip464xlat = true;
assertRequiresClat(false, mNai);
assertShouldStartClat(false, mNai);
- mMisc.skip464xlat = false;
+ mAgentConfig.skip464xlat = false;
assertRequiresClat(true, mNai);
assertShouldStartClat(true, mNai);
diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index 6de068e..a9e0b9a 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -80,6 +80,7 @@
import android.net.NetworkStats;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
+import android.net.netstats.provider.INetworkStatsProviderCallback;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
@@ -102,6 +103,7 @@
import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
import com.android.testutils.HandlerUtilsKt;
+import com.android.testutils.TestableNetworkStatsProvider;
import libcore.io.IoUtils;
@@ -1001,6 +1003,88 @@
mService.unregisterUsageRequest(unknownRequest);
}
+ @Test
+ public void testStatsProviderUpdateStats() throws Exception {
+ // Pretend that network comes online.
+ expectDefaultSettings();
+ final NetworkState[] states = new NetworkState[]{buildWifiState(true /* isMetered */)};
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+
+ // Register custom provider and retrieve callback.
+ final TestableNetworkStatsProvider provider = new TestableNetworkStatsProvider();
+ final INetworkStatsProviderCallback cb =
+ mService.registerNetworkStatsProvider("TEST", provider);
+ assertNotNull(cb);
+
+ mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
+
+ // Verifies that one requestStatsUpdate will be called during iface update.
+ provider.expectStatsUpdate(0 /* unused */);
+
+ // Create some initial traffic and report to the service.
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ final NetworkStats expectedStats = new NetworkStats(0L, 1)
+ .addValues(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT,
+ TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES,
+ 128L, 2L, 128L, 2L, 1L))
+ .addValues(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT,
+ 0xF00D, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES,
+ 64L, 1L, 64L, 1L, 1L));
+ cb.onStatsUpdated(0 /* unused */, expectedStats, expectedStats);
+
+ // Make another empty mutable stats object. This is necessary since the new NetworkStats
+ // object will be used to compare with the old one in NetworkStatsRecoder, two of them
+ // cannot be the same object.
+ expectNetworkStatsUidDetail(buildEmptyStats());
+
+ forcePollAndWaitForIdle();
+
+ // Verifies that one requestStatsUpdate and setAlert will be called during polling.
+ provider.expectStatsUpdate(0 /* unused */);
+ provider.expectSetAlert(MB_IN_BYTES);
+
+ // Verifies that service recorded history, does not verify uid tag part.
+ assertUidTotal(sTemplateWifi, UID_RED, 128L, 2L, 128L, 2L, 1);
+
+ // Verifies that onStatsUpdated updates the stats accordingly.
+ final NetworkStats stats = mSession.getSummaryForAllUid(
+ sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true);
+ assertEquals(2, stats.size());
+ assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 1L);
+ assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 1L);
+
+ // Verifies that unregister the callback will remove the provider from service.
+ cb.unregister();
+ forcePollAndWaitForIdle();
+ provider.assertNoCallback();
+ }
+
+ @Test
+ public void testStatsProviderSetAlert() throws Exception {
+ // Pretend that network comes online.
+ expectDefaultSettings();
+ NetworkState[] states = new NetworkState[]{buildWifiState(true /* isMetered */)};
+ mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
+
+ // Register custom provider and retrieve callback.
+ final TestableNetworkStatsProvider provider = new TestableNetworkStatsProvider();
+ final INetworkStatsProviderCallback cb =
+ mService.registerNetworkStatsProvider("TEST", provider);
+ assertNotNull(cb);
+
+ // Simulates alert quota of the provider has been reached.
+ cb.onAlertReached();
+ HandlerUtilsKt.waitForIdle(mHandler, WAIT_TIMEOUT);
+
+ // Verifies that polling is triggered by alert reached.
+ provider.expectStatsUpdate(0 /* unused */);
+ // Verifies that global alert will be re-armed.
+ provider.expectSetAlert(MB_IN_BYTES);
+ }
+
private static File getBaseDir(File statsDir) {
File baseDir = new File(statsDir, "netstats");
baseDir.mkdirs();
@@ -1102,6 +1186,7 @@
private void expectSettings(long persistBytes, long bucketDuration, long deleteAge)
throws Exception {
when(mSettings.getPollInterval()).thenReturn(HOUR_IN_MILLIS);
+ when(mSettings.getPollDelay()).thenReturn(0L);
when(mSettings.getSampleEnabled()).thenReturn(true);
final Config config = new Config(bucketDuration, deleteAge, deleteAge);
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 4555caa..5b6935b 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -895,7 +895,7 @@
// android:versionCode from the framework AndroidManifest.xml.
ExtractCompileSdkVersions(asset_source->GetAssetManager());
}
- } else if (asset_source->IsPackageDynamic(entry.first)) {
+ } else if (asset_source->IsPackageDynamic(entry.first, entry.second)) {
final_table_.included_packages_[entry.first] = entry.second;
}
}
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 83e20b5..897fa80 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -245,7 +245,8 @@
return package_map;
}
-bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId) const {
+bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId,
+ const std::string& package_name) const {
if (packageId == 0) {
return true;
}
@@ -253,7 +254,7 @@
for (const std::unique_ptr<const ApkAssets>& assets : apk_assets_) {
for (const std::unique_ptr<const android::LoadedPackage>& loaded_package
: assets->GetLoadedArsc()->GetPackages()) {
- if (packageId == loaded_package->GetPackageId() && loaded_package->IsDynamic()) {
+ if (package_name == loaded_package->GetPackageName() && loaded_package->IsDynamic()) {
return true;
}
}
@@ -328,12 +329,12 @@
bool found = false;
ResourceId res_id = 0;
uint32_t type_spec_flags;
+ ResourceName real_name;
// There can be mangled resources embedded within other packages. Here we will
// look into each package and look-up the mangled name until we find the resource.
asset_manager_.ForEachPackage([&](const std::string& package_name, uint8_t id) -> bool {
- ResourceName real_name(name.package, name.type, name.entry);
-
+ real_name = ResourceName(name.package, name.type, name.entry);
if (package_name != name.package) {
real_name.entry = mangled_entry;
real_name.package = package_name;
@@ -353,12 +354,12 @@
}
std::unique_ptr<SymbolTable::Symbol> s;
- if (name.type == ResourceType::kAttr) {
+ if (real_name.type == ResourceType::kAttr) {
s = LookupAttributeInTable(asset_manager_, res_id);
} else {
s = util::make_unique<SymbolTable::Symbol>();
s->id = res_id;
- s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id());
+ s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id(), real_name.package);
}
if (s) {
@@ -406,7 +407,7 @@
} else {
s = util::make_unique<SymbolTable::Symbol>();
s->id = id;
- s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id());
+ s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id(), name.package);
}
if (s) {
diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h
index 6997cd6..06eaf63 100644
--- a/tools/aapt2/process/SymbolTable.h
+++ b/tools/aapt2/process/SymbolTable.h
@@ -194,7 +194,7 @@
bool AddAssetPath(const android::StringPiece& path);
std::map<size_t, std::string> GetAssignedPackageIds() const;
- bool IsPackageDynamic(uint32_t packageId) const;
+ bool IsPackageDynamic(uint32_t packageId, const std::string& package_name) const;
std::unique_ptr<SymbolTable::Symbol> FindByName(
const ResourceName& name) override;
diff --git a/wifi/java/android/net/wifi/ISoftApCallback.aidl b/wifi/java/android/net/wifi/ISoftApCallback.aidl
index 482b491..f81bcb9 100644
--- a/wifi/java/android/net/wifi/ISoftApCallback.aidl
+++ b/wifi/java/android/net/wifi/ISoftApCallback.aidl
@@ -55,9 +55,17 @@
/**
- * Service to manager callback providing information of softap.
+ * Service to manager callback providing capability of softap.
*
* @param capability is the softap capability. {@link SoftApCapability}
*/
void onCapabilityChanged(in SoftApCapability capability);
+
+ /**
+ * Service to manager callback providing blocked client of softap with specific reason code.
+ *
+ * @param client the currently blocked client.
+ * @param blockedReason one of blocked reason from {@link WifiManager.SapClientBlockedReason}
+ */
+ void onBlockedClientConnecting(in WifiClient client, int blockedReason);
}
diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java
index 2b7f8af..a77d30a 100644
--- a/wifi/java/android/net/wifi/SoftApConfiguration.java
+++ b/wifi/java/android/net/wifi/SoftApConfiguration.java
@@ -32,6 +32,9 @@
import java.lang.annotation.RetentionPolicy;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -182,6 +185,22 @@
private final @SecurityType int mSecurityType;
/**
+ * The flag to indicate client need to authorize by user
+ * when client is connecting to AP.
+ */
+ private final boolean mClientControlByUser;
+
+ /**
+ * The list of blocked client that can't associate to the AP.
+ */
+ private final List<MacAddress> mBlockedClientList;
+
+ /**
+ * The list of allowed client that can associate to the AP.
+ */
+ private final List<MacAddress> mAllowedClientList;
+
+ /**
* Delay in milliseconds before shutting down soft AP when
* there are no connected devices.
*/
@@ -219,7 +238,9 @@
/** Private constructor for Builder and Parcelable implementation. */
private SoftApConfiguration(@Nullable String ssid, @Nullable MacAddress bssid,
@Nullable String passphrase, boolean hiddenSsid, @BandType int band, int channel,
- @SecurityType int securityType, int maxNumberOfClients, int shutdownTimeoutMillis) {
+ @SecurityType int securityType, int maxNumberOfClients, int shutdownTimeoutMillis,
+ boolean clientControlByUser, @NonNull List<MacAddress> blockedList,
+ @NonNull List<MacAddress> allowedList) {
mSsid = ssid;
mBssid = bssid;
mPassphrase = passphrase;
@@ -229,6 +250,9 @@
mSecurityType = securityType;
mMaxNumberOfClients = maxNumberOfClients;
mShutdownTimeoutMillis = shutdownTimeoutMillis;
+ mClientControlByUser = clientControlByUser;
+ mBlockedClientList = new ArrayList<>(blockedList);
+ mAllowedClientList = new ArrayList<>(allowedList);
}
@Override
@@ -248,13 +272,17 @@
&& mChannel == other.mChannel
&& mSecurityType == other.mSecurityType
&& mMaxNumberOfClients == other.mMaxNumberOfClients
- && mShutdownTimeoutMillis == other.mShutdownTimeoutMillis;
+ && mShutdownTimeoutMillis == other.mShutdownTimeoutMillis
+ && mClientControlByUser == other.mClientControlByUser
+ && Objects.equals(mBlockedClientList, other.mBlockedClientList)
+ && Objects.equals(mAllowedClientList, other.mAllowedClientList);
}
@Override
public int hashCode() {
return Objects.hash(mSsid, mBssid, mPassphrase, mHiddenSsid,
- mBand, mChannel, mSecurityType, mMaxNumberOfClients, mShutdownTimeoutMillis);
+ mBand, mChannel, mSecurityType, mMaxNumberOfClients, mShutdownTimeoutMillis,
+ mClientControlByUser, mBlockedClientList, mAllowedClientList);
}
@Override
@@ -270,6 +298,9 @@
sbuf.append(" \n SecurityType=").append(getSecurityType());
sbuf.append(" \n MaxClient=").append(mMaxNumberOfClients);
sbuf.append(" \n ShutdownTimeoutMillis=").append(mShutdownTimeoutMillis);
+ sbuf.append(" \n ClientControlByUser=").append(mClientControlByUser);
+ sbuf.append(" \n BlockedClientList=").append(mBlockedClientList);
+ sbuf.append(" \n AllowedClientList=").append(mAllowedClientList);
return sbuf.toString();
}
@@ -284,6 +315,9 @@
dest.writeInt(mSecurityType);
dest.writeInt(mMaxNumberOfClients);
dest.writeInt(mShutdownTimeoutMillis);
+ dest.writeBoolean(mClientControlByUser);
+ dest.writeTypedList(mBlockedClientList);
+ dest.writeTypedList(mAllowedClientList);
}
@Override
@@ -299,7 +333,9 @@
in.readString(),
in.readParcelable(MacAddress.class.getClassLoader()),
in.readString(), in.readBoolean(), in.readInt(), in.readInt(), in.readInt(),
- in.readInt(), in.readInt());
+ in.readInt(), in.readInt(), in.readBoolean(),
+ in.createTypedArrayList(MacAddress.CREATOR),
+ in.createTypedArrayList(MacAddress.CREATOR));
}
@Override
@@ -387,6 +423,34 @@
}
/**
+ * Returns a flag indicating whether clients need to be pre-approved by the user.
+ * (true: authorization required) or not (false: not required).
+ * {@link Builder#enableClientControlByUser(Boolean)}.
+ */
+ public boolean isClientControlByUserEnabled() {
+ return mClientControlByUser;
+ }
+
+ /**
+ * Returns List of clients which aren't allowed to associate to the AP.
+ *
+ * Clients are configured using {@link Builder#setClientList(List, List)}
+ */
+ @NonNull
+ public List<MacAddress> getBlockedClientList() {
+ return mBlockedClientList;
+ }
+
+ /**
+ * List of clients which are allowed to associate to the AP.
+ * Clients are configured using {@link Builder#setClientList(List, List)}
+ */
+ @NonNull
+ public List<MacAddress> getAllowedClientList() {
+ return mAllowedClientList;
+ }
+
+ /**
* Builds a {@link SoftApConfiguration}, which allows an app to configure various aspects of a
* Soft AP.
*
@@ -403,6 +467,9 @@
private int mMaxNumberOfClients;
private int mSecurityType;
private int mShutdownTimeoutMillis;
+ private boolean mClientControlByUser;
+ private List<MacAddress> mBlockedClientList;
+ private List<MacAddress> mAllowedClientList;
/**
* Constructs a Builder with default values (see {@link Builder}).
@@ -417,6 +484,9 @@
mMaxNumberOfClients = 0;
mSecurityType = SECURITY_TYPE_OPEN;
mShutdownTimeoutMillis = 0;
+ mClientControlByUser = false;
+ mBlockedClientList = new ArrayList<>();
+ mAllowedClientList = new ArrayList<>();
}
/**
@@ -434,6 +504,9 @@
mMaxNumberOfClients = other.mMaxNumberOfClients;
mSecurityType = other.mSecurityType;
mShutdownTimeoutMillis = other.mShutdownTimeoutMillis;
+ mClientControlByUser = other.mClientControlByUser;
+ mBlockedClientList = new ArrayList<>(other.mBlockedClientList);
+ mAllowedClientList = new ArrayList<>(other.mAllowedClientList);
}
/**
@@ -445,7 +518,8 @@
public SoftApConfiguration build() {
return new SoftApConfiguration(mSsid, mBssid, mPassphrase,
mHiddenSsid, mBand, mChannel, mSecurityType, mMaxNumberOfClients,
- mShutdownTimeoutMillis);
+ mShutdownTimeoutMillis, mClientControlByUser, mBlockedClientList,
+ mAllowedClientList);
}
/**
@@ -662,5 +736,82 @@
mShutdownTimeoutMillis = timeoutMillis;
return this;
}
+
+ /**
+ * Configure the Soft AP to require manual user control of client association.
+ * If disabled (the default) then any client can associate to this Soft AP using the
+ * correct credentials until the Soft AP capacity is reached (capacity is hardware, carrier,
+ * or user limited - using {@link #setMaxNumberOfClients(int)}).
+ *
+ * If manual user control is enabled then clients will be accepted, rejected, or require
+ * a user approval based on the configuration provided by
+ * {@link #setClientList(List, List)}.
+ *
+ * <p>
+ * This method requires hardware support. Hardware support can be determined using
+ * {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)} and
+ * {@link SoftApCapability#isFeatureSupported(int)}
+ * with {@link SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT}
+ *
+ * <p>
+ * If the method is called on a device without hardware support then starting the soft AP
+ * using {@link WifiManager#startTetheredHotspot(SoftApConfiguration)} will fail with
+ * {@link WifiManager#SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
+ *
+ * <p>
+ * <li>If not set, defaults to false (i.e The authoriztion is not required).</li>
+ *
+ * @param enabled true for enabling the control by user, false otherwise.
+ * @return Builder for chaining.
+ */
+ @NonNull
+ public Builder enableClientControlByUser(boolean enabled) {
+ mClientControlByUser = enabled;
+ return this;
+ }
+
+
+ /**
+ * This method together with {@link enableClientControlByUser(boolean)} control client
+ * connections to the AP. If {@link enableClientControlByUser(false)} is configured than
+ * this API has no effect and clients are allowed to associate to the AP (within limit of
+ * max number of clients).
+ *
+ * If {@link enableClientControlByUser(true)} is configured then this API configures
+ * 2 lists:
+ * <ul>
+ * <li>List of clients which are blocked. These are rejected.</li>
+ * <li>List of clients which are explicitly allowed. These are auto-accepted.</li>
+ * </ul>
+ *
+ * <p>
+ * All other clients which attempt to associate, whose MAC addresses are on neither list,
+ * are:
+ * <ul>
+ * <li>Rejected</li>
+ * <li>A callback {@link WifiManager.SoftApCallback#onBlockedClientConnecting(WifiClient)}
+ * is issued (which allows the user to add them to the allowed client list if desired).<li>
+ * </ul>
+ *
+ * @param blockedClientList list of clients which are not allowed to associate to the AP.
+ * @param allowedClientList list of clients which are allowed to associate to the AP
+ * without user pre-approval.
+ * @return Builder for chaining.
+ */
+ @NonNull
+ public Builder setClientList(@NonNull List<MacAddress> blockedClientList,
+ @NonNull List<MacAddress> allowedClientList) {
+ mBlockedClientList = new ArrayList<>(blockedClientList);
+ mAllowedClientList = new ArrayList<>(allowedClientList);
+ Iterator<MacAddress> iterator = mAllowedClientList.iterator();
+ while (iterator.hasNext()) {
+ MacAddress client = iterator.next();
+ int index = mBlockedClientList.indexOf(client);
+ if (index != -1) {
+ throw new IllegalArgumentException("A MacAddress exist in both list");
+ }
+ }
+ return this;
+ }
}
}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 1baab12..a8a31eb 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -233,16 +233,20 @@
public @interface SuggestionConnectionStatusCode {}
/**
- * Broadcast intent action indicating whether Wi-Fi scanning is allowed currently
- * @hide
+ * Broadcast intent action indicating whether Wi-Fi scanning is currently available.
+ * Available extras:
+ * - {@link #EXTRA_SCAN_AVAILABLE}
*/
- public static final String WIFI_SCAN_AVAILABLE = "wifi_scan_available";
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_WIFI_SCAN_AVAILABLE =
+ "android.net.wifi.action.WIFI_SCAN_AVAILABLE";
/**
- * Extra int indicating scan availability, WIFI_STATE_ENABLED and WIFI_STATE_DISABLED
- * @hide
+ * A boolean extra indicating whether scanning is currently available.
+ * Sent in the broadcast {@link #ACTION_WIFI_SCAN_AVAILABLE}.
+ * Its value is true if scanning is currently available, false otherwise.
*/
- public static final String EXTRA_SCAN_AVAILABLE = "scan_enabled";
+ public static final String EXTRA_SCAN_AVAILABLE = "android.net.wifi.extra.SCAN_AVAILABLE";
/**
* Broadcast intent action indicating that the credential of a Wi-Fi network
@@ -666,7 +670,8 @@
public @interface SapStartFailure {}
/**
- * All other reasons for AP start failure besides {@link #SAP_START_FAILURE_NO_CHANNEL}.
+ * All other reasons for AP start failure besides {@link #SAP_START_FAILURE_NO_CHANNEL} and
+ * {@link #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
*
* @hide
*/
@@ -691,6 +696,37 @@
@SystemApi
public static final int SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION = 2;
+
+ /** @hide */
+ @IntDef(flag = false, prefix = { "SAP_CLIENT_BLOCKED_REASON_" }, value = {
+ SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER,
+ SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SapClientBlockedReason {}
+
+ /**
+ * If Soft Ap client is blocked, this reason code means that client doesn't exist in the
+ * specified configuration {@link SoftApConfiguration.Builder#setClientList(List, List)}
+ * and the {@link SoftApConfiguration.Builder#enableClientControlByUser(true)}
+ * is configured as well.
+ * @hide
+ */
+ @SystemApi
+ public static final int SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER = 0;
+
+ /**
+ * If Soft Ap client is blocked, this reason code means that no more clients can be
+ * associated to this AP since it reached maximum capacity. The maximum capacity is
+ * the minimum of {@link SoftApConfiguration.Builder#setMaxNumberOfClients(int)} and
+ * {@link SoftApCapability#getMaxSupportedClients} which get from
+ * {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS = 1;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"IFACE_IP_MODE_"}, value = {
@@ -3229,8 +3265,17 @@
/**
* Sets the Wi-Fi AP Configuration.
*
+ * If the API is called while the soft AP is enabled, the configuration will apply to
+ * the current soft AP if the new configuration only includes
+ * {@link SoftApConfiguration.Builder#setMaxNumberOfClients(int)}
+ * or {@link SoftApConfiguration.Builder#setShutdownTimeoutMillis(int)}
+ * or {@link SoftApConfiguration.Builder#enableClientControlByUser(boolean)}
+ * or {@link SoftApConfiguration.Builder#setClientList(List, List)}.
+ *
+ * Otherwise, the configuration changes will be applied when the Soft AP is next started
+ * (the framework will not stop/start the AP).
+ *
* @param softApConfig A valid SoftApConfiguration specifying the configuration of the SAP.
-
* @return {@code true} if the operation succeeded, {@code false} otherwise
*
* @hide
@@ -3460,7 +3505,8 @@
* {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED}
* @param failureReason reason when in failed state. One of
* {@link #SAP_START_FAILURE_GENERAL},
- * {@link #SAP_START_FAILURE_NO_CHANNEL}
+ * {@link #SAP_START_FAILURE_NO_CHANNEL},
+ * {@link #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}
*/
default void onStateChanged(@WifiApState int state, @SapStartFailure int failureReason) {}
@@ -3489,6 +3535,22 @@
// Do nothing: can be updated to add SoftApCapability details (e.g. meximum supported
// client number) to the UI.
}
+
+ /**
+ * Called when client trying to connect but device blocked the client with specific reason.
+ *
+ * Can be used to ask user to update client to allowed list or blocked list
+ * when reason is {@link SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER}, or
+ * indicate the block due to maximum supported client number limitation when reason is
+ * {@link SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS}.
+ *
+ * @param client the currently blocked client.
+ * @param blockedReason one of blocked reason from {@link SapClientBlockedReason}
+ */
+ default void onBlockedClientConnecting(@NonNull WifiClient client,
+ @SapClientBlockedReason int blockedReason) {
+ // Do nothing: can be used to ask user to update client to allowed list or blocked list.
+ }
}
/**
@@ -3555,6 +3617,19 @@
mCallback.onCapabilityChanged(capability);
});
}
+
+ @Override
+ public void onBlockedClientConnecting(@NonNull WifiClient client, int blockedReason) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "SoftApCallbackProxy: onBlockedClientConnecting: client=" + client
+ + " with reason = " + blockedReason);
+ }
+
+ Binder.clearCallingIdentity();
+ mExecutor.execute(() -> {
+ mCallback.onBlockedClientConnecting(client, blockedReason);
+ });
+ }
}
/**
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 2c39c32a..4f602fa 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -833,6 +833,7 @@
* delivered to the listener. It is possible that onFullResult will not be called for all
* results of the first scan if the listener was registered during the scan.
*
+ * @param executor the Executor on which to run the callback.
* @param listener specifies the object to report events to. This object is also treated as a
* key for this request, and must also be specified to cancel the request.
* Multiple requests should also not share this object.
@@ -955,15 +956,32 @@
* starts a single scan and reports results asynchronously
* @param settings specifies various parameters for the scan; for more information look at
* {@link ScanSettings}
- * @param workSource WorkSource to blame for power usage
* @param listener specifies the object to report events to. This object is also treated as a
* key for this scan, and must also be specified to cancel the scan. Multiple
* scans should also not share this object.
+ * @param workSource WorkSource to blame for power usage
*/
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
public void startScan(ScanSettings settings, ScanListener listener, WorkSource workSource) {
+ startScan(settings, null, listener, workSource);
+ }
+
+ /**
+ * starts a single scan and reports results asynchronously
+ * @param settings specifies various parameters for the scan; for more information look at
+ * {@link ScanSettings}
+ * @param executor the Executor on which to run the callback.
+ * @param listener specifies the object to report events to. This object is also treated as a
+ * key for this scan, and must also be specified to cancel the scan. Multiple
+ * scans should also not share this object.
+ * @param workSource WorkSource to blame for power usage
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ public void startScan(ScanSettings settings, @Nullable @CallbackExecutor Executor executor,
+ ScanListener listener, WorkSource workSource) {
Objects.requireNonNull(listener, "listener cannot be null");
- int key = addListener(listener);
+ int key = addListener(listener, executor);
if (key == INVALID_KEY) return;
validateChannel();
Bundle scanParams = new Bundle();
@@ -1029,16 +1047,17 @@
* {@link ScanSettings}
* @param pnoSettings specifies various parameters for PNO; for more information look at
* {@link PnoSettings}
+ * @param executor the Executor on which to run the callback.
* @param listener specifies the object to report events to. This object is also treated as a
* key for this scan, and must also be specified to cancel the scan. Multiple
* scans should also not share this object.
* {@hide}
*/
public void startConnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings,
- PnoScanListener listener) {
+ @NonNull @CallbackExecutor Executor executor, PnoScanListener listener) {
Objects.requireNonNull(listener, "listener cannot be null");
Objects.requireNonNull(pnoSettings, "pnoSettings cannot be null");
- int key = addListener(listener);
+ int key = addListener(listener, executor);
if (key == INVALID_KEY) return;
validateChannel();
pnoSettings.isConnected = true;
@@ -1057,10 +1076,10 @@
*/
@RequiresPermission(android.Manifest.permission.NETWORK_STACK)
public void startDisconnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings,
- PnoScanListener listener) {
+ @NonNull @CallbackExecutor Executor executor, PnoScanListener listener) {
Objects.requireNonNull(listener, "listener cannot be null");
Objects.requireNonNull(pnoSettings, "pnoSettings cannot be null");
- int key = addListener(listener);
+ int key = addListener(listener, executor);
if (key == INVALID_KEY) return;
validateChannel();
pnoSettings.isConnected = false;
diff --git a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
index eeea7e2..6884a4e 100644
--- a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
@@ -25,6 +25,8 @@
import org.junit.Test;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Random;
@SmallTest
@@ -112,12 +114,18 @@
@Test
public void testWpa2WithAllFieldCustomized() {
+ List<MacAddress> testBlockedClientList = new ArrayList<>();
+ List<MacAddress> testAllowedClientList = new ArrayList<>();
+ testBlockedClientList.add(MacAddress.fromString("11:22:33:44:55:66"));
+ testAllowedClientList.add(MacAddress.fromString("aa:bb:cc:dd:ee:ff"));
SoftApConfiguration original = new SoftApConfiguration.Builder()
.setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
.setChannel(149, SoftApConfiguration.BAND_5GHZ)
.setHiddenSsid(true)
.setMaxNumberOfClients(10)
.setShutdownTimeoutMillis(500000)
+ .enableClientControlByUser(true)
+ .setClientList(testBlockedClientList, testAllowedClientList)
.build();
assertThat(original.getPassphrase()).isEqualTo("secretsecret");
assertThat(original.getSecurityType()).isEqualTo(
@@ -127,6 +135,9 @@
assertThat(original.isHiddenSsid()).isEqualTo(true);
assertThat(original.getMaxNumberOfClients()).isEqualTo(10);
assertThat(original.getShutdownTimeoutMillis()).isEqualTo(500000);
+ assertThat(original.isClientControlByUserEnabled()).isEqualTo(true);
+ assertThat(original.getBlockedClientList()).isEqualTo(testBlockedClientList);
+ assertThat(original.getAllowedClientList()).isEqualTo(testAllowedClientList);
SoftApConfiguration unparceled = parcelUnparcel(original);
assertThat(unparceled).isNotSameAs(original);
@@ -238,4 +249,17 @@
.setShutdownTimeoutMillis(-1)
.build();
}
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testsetClientListExceptionWhenExistMacAddressInBothList() {
+ final MacAddress testMacAddress_1 = MacAddress.fromString("22:33:44:55:66:77");
+ final MacAddress testMacAddress_2 = MacAddress.fromString("aa:bb:cc:dd:ee:ff");
+ ArrayList<MacAddress> testAllowedClientList = new ArrayList<>();
+ testAllowedClientList.add(testMacAddress_1);
+ testAllowedClientList.add(testMacAddress_2);
+ ArrayList<MacAddress> testBlockedClientList = new ArrayList<>();
+ testBlockedClientList.add(testMacAddress_1);
+ SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
+ configBuilder.setClientList(testBlockedClientList, testAllowedClientList);
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 4b83718..5bdc344 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -70,6 +70,7 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.net.DhcpInfo;
+import android.net.MacAddress;
import android.net.wifi.WifiManager.LocalOnlyHotspotCallback;
import android.net.wifi.WifiManager.LocalOnlyHotspotObserver;
import android.net.wifi.WifiManager.LocalOnlyHotspotReservation;
@@ -897,6 +898,25 @@
}
/*
+ * Verify client-provided callback is being called through callback proxy
+ */
+ @Test
+ public void softApCallbackProxyCallsOnBlockedClientConnecting() throws Exception {
+ WifiClient testWifiClient = new WifiClient(MacAddress.fromString("22:33:44:55:66:77"));
+ ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor =
+ ArgumentCaptor.forClass(ISoftApCallback.Stub.class);
+ mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback);
+ verify(mWifiService).registerSoftApCallback(any(IBinder.class), callbackCaptor.capture(),
+ anyInt());
+
+ callbackCaptor.getValue().onBlockedClientConnecting(testWifiClient,
+ WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS);
+ mLooper.dispatchAll();
+ verify(mSoftApCallback).onBlockedClientConnecting(testWifiClient,
+ WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS);
+ }
+
+ /*
* Verify client-provided callback is being called through callback proxy on multiple events
*/
@Test
diff --git a/wifi/tests/src/android/net/wifi/WifiScannerTest.java b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
index 1af0bcb..0cc76b6 100644
--- a/wifi/tests/src/android/net/wifi/WifiScannerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
@@ -366,7 +366,7 @@
/**
* Test behavior of {@link WifiScanner#startDisconnectedPnoScan(ScanSettings, PnoSettings,
- * WifiScanner.PnoScanListener)}
+ * Executor, WifiScanner.PnoScanListener)}
* @throws Exception
*/
@Test
@@ -375,7 +375,8 @@
PnoSettings pnoSettings = new PnoSettings();
WifiScanner.PnoScanListener pnoScanListener = mock(WifiScanner.PnoScanListener.class);
- mWifiScanner.startDisconnectedPnoScan(scanSettings, pnoSettings, pnoScanListener);
+ mWifiScanner.startDisconnectedPnoScan(
+ scanSettings, pnoSettings, mock(Executor.class), pnoScanListener);
mLooper.dispatchAll();
ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
@@ -396,7 +397,7 @@
/**
* Test behavior of {@link WifiScanner#startConnectedPnoScan(ScanSettings, PnoSettings,
- * WifiScanner.PnoScanListener)}
+ * Executor, WifiScanner.PnoScanListener)}
* @throws Exception
*/
@Test
@@ -405,7 +406,8 @@
PnoSettings pnoSettings = new PnoSettings();
WifiScanner.PnoScanListener pnoScanListener = mock(WifiScanner.PnoScanListener.class);
- mWifiScanner.startConnectedPnoScan(scanSettings, pnoSettings, pnoScanListener);
+ mWifiScanner.startConnectedPnoScan(
+ scanSettings, pnoSettings, mock(Executor.class), pnoScanListener);
mLooper.dispatchAll();
ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
@@ -426,7 +428,7 @@
/**
* Test behavior of {@link WifiScanner#stopPnoScan(ScanListener)}
- * WifiScanner.PnoScanListener)}
+ * Executor, WifiScanner.PnoScanListener)}
* @throws Exception
*/
@Test
@@ -435,7 +437,8 @@
PnoSettings pnoSettings = new PnoSettings();
WifiScanner.PnoScanListener pnoScanListener = mock(WifiScanner.PnoScanListener.class);
- mWifiScanner.startDisconnectedPnoScan(scanSettings, pnoSettings, pnoScanListener);
+ mWifiScanner.startDisconnectedPnoScan(
+ scanSettings, pnoSettings, mock(Executor.class), pnoScanListener);
mLooper.dispatchAll();
mWifiScanner.stopPnoScan(pnoScanListener);
mLooper.dispatchAll();